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, SearchSettings,
67 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::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, 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, wrap_with_prefix};
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::hover_links::{find_url, find_url_from_range};
215use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
252 alt: true,
253 shift: true,
254 control: false,
255 platform: false,
256 function: false,
257};
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled(bool),
720}
721
722impl MinimapVisibility {
723 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
724 if mode.is_full() {
725 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
726 } else {
727 Self::Disabled
728 }
729 }
730
731 fn disabled(&self) -> bool {
732 match *self {
733 Self::Disabled => true,
734 _ => false,
735 }
736 }
737
738 fn visible(&self) -> bool {
739 match *self {
740 Self::Enabled(visible) => visible,
741 _ => false,
742 }
743 }
744
745 fn toggle_visibility(&self) -> Self {
746 match *self {
747 Self::Enabled(visible) => Self::Enabled(!visible),
748 Self::Disabled => Self::Disabled,
749 }
750 }
751}
752
753#[derive(Clone, Debug)]
754struct RunnableTasks {
755 templates: Vec<(TaskSourceKind, TaskTemplate)>,
756 offset: multi_buffer::Anchor,
757 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
758 column: u32,
759 // Values of all named captures, including those starting with '_'
760 extra_variables: HashMap<String, String>,
761 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
762 context_range: Range<BufferOffset>,
763}
764
765impl RunnableTasks {
766 fn resolve<'a>(
767 &'a self,
768 cx: &'a task::TaskContext,
769 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
770 self.templates.iter().filter_map(|(kind, template)| {
771 template
772 .resolve_task(&kind.to_id_base(), cx)
773 .map(|task| (kind.clone(), task))
774 })
775 }
776}
777
778#[derive(Clone)]
779pub struct ResolvedTasks {
780 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
781 position: Anchor,
782}
783
784#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
785struct BufferOffset(usize);
786
787// Addons allow storing per-editor state in other crates (e.g. Vim)
788pub trait Addon: 'static {
789 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
790
791 fn render_buffer_header_controls(
792 &self,
793 _: &ExcerptInfo,
794 _: &Window,
795 _: &App,
796 ) -> Option<AnyElement> {
797 None
798 }
799
800 fn to_any(&self) -> &dyn std::any::Any;
801
802 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
803 None
804 }
805}
806
807/// A set of caret positions, registered when the editor was edited.
808pub struct ChangeList {
809 changes: Vec<Vec<Anchor>>,
810 /// Currently "selected" change.
811 position: Option<usize>,
812}
813
814impl ChangeList {
815 pub fn new() -> Self {
816 Self {
817 changes: Vec::new(),
818 position: None,
819 }
820 }
821
822 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
823 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
824 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
825 if self.changes.is_empty() {
826 return None;
827 }
828
829 let prev = self.position.unwrap_or(self.changes.len());
830 let next = if direction == Direction::Prev {
831 prev.saturating_sub(count)
832 } else {
833 (prev + count).min(self.changes.len() - 1)
834 };
835 self.position = Some(next);
836 self.changes.get(next).map(|anchors| anchors.as_slice())
837 }
838
839 /// Adds a new change to the list, resetting the change list position.
840 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
841 self.position.take();
842 if pop_state {
843 self.changes.pop();
844 }
845 self.changes.push(new_positions.clone());
846 }
847
848 pub fn last(&self) -> Option<&[Anchor]> {
849 self.changes.last().map(|anchors| anchors.as_slice())
850 }
851}
852
853#[derive(Clone)]
854struct InlineBlamePopoverState {
855 scroll_handle: ScrollHandle,
856 commit_message: Option<ParsedCommitMessage>,
857 markdown: Entity<Markdown>,
858}
859
860struct InlineBlamePopover {
861 position: gpui::Point<Pixels>,
862 show_task: Option<Task<()>>,
863 hide_task: Option<Task<()>>,
864 popover_bounds: Option<Bounds<Pixels>>,
865 popover_state: InlineBlamePopoverState,
866}
867
868/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
869/// a breakpoint on them.
870#[derive(Clone, Copy, Debug)]
871struct PhantomBreakpointIndicator {
872 display_row: DisplayRow,
873 /// There's a small debounce between hovering over the line and showing the indicator.
874 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
875 is_active: bool,
876 collides_with_existing_breakpoint: bool,
877}
878/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
879///
880/// See the [module level documentation](self) for more information.
881pub struct Editor {
882 focus_handle: FocusHandle,
883 last_focused_descendant: Option<WeakFocusHandle>,
884 /// The text buffer being edited
885 buffer: Entity<MultiBuffer>,
886 /// Map of how text in the buffer should be displayed.
887 /// Handles soft wraps, folds, fake inlay text insertions, etc.
888 pub display_map: Entity<DisplayMap>,
889 pub selections: SelectionsCollection,
890 pub scroll_manager: ScrollManager,
891 /// When inline assist editors are linked, they all render cursors because
892 /// typing enters text into each of them, even the ones that aren't focused.
893 pub(crate) show_cursor_when_unfocused: bool,
894 columnar_selection_tail: Option<Anchor>,
895 add_selections_state: Option<AddSelectionsState>,
896 select_next_state: Option<SelectNextState>,
897 select_prev_state: Option<SelectNextState>,
898 selection_history: SelectionHistory,
899 autoclose_regions: Vec<AutocloseRegion>,
900 snippet_stack: InvalidationStack<SnippetState>,
901 select_syntax_node_history: SelectSyntaxNodeHistory,
902 ime_transaction: Option<TransactionId>,
903 pub diagnostics_max_severity: DiagnosticSeverity,
904 active_diagnostics: ActiveDiagnostic,
905 show_inline_diagnostics: bool,
906 inline_diagnostics_update: Task<()>,
907 inline_diagnostics_enabled: bool,
908 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
909 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
910 hard_wrap: Option<usize>,
911
912 // TODO: make this a access method
913 pub project: Option<Entity<Project>>,
914 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
915 completion_provider: Option<Box<dyn CompletionProvider>>,
916 collaboration_hub: Option<Box<dyn CollaborationHub>>,
917 blink_manager: Entity<BlinkManager>,
918 show_cursor_names: bool,
919 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
920 pub show_local_selections: bool,
921 mode: EditorMode,
922 show_breadcrumbs: bool,
923 show_gutter: bool,
924 show_scrollbars: bool,
925 minimap_visibility: MinimapVisibility,
926 offset_content: bool,
927 disable_expand_excerpt_buttons: bool,
928 show_line_numbers: Option<bool>,
929 use_relative_line_numbers: Option<bool>,
930 show_git_diff_gutter: Option<bool>,
931 show_code_actions: Option<bool>,
932 show_runnables: Option<bool>,
933 show_breakpoints: Option<bool>,
934 show_wrap_guides: Option<bool>,
935 show_indent_guides: Option<bool>,
936 placeholder_text: Option<Arc<str>>,
937 highlight_order: usize,
938 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
939 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
940 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
941 scrollbar_marker_state: ScrollbarMarkerState,
942 active_indent_guides_state: ActiveIndentGuidesState,
943 nav_history: Option<ItemNavHistory>,
944 context_menu: RefCell<Option<CodeContextMenu>>,
945 context_menu_options: Option<ContextMenuOptions>,
946 mouse_context_menu: Option<MouseContextMenu>,
947 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
948 inline_blame_popover: Option<InlineBlamePopover>,
949 signature_help_state: SignatureHelpState,
950 auto_signature_help: Option<bool>,
951 find_all_references_task_sources: Vec<Anchor>,
952 next_completion_id: CompletionId,
953 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
954 code_actions_task: Option<Task<Result<()>>>,
955 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
957 document_highlights_task: Option<Task<()>>,
958 linked_editing_range_task: Option<Task<Option<()>>>,
959 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
960 pending_rename: Option<RenameState>,
961 searchable: bool,
962 cursor_shape: CursorShape,
963 current_line_highlight: Option<CurrentLineHighlight>,
964 collapse_matches: bool,
965 autoindent_mode: Option<AutoindentMode>,
966 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
967 input_enabled: bool,
968 use_modal_editing: bool,
969 read_only: bool,
970 leader_id: Option<CollaboratorId>,
971 remote_id: Option<ViewId>,
972 pub hover_state: HoverState,
973 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
974 gutter_hovered: bool,
975 hovered_link_state: Option<HoveredLinkState>,
976 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
977 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
978 active_inline_completion: Option<InlineCompletionState>,
979 /// Used to prevent flickering as the user types while the menu is open
980 stale_inline_completion_in_menu: Option<InlineCompletionState>,
981 edit_prediction_settings: EditPredictionSettings,
982 inline_completions_hidden_for_vim_mode: bool,
983 show_inline_completions_override: Option<bool>,
984 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
985 edit_prediction_preview: EditPredictionPreview,
986 edit_prediction_indent_conflict: bool,
987 edit_prediction_requires_modifier_in_indent_conflict: bool,
988 inlay_hint_cache: InlayHintCache,
989 next_inlay_id: usize,
990 _subscriptions: Vec<Subscription>,
991 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
992 gutter_dimensions: GutterDimensions,
993 style: Option<EditorStyle>,
994 text_style_refinement: Option<TextStyleRefinement>,
995 next_editor_action_id: EditorActionId,
996 editor_actions:
997 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
998 use_autoclose: bool,
999 use_auto_surround: bool,
1000 auto_replace_emoji_shortcode: bool,
1001 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1002 show_git_blame_gutter: bool,
1003 show_git_blame_inline: bool,
1004 show_git_blame_inline_delay_task: Option<Task<()>>,
1005 git_blame_inline_enabled: bool,
1006 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1007 serialize_dirty_buffers: bool,
1008 show_selection_menu: Option<bool>,
1009 blame: Option<Entity<GitBlame>>,
1010 blame_subscription: Option<Subscription>,
1011 custom_context_menu: Option<
1012 Box<
1013 dyn 'static
1014 + Fn(
1015 &mut Self,
1016 DisplayPoint,
1017 &mut Window,
1018 &mut Context<Self>,
1019 ) -> Option<Entity<ui::ContextMenu>>,
1020 >,
1021 >,
1022 last_bounds: Option<Bounds<Pixels>>,
1023 last_position_map: Option<Rc<PositionMap>>,
1024 expect_bounds_change: Option<Bounds<Pixels>>,
1025 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1026 tasks_update_task: Option<Task<()>>,
1027 breakpoint_store: Option<Entity<BreakpointStore>>,
1028 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1029 in_project_search: bool,
1030 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1031 breadcrumb_header: Option<String>,
1032 focused_block: Option<FocusedBlock>,
1033 next_scroll_position: NextScrollCursorCenterTopBottom,
1034 addons: HashMap<TypeId, Box<dyn Addon>>,
1035 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1036 load_diff_task: Option<Shared<Task<()>>>,
1037 /// Whether we are temporarily displaying a diff other than git's
1038 temporary_diff_override: bool,
1039 selection_mark_mode: bool,
1040 toggle_fold_multiple_buffers: Task<()>,
1041 _scroll_cursor_center_top_bottom_task: Task<()>,
1042 serialize_selections: Task<()>,
1043 serialize_folds: Task<()>,
1044 mouse_cursor_hidden: bool,
1045 minimap: Option<Entity<Self>>,
1046 hide_mouse_mode: HideMouseMode,
1047 pub change_list: ChangeList,
1048 inline_value_cache: InlineValueCache,
1049}
1050
1051#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1052enum NextScrollCursorCenterTopBottom {
1053 #[default]
1054 Center,
1055 Top,
1056 Bottom,
1057}
1058
1059impl NextScrollCursorCenterTopBottom {
1060 fn next(&self) -> Self {
1061 match self {
1062 Self::Center => Self::Top,
1063 Self::Top => Self::Bottom,
1064 Self::Bottom => Self::Center,
1065 }
1066 }
1067}
1068
1069#[derive(Clone)]
1070pub struct EditorSnapshot {
1071 pub mode: EditorMode,
1072 show_gutter: bool,
1073 show_line_numbers: Option<bool>,
1074 show_git_diff_gutter: Option<bool>,
1075 show_runnables: Option<bool>,
1076 show_breakpoints: Option<bool>,
1077 git_blame_gutter_max_author_length: Option<usize>,
1078 pub display_snapshot: DisplaySnapshot,
1079 pub placeholder_text: Option<Arc<str>>,
1080 is_focused: bool,
1081 scroll_anchor: ScrollAnchor,
1082 ongoing_scroll: OngoingScroll,
1083 current_line_highlight: CurrentLineHighlight,
1084 gutter_hovered: bool,
1085}
1086
1087#[derive(Default, Debug, Clone, Copy)]
1088pub struct GutterDimensions {
1089 pub left_padding: Pixels,
1090 pub right_padding: Pixels,
1091 pub width: Pixels,
1092 pub margin: Pixels,
1093 pub git_blame_entries_width: Option<Pixels>,
1094}
1095
1096impl GutterDimensions {
1097 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1098 Self {
1099 margin: Self::default_gutter_margin(font_id, font_size, cx),
1100 ..Default::default()
1101 }
1102 }
1103
1104 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1105 -cx.text_system().descent(font_id, font_size)
1106 }
1107 /// The full width of the space taken up by the gutter.
1108 pub fn full_width(&self) -> Pixels {
1109 self.margin + self.width
1110 }
1111
1112 /// The width of the space reserved for the fold indicators,
1113 /// use alongside 'justify_end' and `gutter_width` to
1114 /// right align content with the line numbers
1115 pub fn fold_area_width(&self) -> Pixels {
1116 self.margin + self.right_padding
1117 }
1118}
1119
1120#[derive(Debug)]
1121pub struct RemoteSelection {
1122 pub replica_id: ReplicaId,
1123 pub selection: Selection<Anchor>,
1124 pub cursor_shape: CursorShape,
1125 pub collaborator_id: CollaboratorId,
1126 pub line_mode: bool,
1127 pub user_name: Option<SharedString>,
1128 pub color: PlayerColor,
1129}
1130
1131#[derive(Clone, Debug)]
1132struct SelectionHistoryEntry {
1133 selections: Arc<[Selection<Anchor>]>,
1134 select_next_state: Option<SelectNextState>,
1135 select_prev_state: Option<SelectNextState>,
1136 add_selections_state: Option<AddSelectionsState>,
1137}
1138
1139enum SelectionHistoryMode {
1140 Normal,
1141 Undoing,
1142 Redoing,
1143}
1144
1145#[derive(Clone, PartialEq, Eq, Hash)]
1146struct HoveredCursor {
1147 replica_id: u16,
1148 selection_id: usize,
1149}
1150
1151impl Default for SelectionHistoryMode {
1152 fn default() -> Self {
1153 Self::Normal
1154 }
1155}
1156
1157#[derive(Default)]
1158struct SelectionHistory {
1159 #[allow(clippy::type_complexity)]
1160 selections_by_transaction:
1161 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1162 mode: SelectionHistoryMode,
1163 undo_stack: VecDeque<SelectionHistoryEntry>,
1164 redo_stack: VecDeque<SelectionHistoryEntry>,
1165}
1166
1167impl SelectionHistory {
1168 fn insert_transaction(
1169 &mut self,
1170 transaction_id: TransactionId,
1171 selections: Arc<[Selection<Anchor>]>,
1172 ) {
1173 self.selections_by_transaction
1174 .insert(transaction_id, (selections, None));
1175 }
1176
1177 #[allow(clippy::type_complexity)]
1178 fn transaction(
1179 &self,
1180 transaction_id: TransactionId,
1181 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1182 self.selections_by_transaction.get(&transaction_id)
1183 }
1184
1185 #[allow(clippy::type_complexity)]
1186 fn transaction_mut(
1187 &mut self,
1188 transaction_id: TransactionId,
1189 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1190 self.selections_by_transaction.get_mut(&transaction_id)
1191 }
1192
1193 fn push(&mut self, entry: SelectionHistoryEntry) {
1194 if !entry.selections.is_empty() {
1195 match self.mode {
1196 SelectionHistoryMode::Normal => {
1197 self.push_undo(entry);
1198 self.redo_stack.clear();
1199 }
1200 SelectionHistoryMode::Undoing => self.push_redo(entry),
1201 SelectionHistoryMode::Redoing => self.push_undo(entry),
1202 }
1203 }
1204 }
1205
1206 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1207 if self
1208 .undo_stack
1209 .back()
1210 .map_or(true, |e| e.selections != entry.selections)
1211 {
1212 self.undo_stack.push_back(entry);
1213 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1214 self.undo_stack.pop_front();
1215 }
1216 }
1217 }
1218
1219 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1220 if self
1221 .redo_stack
1222 .back()
1223 .map_or(true, |e| e.selections != entry.selections)
1224 {
1225 self.redo_stack.push_back(entry);
1226 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1227 self.redo_stack.pop_front();
1228 }
1229 }
1230 }
1231}
1232
1233#[derive(Clone, Copy)]
1234pub struct RowHighlightOptions {
1235 pub autoscroll: bool,
1236 pub include_gutter: bool,
1237}
1238
1239impl Default for RowHighlightOptions {
1240 fn default() -> Self {
1241 Self {
1242 autoscroll: Default::default(),
1243 include_gutter: true,
1244 }
1245 }
1246}
1247
1248struct RowHighlight {
1249 index: usize,
1250 range: Range<Anchor>,
1251 color: Hsla,
1252 options: RowHighlightOptions,
1253 type_id: TypeId,
1254}
1255
1256#[derive(Clone, Debug)]
1257struct AddSelectionsState {
1258 above: bool,
1259 stack: Vec<usize>,
1260}
1261
1262#[derive(Clone)]
1263struct SelectNextState {
1264 query: AhoCorasick,
1265 wordwise: bool,
1266 done: bool,
1267}
1268
1269impl std::fmt::Debug for SelectNextState {
1270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1271 f.debug_struct(std::any::type_name::<Self>())
1272 .field("wordwise", &self.wordwise)
1273 .field("done", &self.done)
1274 .finish()
1275 }
1276}
1277
1278#[derive(Debug)]
1279struct AutocloseRegion {
1280 selection_id: usize,
1281 range: Range<Anchor>,
1282 pair: BracketPair,
1283}
1284
1285#[derive(Debug)]
1286struct SnippetState {
1287 ranges: Vec<Vec<Range<Anchor>>>,
1288 active_index: usize,
1289 choices: Vec<Option<Vec<String>>>,
1290}
1291
1292#[doc(hidden)]
1293pub struct RenameState {
1294 pub range: Range<Anchor>,
1295 pub old_name: Arc<str>,
1296 pub editor: Entity<Editor>,
1297 block_id: CustomBlockId,
1298}
1299
1300struct InvalidationStack<T>(Vec<T>);
1301
1302struct RegisteredInlineCompletionProvider {
1303 provider: Arc<dyn InlineCompletionProviderHandle>,
1304 _subscription: Subscription,
1305}
1306
1307#[derive(Debug, PartialEq, Eq)]
1308pub struct ActiveDiagnosticGroup {
1309 pub active_range: Range<Anchor>,
1310 pub active_message: String,
1311 pub group_id: usize,
1312 pub blocks: HashSet<CustomBlockId>,
1313}
1314
1315#[derive(Debug, PartialEq, Eq)]
1316
1317pub(crate) enum ActiveDiagnostic {
1318 None,
1319 All,
1320 Group(ActiveDiagnosticGroup),
1321}
1322
1323#[derive(Serialize, Deserialize, Clone, Debug)]
1324pub struct ClipboardSelection {
1325 /// The number of bytes in this selection.
1326 pub len: usize,
1327 /// Whether this was a full-line selection.
1328 pub is_entire_line: bool,
1329 /// The indentation of the first line when this content was originally copied.
1330 pub first_line_indent: u32,
1331}
1332
1333// selections, scroll behavior, was newest selection reversed
1334type SelectSyntaxNodeHistoryState = (
1335 Box<[Selection<usize>]>,
1336 SelectSyntaxNodeScrollBehavior,
1337 bool,
1338);
1339
1340#[derive(Default)]
1341struct SelectSyntaxNodeHistory {
1342 stack: Vec<SelectSyntaxNodeHistoryState>,
1343 // disable temporarily to allow changing selections without losing the stack
1344 pub disable_clearing: bool,
1345}
1346
1347impl SelectSyntaxNodeHistory {
1348 pub fn try_clear(&mut self) {
1349 if !self.disable_clearing {
1350 self.stack.clear();
1351 }
1352 }
1353
1354 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1355 self.stack.push(selection);
1356 }
1357
1358 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1359 self.stack.pop()
1360 }
1361}
1362
1363enum SelectSyntaxNodeScrollBehavior {
1364 CursorTop,
1365 FitSelection,
1366 CursorBottom,
1367}
1368
1369#[derive(Debug)]
1370pub(crate) struct NavigationData {
1371 cursor_anchor: Anchor,
1372 cursor_position: Point,
1373 scroll_anchor: ScrollAnchor,
1374 scroll_top_row: u32,
1375}
1376
1377#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1378pub enum GotoDefinitionKind {
1379 Symbol,
1380 Declaration,
1381 Type,
1382 Implementation,
1383}
1384
1385#[derive(Debug, Clone)]
1386enum InlayHintRefreshReason {
1387 ModifiersChanged(bool),
1388 Toggle(bool),
1389 SettingsChange(InlayHintSettings),
1390 NewLinesShown,
1391 BufferEdited(HashSet<Arc<Language>>),
1392 RefreshRequested,
1393 ExcerptsRemoved(Vec<ExcerptId>),
1394}
1395
1396impl InlayHintRefreshReason {
1397 fn description(&self) -> &'static str {
1398 match self {
1399 Self::ModifiersChanged(_) => "modifiers changed",
1400 Self::Toggle(_) => "toggle",
1401 Self::SettingsChange(_) => "settings change",
1402 Self::NewLinesShown => "new lines shown",
1403 Self::BufferEdited(_) => "buffer edited",
1404 Self::RefreshRequested => "refresh requested",
1405 Self::ExcerptsRemoved(_) => "excerpts removed",
1406 }
1407 }
1408}
1409
1410pub enum FormatTarget {
1411 Buffers,
1412 Ranges(Vec<Range<MultiBufferPoint>>),
1413}
1414
1415pub(crate) struct FocusedBlock {
1416 id: BlockId,
1417 focus_handle: WeakFocusHandle,
1418}
1419
1420#[derive(Clone)]
1421enum JumpData {
1422 MultiBufferRow {
1423 row: MultiBufferRow,
1424 line_offset_from_top: u32,
1425 },
1426 MultiBufferPoint {
1427 excerpt_id: ExcerptId,
1428 position: Point,
1429 anchor: text::Anchor,
1430 line_offset_from_top: u32,
1431 },
1432}
1433
1434pub enum MultibufferSelectionMode {
1435 First,
1436 All,
1437}
1438
1439#[derive(Clone, Copy, Debug, Default)]
1440pub struct RewrapOptions {
1441 pub override_language_settings: bool,
1442 pub preserve_existing_whitespace: bool,
1443}
1444
1445impl Editor {
1446 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1447 let buffer = cx.new(|cx| Buffer::local("", cx));
1448 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1449 Self::new(
1450 EditorMode::SingleLine { auto_width: false },
1451 buffer,
1452 None,
1453 window,
1454 cx,
1455 )
1456 }
1457
1458 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1459 let buffer = cx.new(|cx| Buffer::local("", cx));
1460 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1461 Self::new(EditorMode::full(), buffer, None, window, cx)
1462 }
1463
1464 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1465 let buffer = cx.new(|cx| Buffer::local("", cx));
1466 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1467 Self::new(
1468 EditorMode::SingleLine { auto_width: true },
1469 buffer,
1470 None,
1471 window,
1472 cx,
1473 )
1474 }
1475
1476 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1477 let buffer = cx.new(|cx| Buffer::local("", cx));
1478 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1479 Self::new(
1480 EditorMode::AutoHeight { max_lines },
1481 buffer,
1482 None,
1483 window,
1484 cx,
1485 )
1486 }
1487
1488 pub fn for_buffer(
1489 buffer: Entity<Buffer>,
1490 project: Option<Entity<Project>>,
1491 window: &mut Window,
1492 cx: &mut Context<Self>,
1493 ) -> Self {
1494 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1495 Self::new(EditorMode::full(), buffer, project, window, cx)
1496 }
1497
1498 pub fn for_multibuffer(
1499 buffer: Entity<MultiBuffer>,
1500 project: Option<Entity<Project>>,
1501 window: &mut Window,
1502 cx: &mut Context<Self>,
1503 ) -> Self {
1504 Self::new(EditorMode::full(), buffer, project, window, cx)
1505 }
1506
1507 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1508 let mut clone = Self::new(
1509 self.mode.clone(),
1510 self.buffer.clone(),
1511 self.project.clone(),
1512 window,
1513 cx,
1514 );
1515 self.display_map.update(cx, |display_map, cx| {
1516 let snapshot = display_map.snapshot(cx);
1517 clone.display_map.update(cx, |display_map, cx| {
1518 display_map.set_state(&snapshot, cx);
1519 });
1520 });
1521 clone.folds_did_change(cx);
1522 clone.selections.clone_state(&self.selections);
1523 clone.scroll_manager.clone_state(&self.scroll_manager);
1524 clone.searchable = self.searchable;
1525 clone.read_only = self.read_only;
1526 clone
1527 }
1528
1529 pub fn new(
1530 mode: EditorMode,
1531 buffer: Entity<MultiBuffer>,
1532 project: Option<Entity<Project>>,
1533 window: &mut Window,
1534 cx: &mut Context<Self>,
1535 ) -> Self {
1536 Editor::new_internal(mode, buffer, project, None, window, cx)
1537 }
1538
1539 fn new_internal(
1540 mode: EditorMode,
1541 buffer: Entity<MultiBuffer>,
1542 project: Option<Entity<Project>>,
1543 display_map: Option<Entity<DisplayMap>>,
1544 window: &mut Window,
1545 cx: &mut Context<Self>,
1546 ) -> Self {
1547 debug_assert!(
1548 display_map.is_none() || mode.is_minimap(),
1549 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1550 );
1551
1552 let full_mode = mode.is_full();
1553 let diagnostics_max_severity = if full_mode {
1554 EditorSettings::get_global(cx)
1555 .diagnostics_max_severity
1556 .unwrap_or(DiagnosticSeverity::Hint)
1557 } else {
1558 DiagnosticSeverity::Off
1559 };
1560 let style = window.text_style();
1561 let font_size = style.font_size.to_pixels(window.rem_size());
1562 let editor = cx.entity().downgrade();
1563 let fold_placeholder = FoldPlaceholder {
1564 constrain_width: true,
1565 render: Arc::new(move |fold_id, fold_range, cx| {
1566 let editor = editor.clone();
1567 div()
1568 .id(fold_id)
1569 .bg(cx.theme().colors().ghost_element_background)
1570 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1571 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1572 .rounded_xs()
1573 .size_full()
1574 .cursor_pointer()
1575 .child("⋯")
1576 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1577 .on_click(move |_, _window, cx| {
1578 editor
1579 .update(cx, |editor, cx| {
1580 editor.unfold_ranges(
1581 &[fold_range.start..fold_range.end],
1582 true,
1583 false,
1584 cx,
1585 );
1586 cx.stop_propagation();
1587 })
1588 .ok();
1589 })
1590 .into_any()
1591 }),
1592 merge_adjacent: true,
1593 ..FoldPlaceholder::default()
1594 };
1595 let display_map = display_map.unwrap_or_else(|| {
1596 cx.new(|cx| {
1597 DisplayMap::new(
1598 buffer.clone(),
1599 style.font(),
1600 font_size,
1601 None,
1602 FILE_HEADER_HEIGHT,
1603 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1604 fold_placeholder,
1605 diagnostics_max_severity,
1606 cx,
1607 )
1608 })
1609 });
1610
1611 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1612
1613 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1614
1615 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1616 .then(|| language_settings::SoftWrap::None);
1617
1618 let mut project_subscriptions = Vec::new();
1619 if mode.is_full() {
1620 if let Some(project) = project.as_ref() {
1621 project_subscriptions.push(cx.subscribe_in(
1622 project,
1623 window,
1624 |editor, _, event, window, cx| match event {
1625 project::Event::RefreshCodeLens => {
1626 // we always query lens with actions, without storing them, always refreshing them
1627 }
1628 project::Event::RefreshInlayHints => {
1629 editor
1630 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1631 }
1632 project::Event::SnippetEdit(id, snippet_edits) => {
1633 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1634 let focus_handle = editor.focus_handle(cx);
1635 if focus_handle.is_focused(window) {
1636 let snapshot = buffer.read(cx).snapshot();
1637 for (range, snippet) in snippet_edits {
1638 let editor_range =
1639 language::range_from_lsp(*range).to_offset(&snapshot);
1640 editor
1641 .insert_snippet(
1642 &[editor_range],
1643 snippet.clone(),
1644 window,
1645 cx,
1646 )
1647 .ok();
1648 }
1649 }
1650 }
1651 }
1652 _ => {}
1653 },
1654 ));
1655 if let Some(task_inventory) = project
1656 .read(cx)
1657 .task_store()
1658 .read(cx)
1659 .task_inventory()
1660 .cloned()
1661 {
1662 project_subscriptions.push(cx.observe_in(
1663 &task_inventory,
1664 window,
1665 |editor, _, window, cx| {
1666 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1667 },
1668 ));
1669 };
1670
1671 project_subscriptions.push(cx.subscribe_in(
1672 &project.read(cx).breakpoint_store(),
1673 window,
1674 |editor, _, event, window, cx| match event {
1675 BreakpointStoreEvent::ClearDebugLines => {
1676 editor.clear_row_highlights::<ActiveDebugLine>();
1677 editor.refresh_inline_values(cx);
1678 }
1679 BreakpointStoreEvent::SetDebugLine => {
1680 if editor.go_to_active_debug_line(window, cx) {
1681 cx.stop_propagation();
1682 }
1683
1684 editor.refresh_inline_values(cx);
1685 }
1686 _ => {}
1687 },
1688 ));
1689 }
1690 }
1691
1692 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1693
1694 let inlay_hint_settings =
1695 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1696 let focus_handle = cx.focus_handle();
1697 cx.on_focus(&focus_handle, window, Self::handle_focus)
1698 .detach();
1699 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1700 .detach();
1701 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1702 .detach();
1703 cx.on_blur(&focus_handle, window, Self::handle_blur)
1704 .detach();
1705
1706 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1707 Some(false)
1708 } else {
1709 None
1710 };
1711
1712 let breakpoint_store = match (&mode, project.as_ref()) {
1713 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1714 _ => None,
1715 };
1716
1717 let mut code_action_providers = Vec::new();
1718 let mut load_uncommitted_diff = None;
1719 if let Some(project) = project.clone() {
1720 load_uncommitted_diff = Some(
1721 update_uncommitted_diff_for_buffer(
1722 cx.entity(),
1723 &project,
1724 buffer.read(cx).all_buffers(),
1725 buffer.clone(),
1726 cx,
1727 )
1728 .shared(),
1729 );
1730 code_action_providers.push(Rc::new(project) as Rc<_>);
1731 }
1732
1733 let mut this = Self {
1734 focus_handle,
1735 show_cursor_when_unfocused: false,
1736 last_focused_descendant: None,
1737 buffer: buffer.clone(),
1738 display_map: display_map.clone(),
1739 selections,
1740 scroll_manager: ScrollManager::new(cx),
1741 columnar_selection_tail: None,
1742 add_selections_state: None,
1743 select_next_state: None,
1744 select_prev_state: None,
1745 selection_history: SelectionHistory::default(),
1746 autoclose_regions: Vec::new(),
1747 snippet_stack: InvalidationStack::default(),
1748 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1749 ime_transaction: None,
1750 active_diagnostics: ActiveDiagnostic::None,
1751 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1752 inline_diagnostics_update: Task::ready(()),
1753 inline_diagnostics: Vec::new(),
1754 soft_wrap_mode_override,
1755 diagnostics_max_severity,
1756 hard_wrap: None,
1757 completion_provider: project.clone().map(|project| Box::new(project) as _),
1758 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1759 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1760 project,
1761 blink_manager: blink_manager.clone(),
1762 show_local_selections: true,
1763 show_scrollbars: full_mode,
1764 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1765 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1766 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1767 show_gutter: mode.is_full(),
1768 show_line_numbers: None,
1769 use_relative_line_numbers: None,
1770 disable_expand_excerpt_buttons: false,
1771 show_git_diff_gutter: None,
1772 show_code_actions: None,
1773 show_runnables: None,
1774 show_breakpoints: None,
1775 show_wrap_guides: None,
1776 show_indent_guides,
1777 placeholder_text: None,
1778 highlight_order: 0,
1779 highlighted_rows: HashMap::default(),
1780 background_highlights: TreeMap::default(),
1781 gutter_highlights: TreeMap::default(),
1782 scrollbar_marker_state: ScrollbarMarkerState::default(),
1783 active_indent_guides_state: ActiveIndentGuidesState::default(),
1784 nav_history: None,
1785 context_menu: RefCell::new(None),
1786 context_menu_options: None,
1787 mouse_context_menu: None,
1788 completion_tasks: Vec::new(),
1789 inline_blame_popover: None,
1790 signature_help_state: SignatureHelpState::default(),
1791 auto_signature_help: None,
1792 find_all_references_task_sources: Vec::new(),
1793 next_completion_id: 0,
1794 next_inlay_id: 0,
1795 code_action_providers,
1796 available_code_actions: None,
1797 code_actions_task: None,
1798 quick_selection_highlight_task: None,
1799 debounced_selection_highlight_task: None,
1800 document_highlights_task: None,
1801 linked_editing_range_task: None,
1802 pending_rename: None,
1803 searchable: true,
1804 cursor_shape: EditorSettings::get_global(cx)
1805 .cursor_shape
1806 .unwrap_or_default(),
1807 current_line_highlight: None,
1808 autoindent_mode: Some(AutoindentMode::EachLine),
1809 collapse_matches: false,
1810 workspace: None,
1811 input_enabled: true,
1812 use_modal_editing: mode.is_full(),
1813 read_only: mode.is_minimap(),
1814 use_autoclose: true,
1815 use_auto_surround: true,
1816 auto_replace_emoji_shortcode: false,
1817 jsx_tag_auto_close_enabled_in_any_buffer: false,
1818 leader_id: None,
1819 remote_id: None,
1820 hover_state: HoverState::default(),
1821 pending_mouse_down: None,
1822 hovered_link_state: None,
1823 edit_prediction_provider: None,
1824 active_inline_completion: None,
1825 stale_inline_completion_in_menu: None,
1826 edit_prediction_preview: EditPredictionPreview::Inactive {
1827 released_too_fast: false,
1828 },
1829 inline_diagnostics_enabled: mode.is_full(),
1830 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1831 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1832
1833 gutter_hovered: false,
1834 pixel_position_of_newest_cursor: None,
1835 last_bounds: None,
1836 last_position_map: None,
1837 expect_bounds_change: None,
1838 gutter_dimensions: GutterDimensions::default(),
1839 style: None,
1840 show_cursor_names: false,
1841 hovered_cursors: HashMap::default(),
1842 next_editor_action_id: EditorActionId::default(),
1843 editor_actions: Rc::default(),
1844 inline_completions_hidden_for_vim_mode: false,
1845 show_inline_completions_override: None,
1846 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1847 edit_prediction_settings: EditPredictionSettings::Disabled,
1848 edit_prediction_indent_conflict: false,
1849 edit_prediction_requires_modifier_in_indent_conflict: true,
1850 custom_context_menu: None,
1851 show_git_blame_gutter: false,
1852 show_git_blame_inline: false,
1853 show_selection_menu: None,
1854 show_git_blame_inline_delay_task: None,
1855 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1856 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1857 serialize_dirty_buffers: !mode.is_minimap()
1858 && ProjectSettings::get_global(cx)
1859 .session
1860 .restore_unsaved_buffers,
1861 blame: None,
1862 blame_subscription: None,
1863 tasks: BTreeMap::default(),
1864
1865 breakpoint_store,
1866 gutter_breakpoint_indicator: (None, None),
1867 _subscriptions: vec![
1868 cx.observe(&buffer, Self::on_buffer_changed),
1869 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1870 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1871 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1872 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1873 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1874 cx.observe_window_activation(window, |editor, window, cx| {
1875 let active = window.is_window_active();
1876 editor.blink_manager.update(cx, |blink_manager, cx| {
1877 if active {
1878 blink_manager.enable(cx);
1879 } else {
1880 blink_manager.disable(cx);
1881 }
1882 });
1883 }),
1884 ],
1885 tasks_update_task: None,
1886 linked_edit_ranges: Default::default(),
1887 in_project_search: false,
1888 previous_search_ranges: None,
1889 breadcrumb_header: None,
1890 focused_block: None,
1891 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1892 addons: HashMap::default(),
1893 registered_buffers: HashMap::default(),
1894 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1895 selection_mark_mode: false,
1896 toggle_fold_multiple_buffers: Task::ready(()),
1897 serialize_selections: Task::ready(()),
1898 serialize_folds: Task::ready(()),
1899 text_style_refinement: None,
1900 load_diff_task: load_uncommitted_diff,
1901 temporary_diff_override: false,
1902 mouse_cursor_hidden: false,
1903 minimap: None,
1904 hide_mouse_mode: EditorSettings::get_global(cx)
1905 .hide_mouse
1906 .unwrap_or_default(),
1907 change_list: ChangeList::new(),
1908 mode,
1909 };
1910 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1911 this._subscriptions
1912 .push(cx.observe(breakpoints, |_, _, cx| {
1913 cx.notify();
1914 }));
1915 }
1916 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1917 this._subscriptions.extend(project_subscriptions);
1918
1919 this._subscriptions.push(cx.subscribe_in(
1920 &cx.entity(),
1921 window,
1922 |editor, _, e: &EditorEvent, window, cx| match e {
1923 EditorEvent::ScrollPositionChanged { local, .. } => {
1924 if *local {
1925 let new_anchor = editor.scroll_manager.anchor();
1926 let snapshot = editor.snapshot(window, cx);
1927 editor.update_restoration_data(cx, move |data| {
1928 data.scroll_position = (
1929 new_anchor.top_row(&snapshot.buffer_snapshot),
1930 new_anchor.offset,
1931 );
1932 });
1933 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1934 editor.inline_blame_popover.take();
1935 }
1936 }
1937 EditorEvent::Edited { .. } => {
1938 if !vim_enabled(cx) {
1939 let (map, selections) = editor.selections.all_adjusted_display(cx);
1940 let pop_state = editor
1941 .change_list
1942 .last()
1943 .map(|previous| {
1944 previous.len() == selections.len()
1945 && previous.iter().enumerate().all(|(ix, p)| {
1946 p.to_display_point(&map).row()
1947 == selections[ix].head().row()
1948 })
1949 })
1950 .unwrap_or(false);
1951 let new_positions = selections
1952 .into_iter()
1953 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1954 .collect();
1955 editor
1956 .change_list
1957 .push_to_change_list(pop_state, new_positions);
1958 }
1959 }
1960 _ => (),
1961 },
1962 ));
1963
1964 if let Some(dap_store) = this
1965 .project
1966 .as_ref()
1967 .map(|project| project.read(cx).dap_store())
1968 {
1969 let weak_editor = cx.weak_entity();
1970
1971 this._subscriptions
1972 .push(
1973 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1974 let session_entity = cx.entity();
1975 weak_editor
1976 .update(cx, |editor, cx| {
1977 editor._subscriptions.push(
1978 cx.subscribe(&session_entity, Self::on_debug_session_event),
1979 );
1980 })
1981 .ok();
1982 }),
1983 );
1984
1985 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1986 this._subscriptions
1987 .push(cx.subscribe(&session, Self::on_debug_session_event));
1988 }
1989 }
1990
1991 this.end_selection(window, cx);
1992 this.scroll_manager.show_scrollbars(window, cx);
1993 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1994
1995 if full_mode {
1996 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1997 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1998
1999 if this.git_blame_inline_enabled {
2000 this.start_git_blame_inline(false, window, cx);
2001 }
2002
2003 this.go_to_active_debug_line(window, cx);
2004
2005 if let Some(buffer) = buffer.read(cx).as_singleton() {
2006 if let Some(project) = this.project.as_ref() {
2007 let handle = project.update(cx, |project, cx| {
2008 project.register_buffer_with_language_servers(&buffer, cx)
2009 });
2010 this.registered_buffers
2011 .insert(buffer.read(cx).remote_id(), handle);
2012 }
2013 }
2014
2015 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2016 }
2017
2018 this.report_editor_event("Editor Opened", None, cx);
2019 this
2020 }
2021
2022 pub fn deploy_mouse_context_menu(
2023 &mut self,
2024 position: gpui::Point<Pixels>,
2025 context_menu: Entity<ContextMenu>,
2026 window: &mut Window,
2027 cx: &mut Context<Self>,
2028 ) {
2029 self.mouse_context_menu = Some(MouseContextMenu::new(
2030 self,
2031 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2032 context_menu,
2033 window,
2034 cx,
2035 ));
2036 }
2037
2038 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2039 self.mouse_context_menu
2040 .as_ref()
2041 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2042 }
2043
2044 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2045 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2046 }
2047
2048 fn key_context_internal(
2049 &self,
2050 has_active_edit_prediction: bool,
2051 window: &Window,
2052 cx: &App,
2053 ) -> KeyContext {
2054 let mut key_context = KeyContext::new_with_defaults();
2055 key_context.add("Editor");
2056 let mode = match self.mode {
2057 EditorMode::SingleLine { .. } => "single_line",
2058 EditorMode::AutoHeight { .. } => "auto_height",
2059 EditorMode::Minimap { .. } => "minimap",
2060 EditorMode::Full { .. } => "full",
2061 };
2062
2063 if EditorSettings::jupyter_enabled(cx) {
2064 key_context.add("jupyter");
2065 }
2066
2067 key_context.set("mode", mode);
2068 if self.pending_rename.is_some() {
2069 key_context.add("renaming");
2070 }
2071
2072 match self.context_menu.borrow().as_ref() {
2073 Some(CodeContextMenu::Completions(_)) => {
2074 key_context.add("menu");
2075 key_context.add("showing_completions");
2076 }
2077 Some(CodeContextMenu::CodeActions(_)) => {
2078 key_context.add("menu");
2079 key_context.add("showing_code_actions")
2080 }
2081 None => {}
2082 }
2083
2084 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2085 if !self.focus_handle(cx).contains_focused(window, cx)
2086 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2087 {
2088 for addon in self.addons.values() {
2089 addon.extend_key_context(&mut key_context, cx)
2090 }
2091 }
2092
2093 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2094 if let Some(extension) = singleton_buffer
2095 .read(cx)
2096 .file()
2097 .and_then(|file| file.path().extension()?.to_str())
2098 {
2099 key_context.set("extension", extension.to_string());
2100 }
2101 } else {
2102 key_context.add("multibuffer");
2103 }
2104
2105 if has_active_edit_prediction {
2106 if self.edit_prediction_in_conflict() {
2107 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2108 } else {
2109 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2110 key_context.add("copilot_suggestion");
2111 }
2112 }
2113
2114 if self.selection_mark_mode {
2115 key_context.add("selection_mode");
2116 }
2117
2118 key_context
2119 }
2120
2121 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2122 self.mouse_cursor_hidden = match origin {
2123 HideMouseCursorOrigin::TypingAction => {
2124 matches!(
2125 self.hide_mouse_mode,
2126 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2127 )
2128 }
2129 HideMouseCursorOrigin::MovementAction => {
2130 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2131 }
2132 };
2133 }
2134
2135 pub fn edit_prediction_in_conflict(&self) -> bool {
2136 if !self.show_edit_predictions_in_menu() {
2137 return false;
2138 }
2139
2140 let showing_completions = self
2141 .context_menu
2142 .borrow()
2143 .as_ref()
2144 .map_or(false, |context| {
2145 matches!(context, CodeContextMenu::Completions(_))
2146 });
2147
2148 showing_completions
2149 || self.edit_prediction_requires_modifier()
2150 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2151 // bindings to insert tab characters.
2152 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2153 }
2154
2155 pub fn accept_edit_prediction_keybind(
2156 &self,
2157 window: &Window,
2158 cx: &App,
2159 ) -> AcceptEditPredictionBinding {
2160 let key_context = self.key_context_internal(true, window, cx);
2161 let in_conflict = self.edit_prediction_in_conflict();
2162
2163 AcceptEditPredictionBinding(
2164 window
2165 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2166 .into_iter()
2167 .filter(|binding| {
2168 !in_conflict
2169 || binding
2170 .keystrokes()
2171 .first()
2172 .map_or(false, |keystroke| keystroke.modifiers.modified())
2173 })
2174 .rev()
2175 .min_by_key(|binding| {
2176 binding
2177 .keystrokes()
2178 .first()
2179 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2180 }),
2181 )
2182 }
2183
2184 pub fn new_file(
2185 workspace: &mut Workspace,
2186 _: &workspace::NewFile,
2187 window: &mut Window,
2188 cx: &mut Context<Workspace>,
2189 ) {
2190 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2191 "Failed to create buffer",
2192 window,
2193 cx,
2194 |e, _, _| match e.error_code() {
2195 ErrorCode::RemoteUpgradeRequired => Some(format!(
2196 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2197 e.error_tag("required").unwrap_or("the latest version")
2198 )),
2199 _ => None,
2200 },
2201 );
2202 }
2203
2204 pub fn new_in_workspace(
2205 workspace: &mut Workspace,
2206 window: &mut Window,
2207 cx: &mut Context<Workspace>,
2208 ) -> Task<Result<Entity<Editor>>> {
2209 let project = workspace.project().clone();
2210 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2211
2212 cx.spawn_in(window, async move |workspace, cx| {
2213 let buffer = create.await?;
2214 workspace.update_in(cx, |workspace, window, cx| {
2215 let editor =
2216 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2217 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2218 editor
2219 })
2220 })
2221 }
2222
2223 fn new_file_vertical(
2224 workspace: &mut Workspace,
2225 _: &workspace::NewFileSplitVertical,
2226 window: &mut Window,
2227 cx: &mut Context<Workspace>,
2228 ) {
2229 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2230 }
2231
2232 fn new_file_horizontal(
2233 workspace: &mut Workspace,
2234 _: &workspace::NewFileSplitHorizontal,
2235 window: &mut Window,
2236 cx: &mut Context<Workspace>,
2237 ) {
2238 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2239 }
2240
2241 fn new_file_in_direction(
2242 workspace: &mut Workspace,
2243 direction: SplitDirection,
2244 window: &mut Window,
2245 cx: &mut Context<Workspace>,
2246 ) {
2247 let project = workspace.project().clone();
2248 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2249
2250 cx.spawn_in(window, async move |workspace, cx| {
2251 let buffer = create.await?;
2252 workspace.update_in(cx, move |workspace, window, cx| {
2253 workspace.split_item(
2254 direction,
2255 Box::new(
2256 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2257 ),
2258 window,
2259 cx,
2260 )
2261 })?;
2262 anyhow::Ok(())
2263 })
2264 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2265 match e.error_code() {
2266 ErrorCode::RemoteUpgradeRequired => Some(format!(
2267 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2268 e.error_tag("required").unwrap_or("the latest version")
2269 )),
2270 _ => None,
2271 }
2272 });
2273 }
2274
2275 pub fn leader_id(&self) -> Option<CollaboratorId> {
2276 self.leader_id
2277 }
2278
2279 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2280 &self.buffer
2281 }
2282
2283 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2284 self.workspace.as_ref()?.0.upgrade()
2285 }
2286
2287 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2288 self.buffer().read(cx).title(cx)
2289 }
2290
2291 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2292 let git_blame_gutter_max_author_length = self
2293 .render_git_blame_gutter(cx)
2294 .then(|| {
2295 if let Some(blame) = self.blame.as_ref() {
2296 let max_author_length =
2297 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2298 Some(max_author_length)
2299 } else {
2300 None
2301 }
2302 })
2303 .flatten();
2304
2305 EditorSnapshot {
2306 mode: self.mode.clone(),
2307 show_gutter: self.show_gutter,
2308 show_line_numbers: self.show_line_numbers,
2309 show_git_diff_gutter: self.show_git_diff_gutter,
2310 show_runnables: self.show_runnables,
2311 show_breakpoints: self.show_breakpoints,
2312 git_blame_gutter_max_author_length,
2313 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2314 scroll_anchor: self.scroll_manager.anchor(),
2315 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2316 placeholder_text: self.placeholder_text.clone(),
2317 is_focused: self.focus_handle.is_focused(window),
2318 current_line_highlight: self
2319 .current_line_highlight
2320 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2321 gutter_hovered: self.gutter_hovered,
2322 }
2323 }
2324
2325 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2326 self.buffer.read(cx).language_at(point, cx)
2327 }
2328
2329 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2330 self.buffer.read(cx).read(cx).file_at(point).cloned()
2331 }
2332
2333 pub fn active_excerpt(
2334 &self,
2335 cx: &App,
2336 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2337 self.buffer
2338 .read(cx)
2339 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2340 }
2341
2342 pub fn mode(&self) -> &EditorMode {
2343 &self.mode
2344 }
2345
2346 pub fn set_mode(&mut self, mode: EditorMode) {
2347 self.mode = mode;
2348 }
2349
2350 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2351 self.collaboration_hub.as_deref()
2352 }
2353
2354 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2355 self.collaboration_hub = Some(hub);
2356 }
2357
2358 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2359 self.in_project_search = in_project_search;
2360 }
2361
2362 pub fn set_custom_context_menu(
2363 &mut self,
2364 f: impl 'static
2365 + Fn(
2366 &mut Self,
2367 DisplayPoint,
2368 &mut Window,
2369 &mut Context<Self>,
2370 ) -> Option<Entity<ui::ContextMenu>>,
2371 ) {
2372 self.custom_context_menu = Some(Box::new(f))
2373 }
2374
2375 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2376 self.completion_provider = provider;
2377 }
2378
2379 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2380 self.semantics_provider.clone()
2381 }
2382
2383 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2384 self.semantics_provider = provider;
2385 }
2386
2387 pub fn set_edit_prediction_provider<T>(
2388 &mut self,
2389 provider: Option<Entity<T>>,
2390 window: &mut Window,
2391 cx: &mut Context<Self>,
2392 ) where
2393 T: EditPredictionProvider,
2394 {
2395 self.edit_prediction_provider =
2396 provider.map(|provider| RegisteredInlineCompletionProvider {
2397 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2398 if this.focus_handle.is_focused(window) {
2399 this.update_visible_inline_completion(window, cx);
2400 }
2401 }),
2402 provider: Arc::new(provider),
2403 });
2404 self.update_edit_prediction_settings(cx);
2405 self.refresh_inline_completion(false, false, window, cx);
2406 }
2407
2408 pub fn placeholder_text(&self) -> Option<&str> {
2409 self.placeholder_text.as_deref()
2410 }
2411
2412 pub fn set_placeholder_text(
2413 &mut self,
2414 placeholder_text: impl Into<Arc<str>>,
2415 cx: &mut Context<Self>,
2416 ) {
2417 let placeholder_text = Some(placeholder_text.into());
2418 if self.placeholder_text != placeholder_text {
2419 self.placeholder_text = placeholder_text;
2420 cx.notify();
2421 }
2422 }
2423
2424 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2425 self.cursor_shape = cursor_shape;
2426
2427 // Disrupt blink for immediate user feedback that the cursor shape has changed
2428 self.blink_manager.update(cx, BlinkManager::show_cursor);
2429
2430 cx.notify();
2431 }
2432
2433 pub fn set_current_line_highlight(
2434 &mut self,
2435 current_line_highlight: Option<CurrentLineHighlight>,
2436 ) {
2437 self.current_line_highlight = current_line_highlight;
2438 }
2439
2440 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2441 self.collapse_matches = collapse_matches;
2442 }
2443
2444 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2445 let buffers = self.buffer.read(cx).all_buffers();
2446 let Some(project) = self.project.as_ref() else {
2447 return;
2448 };
2449 project.update(cx, |project, cx| {
2450 for buffer in buffers {
2451 self.registered_buffers
2452 .entry(buffer.read(cx).remote_id())
2453 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2454 }
2455 })
2456 }
2457
2458 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2459 if self.collapse_matches {
2460 return range.start..range.start;
2461 }
2462 range.clone()
2463 }
2464
2465 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2466 if self.display_map.read(cx).clip_at_line_ends != clip {
2467 self.display_map
2468 .update(cx, |map, _| map.clip_at_line_ends = clip);
2469 }
2470 }
2471
2472 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2473 self.input_enabled = input_enabled;
2474 }
2475
2476 pub fn set_inline_completions_hidden_for_vim_mode(
2477 &mut self,
2478 hidden: bool,
2479 window: &mut Window,
2480 cx: &mut Context<Self>,
2481 ) {
2482 if hidden != self.inline_completions_hidden_for_vim_mode {
2483 self.inline_completions_hidden_for_vim_mode = hidden;
2484 if hidden {
2485 self.update_visible_inline_completion(window, cx);
2486 } else {
2487 self.refresh_inline_completion(true, false, window, cx);
2488 }
2489 }
2490 }
2491
2492 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2493 self.menu_inline_completions_policy = value;
2494 }
2495
2496 pub fn set_autoindent(&mut self, autoindent: bool) {
2497 if autoindent {
2498 self.autoindent_mode = Some(AutoindentMode::EachLine);
2499 } else {
2500 self.autoindent_mode = None;
2501 }
2502 }
2503
2504 pub fn read_only(&self, cx: &App) -> bool {
2505 self.read_only || self.buffer.read(cx).read_only()
2506 }
2507
2508 pub fn set_read_only(&mut self, read_only: bool) {
2509 self.read_only = read_only;
2510 }
2511
2512 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2513 self.use_autoclose = autoclose;
2514 }
2515
2516 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2517 self.use_auto_surround = auto_surround;
2518 }
2519
2520 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2521 self.auto_replace_emoji_shortcode = auto_replace;
2522 }
2523
2524 pub fn toggle_edit_predictions(
2525 &mut self,
2526 _: &ToggleEditPrediction,
2527 window: &mut Window,
2528 cx: &mut Context<Self>,
2529 ) {
2530 if self.show_inline_completions_override.is_some() {
2531 self.set_show_edit_predictions(None, window, cx);
2532 } else {
2533 let show_edit_predictions = !self.edit_predictions_enabled();
2534 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2535 }
2536 }
2537
2538 pub fn set_show_edit_predictions(
2539 &mut self,
2540 show_edit_predictions: Option<bool>,
2541 window: &mut Window,
2542 cx: &mut Context<Self>,
2543 ) {
2544 self.show_inline_completions_override = show_edit_predictions;
2545 self.update_edit_prediction_settings(cx);
2546
2547 if let Some(false) = show_edit_predictions {
2548 self.discard_inline_completion(false, cx);
2549 } else {
2550 self.refresh_inline_completion(false, true, window, cx);
2551 }
2552 }
2553
2554 fn inline_completions_disabled_in_scope(
2555 &self,
2556 buffer: &Entity<Buffer>,
2557 buffer_position: language::Anchor,
2558 cx: &App,
2559 ) -> bool {
2560 let snapshot = buffer.read(cx).snapshot();
2561 let settings = snapshot.settings_at(buffer_position, cx);
2562
2563 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2564 return false;
2565 };
2566
2567 scope.override_name().map_or(false, |scope_name| {
2568 settings
2569 .edit_predictions_disabled_in
2570 .iter()
2571 .any(|s| s == scope_name)
2572 })
2573 }
2574
2575 pub fn set_use_modal_editing(&mut self, to: bool) {
2576 self.use_modal_editing = to;
2577 }
2578
2579 pub fn use_modal_editing(&self) -> bool {
2580 self.use_modal_editing
2581 }
2582
2583 fn selections_did_change(
2584 &mut self,
2585 local: bool,
2586 old_cursor_position: &Anchor,
2587 show_completions: bool,
2588 window: &mut Window,
2589 cx: &mut Context<Self>,
2590 ) {
2591 window.invalidate_character_coordinates();
2592
2593 // Copy selections to primary selection buffer
2594 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2595 if local {
2596 let selections = self.selections.all::<usize>(cx);
2597 let buffer_handle = self.buffer.read(cx).read(cx);
2598
2599 let mut text = String::new();
2600 for (index, selection) in selections.iter().enumerate() {
2601 let text_for_selection = buffer_handle
2602 .text_for_range(selection.start..selection.end)
2603 .collect::<String>();
2604
2605 text.push_str(&text_for_selection);
2606 if index != selections.len() - 1 {
2607 text.push('\n');
2608 }
2609 }
2610
2611 if !text.is_empty() {
2612 cx.write_to_primary(ClipboardItem::new_string(text));
2613 }
2614 }
2615
2616 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2617 self.buffer.update(cx, |buffer, cx| {
2618 buffer.set_active_selections(
2619 &self.selections.disjoint_anchors(),
2620 self.selections.line_mode,
2621 self.cursor_shape,
2622 cx,
2623 )
2624 });
2625 }
2626 let display_map = self
2627 .display_map
2628 .update(cx, |display_map, cx| display_map.snapshot(cx));
2629 let buffer = &display_map.buffer_snapshot;
2630 self.add_selections_state = None;
2631 self.select_next_state = None;
2632 self.select_prev_state = None;
2633 self.select_syntax_node_history.try_clear();
2634 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2635 self.snippet_stack
2636 .invalidate(&self.selections.disjoint_anchors(), buffer);
2637 self.take_rename(false, window, cx);
2638
2639 let new_cursor_position = self.selections.newest_anchor().head();
2640
2641 self.push_to_nav_history(
2642 *old_cursor_position,
2643 Some(new_cursor_position.to_point(buffer)),
2644 false,
2645 cx,
2646 );
2647
2648 if local {
2649 let new_cursor_position = self.selections.newest_anchor().head();
2650 let mut context_menu = self.context_menu.borrow_mut();
2651 let completion_menu = match context_menu.as_ref() {
2652 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2653 _ => {
2654 *context_menu = None;
2655 None
2656 }
2657 };
2658 if let Some(buffer_id) = new_cursor_position.buffer_id {
2659 if !self.registered_buffers.contains_key(&buffer_id) {
2660 if let Some(project) = self.project.as_ref() {
2661 project.update(cx, |project, cx| {
2662 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2663 return;
2664 };
2665 self.registered_buffers.insert(
2666 buffer_id,
2667 project.register_buffer_with_language_servers(&buffer, cx),
2668 );
2669 })
2670 }
2671 }
2672 }
2673
2674 if let Some(completion_menu) = completion_menu {
2675 let cursor_position = new_cursor_position.to_offset(buffer);
2676 let (word_range, kind) =
2677 buffer.surrounding_word(completion_menu.initial_position, true);
2678 if kind == Some(CharKind::Word)
2679 && word_range.to_inclusive().contains(&cursor_position)
2680 {
2681 let mut completion_menu = completion_menu.clone();
2682 drop(context_menu);
2683
2684 let query = Self::completion_query(buffer, cursor_position);
2685 cx.spawn(async move |this, cx| {
2686 completion_menu
2687 .filter(query.as_deref(), cx.background_executor().clone())
2688 .await;
2689
2690 this.update(cx, |this, cx| {
2691 let mut context_menu = this.context_menu.borrow_mut();
2692 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2693 else {
2694 return;
2695 };
2696
2697 if menu.id > completion_menu.id {
2698 return;
2699 }
2700
2701 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2702 drop(context_menu);
2703 cx.notify();
2704 })
2705 })
2706 .detach();
2707
2708 if show_completions {
2709 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2710 }
2711 } else {
2712 drop(context_menu);
2713 self.hide_context_menu(window, cx);
2714 }
2715 } else {
2716 drop(context_menu);
2717 }
2718
2719 hide_hover(self, cx);
2720
2721 if old_cursor_position.to_display_point(&display_map).row()
2722 != new_cursor_position.to_display_point(&display_map).row()
2723 {
2724 self.available_code_actions.take();
2725 }
2726 self.refresh_code_actions(window, cx);
2727 self.refresh_document_highlights(cx);
2728 self.refresh_selected_text_highlights(false, window, cx);
2729 refresh_matching_bracket_highlights(self, window, cx);
2730 self.update_visible_inline_completion(window, cx);
2731 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2732 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2733 self.inline_blame_popover.take();
2734 if self.git_blame_inline_enabled {
2735 self.start_inline_blame_timer(window, cx);
2736 }
2737 }
2738
2739 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2740 cx.emit(EditorEvent::SelectionsChanged { local });
2741
2742 let selections = &self.selections.disjoint;
2743 if selections.len() == 1 {
2744 cx.emit(SearchEvent::ActiveMatchChanged)
2745 }
2746 if local {
2747 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2748 let inmemory_selections = selections
2749 .iter()
2750 .map(|s| {
2751 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2752 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2753 })
2754 .collect();
2755 self.update_restoration_data(cx, |data| {
2756 data.selections = inmemory_selections;
2757 });
2758
2759 if WorkspaceSettings::get(None, cx).restore_on_startup
2760 != RestoreOnStartupBehavior::None
2761 {
2762 if let Some(workspace_id) =
2763 self.workspace.as_ref().and_then(|workspace| workspace.1)
2764 {
2765 let snapshot = self.buffer().read(cx).snapshot(cx);
2766 let selections = selections.clone();
2767 let background_executor = cx.background_executor().clone();
2768 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2769 self.serialize_selections = cx.background_spawn(async move {
2770 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2771 let db_selections = selections
2772 .iter()
2773 .map(|selection| {
2774 (
2775 selection.start.to_offset(&snapshot),
2776 selection.end.to_offset(&snapshot),
2777 )
2778 })
2779 .collect();
2780
2781 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2782 .await
2783 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2784 .log_err();
2785 });
2786 }
2787 }
2788 }
2789 }
2790
2791 cx.notify();
2792 }
2793
2794 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2795 use text::ToOffset as _;
2796 use text::ToPoint as _;
2797
2798 if self.mode.is_minimap()
2799 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2800 {
2801 return;
2802 }
2803
2804 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2805 return;
2806 };
2807
2808 let snapshot = singleton.read(cx).snapshot();
2809 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2810 let display_snapshot = display_map.snapshot(cx);
2811
2812 display_snapshot
2813 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2814 .map(|fold| {
2815 fold.range.start.text_anchor.to_point(&snapshot)
2816 ..fold.range.end.text_anchor.to_point(&snapshot)
2817 })
2818 .collect()
2819 });
2820 self.update_restoration_data(cx, |data| {
2821 data.folds = inmemory_folds;
2822 });
2823
2824 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2825 return;
2826 };
2827 let background_executor = cx.background_executor().clone();
2828 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2829 let db_folds = self.display_map.update(cx, |display_map, cx| {
2830 display_map
2831 .snapshot(cx)
2832 .folds_in_range(0..snapshot.len())
2833 .map(|fold| {
2834 (
2835 fold.range.start.text_anchor.to_offset(&snapshot),
2836 fold.range.end.text_anchor.to_offset(&snapshot),
2837 )
2838 })
2839 .collect()
2840 });
2841 self.serialize_folds = cx.background_spawn(async move {
2842 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2843 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2844 .await
2845 .with_context(|| {
2846 format!(
2847 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2848 )
2849 })
2850 .log_err();
2851 });
2852 }
2853
2854 pub fn sync_selections(
2855 &mut self,
2856 other: Entity<Editor>,
2857 cx: &mut Context<Self>,
2858 ) -> gpui::Subscription {
2859 let other_selections = other.read(cx).selections.disjoint.to_vec();
2860 self.selections.change_with(cx, |selections| {
2861 selections.select_anchors(other_selections);
2862 });
2863
2864 let other_subscription =
2865 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2866 EditorEvent::SelectionsChanged { local: true } => {
2867 let other_selections = other.read(cx).selections.disjoint.to_vec();
2868 if other_selections.is_empty() {
2869 return;
2870 }
2871 this.selections.change_with(cx, |selections| {
2872 selections.select_anchors(other_selections);
2873 });
2874 }
2875 _ => {}
2876 });
2877
2878 let this_subscription =
2879 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2880 EditorEvent::SelectionsChanged { local: true } => {
2881 let these_selections = this.selections.disjoint.to_vec();
2882 if these_selections.is_empty() {
2883 return;
2884 }
2885 other.update(cx, |other_editor, cx| {
2886 other_editor.selections.change_with(cx, |selections| {
2887 selections.select_anchors(these_selections);
2888 })
2889 });
2890 }
2891 _ => {}
2892 });
2893
2894 Subscription::join(other_subscription, this_subscription)
2895 }
2896
2897 pub fn change_selections<R>(
2898 &mut self,
2899 autoscroll: Option<Autoscroll>,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2903 ) -> R {
2904 self.change_selections_inner(autoscroll, true, window, cx, change)
2905 }
2906
2907 fn change_selections_inner<R>(
2908 &mut self,
2909 autoscroll: Option<Autoscroll>,
2910 request_completions: bool,
2911 window: &mut Window,
2912 cx: &mut Context<Self>,
2913 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2914 ) -> R {
2915 let old_cursor_position = self.selections.newest_anchor().head();
2916 self.push_to_selection_history();
2917
2918 let (changed, result) = self.selections.change_with(cx, change);
2919
2920 if changed {
2921 if let Some(autoscroll) = autoscroll {
2922 self.request_autoscroll(autoscroll, cx);
2923 }
2924 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2925
2926 if self.should_open_signature_help_automatically(
2927 &old_cursor_position,
2928 self.signature_help_state.backspace_pressed(),
2929 cx,
2930 ) {
2931 self.show_signature_help(&ShowSignatureHelp, window, cx);
2932 }
2933 self.signature_help_state.set_backspace_pressed(false);
2934 }
2935
2936 result
2937 }
2938
2939 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2940 where
2941 I: IntoIterator<Item = (Range<S>, T)>,
2942 S: ToOffset,
2943 T: Into<Arc<str>>,
2944 {
2945 if self.read_only(cx) {
2946 return;
2947 }
2948
2949 self.buffer
2950 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2951 }
2952
2953 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2954 where
2955 I: IntoIterator<Item = (Range<S>, T)>,
2956 S: ToOffset,
2957 T: Into<Arc<str>>,
2958 {
2959 if self.read_only(cx) {
2960 return;
2961 }
2962
2963 self.buffer.update(cx, |buffer, cx| {
2964 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2965 });
2966 }
2967
2968 pub fn edit_with_block_indent<I, S, T>(
2969 &mut self,
2970 edits: I,
2971 original_indent_columns: Vec<Option<u32>>,
2972 cx: &mut Context<Self>,
2973 ) where
2974 I: IntoIterator<Item = (Range<S>, T)>,
2975 S: ToOffset,
2976 T: Into<Arc<str>>,
2977 {
2978 if self.read_only(cx) {
2979 return;
2980 }
2981
2982 self.buffer.update(cx, |buffer, cx| {
2983 buffer.edit(
2984 edits,
2985 Some(AutoindentMode::Block {
2986 original_indent_columns,
2987 }),
2988 cx,
2989 )
2990 });
2991 }
2992
2993 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2994 self.hide_context_menu(window, cx);
2995
2996 match phase {
2997 SelectPhase::Begin {
2998 position,
2999 add,
3000 click_count,
3001 } => self.begin_selection(position, add, click_count, window, cx),
3002 SelectPhase::BeginColumnar {
3003 position,
3004 goal_column,
3005 reset,
3006 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3007 SelectPhase::Extend {
3008 position,
3009 click_count,
3010 } => self.extend_selection(position, click_count, window, cx),
3011 SelectPhase::Update {
3012 position,
3013 goal_column,
3014 scroll_delta,
3015 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3016 SelectPhase::End => self.end_selection(window, cx),
3017 }
3018 }
3019
3020 fn extend_selection(
3021 &mut self,
3022 position: DisplayPoint,
3023 click_count: usize,
3024 window: &mut Window,
3025 cx: &mut Context<Self>,
3026 ) {
3027 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3028 let tail = self.selections.newest::<usize>(cx).tail();
3029 self.begin_selection(position, false, click_count, window, cx);
3030
3031 let position = position.to_offset(&display_map, Bias::Left);
3032 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3033
3034 let mut pending_selection = self
3035 .selections
3036 .pending_anchor()
3037 .expect("extend_selection not called with pending selection");
3038 if position >= tail {
3039 pending_selection.start = tail_anchor;
3040 } else {
3041 pending_selection.end = tail_anchor;
3042 pending_selection.reversed = true;
3043 }
3044
3045 let mut pending_mode = self.selections.pending_mode().unwrap();
3046 match &mut pending_mode {
3047 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3048 _ => {}
3049 }
3050
3051 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3052
3053 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3054 s.set_pending(pending_selection, pending_mode)
3055 });
3056 }
3057
3058 fn begin_selection(
3059 &mut self,
3060 position: DisplayPoint,
3061 add: bool,
3062 click_count: usize,
3063 window: &mut Window,
3064 cx: &mut Context<Self>,
3065 ) {
3066 if !self.focus_handle.is_focused(window) {
3067 self.last_focused_descendant = None;
3068 window.focus(&self.focus_handle);
3069 }
3070
3071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3072 let buffer = &display_map.buffer_snapshot;
3073 let position = display_map.clip_point(position, Bias::Left);
3074
3075 let start;
3076 let end;
3077 let mode;
3078 let mut auto_scroll;
3079 match click_count {
3080 1 => {
3081 start = buffer.anchor_before(position.to_point(&display_map));
3082 end = start;
3083 mode = SelectMode::Character;
3084 auto_scroll = true;
3085 }
3086 2 => {
3087 let range = movement::surrounding_word(&display_map, position);
3088 start = buffer.anchor_before(range.start.to_point(&display_map));
3089 end = buffer.anchor_before(range.end.to_point(&display_map));
3090 mode = SelectMode::Word(start..end);
3091 auto_scroll = true;
3092 }
3093 3 => {
3094 let position = display_map
3095 .clip_point(position, Bias::Left)
3096 .to_point(&display_map);
3097 let line_start = display_map.prev_line_boundary(position).0;
3098 let next_line_start = buffer.clip_point(
3099 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3100 Bias::Left,
3101 );
3102 start = buffer.anchor_before(line_start);
3103 end = buffer.anchor_before(next_line_start);
3104 mode = SelectMode::Line(start..end);
3105 auto_scroll = true;
3106 }
3107 _ => {
3108 start = buffer.anchor_before(0);
3109 end = buffer.anchor_before(buffer.len());
3110 mode = SelectMode::All;
3111 auto_scroll = false;
3112 }
3113 }
3114 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3115
3116 let point_to_delete: Option<usize> = {
3117 let selected_points: Vec<Selection<Point>> =
3118 self.selections.disjoint_in_range(start..end, cx);
3119
3120 if !add || click_count > 1 {
3121 None
3122 } else if !selected_points.is_empty() {
3123 Some(selected_points[0].id)
3124 } else {
3125 let clicked_point_already_selected =
3126 self.selections.disjoint.iter().find(|selection| {
3127 selection.start.to_point(buffer) == start.to_point(buffer)
3128 || selection.end.to_point(buffer) == end.to_point(buffer)
3129 });
3130
3131 clicked_point_already_selected.map(|selection| selection.id)
3132 }
3133 };
3134
3135 let selections_count = self.selections.count();
3136
3137 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3138 if let Some(point_to_delete) = point_to_delete {
3139 s.delete(point_to_delete);
3140
3141 if selections_count == 1 {
3142 s.set_pending_anchor_range(start..end, mode);
3143 }
3144 } else {
3145 if !add {
3146 s.clear_disjoint();
3147 }
3148
3149 s.set_pending_anchor_range(start..end, mode);
3150 }
3151 });
3152 }
3153
3154 fn begin_columnar_selection(
3155 &mut self,
3156 position: DisplayPoint,
3157 goal_column: u32,
3158 reset: bool,
3159 window: &mut Window,
3160 cx: &mut Context<Self>,
3161 ) {
3162 if !self.focus_handle.is_focused(window) {
3163 self.last_focused_descendant = None;
3164 window.focus(&self.focus_handle);
3165 }
3166
3167 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3168
3169 if reset {
3170 let pointer_position = display_map
3171 .buffer_snapshot
3172 .anchor_before(position.to_point(&display_map));
3173
3174 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3175 s.clear_disjoint();
3176 s.set_pending_anchor_range(
3177 pointer_position..pointer_position,
3178 SelectMode::Character,
3179 );
3180 });
3181 }
3182
3183 let tail = self.selections.newest::<Point>(cx).tail();
3184 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3185
3186 if !reset {
3187 self.select_columns(
3188 tail.to_display_point(&display_map),
3189 position,
3190 goal_column,
3191 &display_map,
3192 window,
3193 cx,
3194 );
3195 }
3196 }
3197
3198 fn update_selection(
3199 &mut self,
3200 position: DisplayPoint,
3201 goal_column: u32,
3202 scroll_delta: gpui::Point<f32>,
3203 window: &mut Window,
3204 cx: &mut Context<Self>,
3205 ) {
3206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3207
3208 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3209 let tail = tail.to_display_point(&display_map);
3210 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3211 } else if let Some(mut pending) = self.selections.pending_anchor() {
3212 let buffer = self.buffer.read(cx).snapshot(cx);
3213 let head;
3214 let tail;
3215 let mode = self.selections.pending_mode().unwrap();
3216 match &mode {
3217 SelectMode::Character => {
3218 head = position.to_point(&display_map);
3219 tail = pending.tail().to_point(&buffer);
3220 }
3221 SelectMode::Word(original_range) => {
3222 let original_display_range = original_range.start.to_display_point(&display_map)
3223 ..original_range.end.to_display_point(&display_map);
3224 let original_buffer_range = original_display_range.start.to_point(&display_map)
3225 ..original_display_range.end.to_point(&display_map);
3226 if movement::is_inside_word(&display_map, position)
3227 || original_display_range.contains(&position)
3228 {
3229 let word_range = movement::surrounding_word(&display_map, position);
3230 if word_range.start < original_display_range.start {
3231 head = word_range.start.to_point(&display_map);
3232 } else {
3233 head = word_range.end.to_point(&display_map);
3234 }
3235 } else {
3236 head = position.to_point(&display_map);
3237 }
3238
3239 if head <= original_buffer_range.start {
3240 tail = original_buffer_range.end;
3241 } else {
3242 tail = original_buffer_range.start;
3243 }
3244 }
3245 SelectMode::Line(original_range) => {
3246 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3247
3248 let position = display_map
3249 .clip_point(position, Bias::Left)
3250 .to_point(&display_map);
3251 let line_start = display_map.prev_line_boundary(position).0;
3252 let next_line_start = buffer.clip_point(
3253 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3254 Bias::Left,
3255 );
3256
3257 if line_start < original_range.start {
3258 head = line_start
3259 } else {
3260 head = next_line_start
3261 }
3262
3263 if head <= original_range.start {
3264 tail = original_range.end;
3265 } else {
3266 tail = original_range.start;
3267 }
3268 }
3269 SelectMode::All => {
3270 return;
3271 }
3272 };
3273
3274 if head < tail {
3275 pending.start = buffer.anchor_before(head);
3276 pending.end = buffer.anchor_before(tail);
3277 pending.reversed = true;
3278 } else {
3279 pending.start = buffer.anchor_before(tail);
3280 pending.end = buffer.anchor_before(head);
3281 pending.reversed = false;
3282 }
3283
3284 self.change_selections(None, window, cx, |s| {
3285 s.set_pending(pending, mode);
3286 });
3287 } else {
3288 log::error!("update_selection dispatched with no pending selection");
3289 return;
3290 }
3291
3292 self.apply_scroll_delta(scroll_delta, window, cx);
3293 cx.notify();
3294 }
3295
3296 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3297 self.columnar_selection_tail.take();
3298 if self.selections.pending_anchor().is_some() {
3299 let selections = self.selections.all::<usize>(cx);
3300 self.change_selections(None, window, cx, |s| {
3301 s.select(selections);
3302 s.clear_pending();
3303 });
3304 }
3305 }
3306
3307 fn select_columns(
3308 &mut self,
3309 tail: DisplayPoint,
3310 head: DisplayPoint,
3311 goal_column: u32,
3312 display_map: &DisplaySnapshot,
3313 window: &mut Window,
3314 cx: &mut Context<Self>,
3315 ) {
3316 let start_row = cmp::min(tail.row(), head.row());
3317 let end_row = cmp::max(tail.row(), head.row());
3318 let start_column = cmp::min(tail.column(), goal_column);
3319 let end_column = cmp::max(tail.column(), goal_column);
3320 let reversed = start_column < tail.column();
3321
3322 let selection_ranges = (start_row.0..=end_row.0)
3323 .map(DisplayRow)
3324 .filter_map(|row| {
3325 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3326 let start = display_map
3327 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3328 .to_point(display_map);
3329 let end = display_map
3330 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3331 .to_point(display_map);
3332 if reversed {
3333 Some(end..start)
3334 } else {
3335 Some(start..end)
3336 }
3337 } else {
3338 None
3339 }
3340 })
3341 .collect::<Vec<_>>();
3342
3343 self.change_selections(None, window, cx, |s| {
3344 s.select_ranges(selection_ranges);
3345 });
3346 cx.notify();
3347 }
3348
3349 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3350 self.selections
3351 .all_adjusted(cx)
3352 .iter()
3353 .any(|selection| !selection.is_empty())
3354 }
3355
3356 pub fn has_pending_nonempty_selection(&self) -> bool {
3357 let pending_nonempty_selection = match self.selections.pending_anchor() {
3358 Some(Selection { start, end, .. }) => start != end,
3359 None => false,
3360 };
3361
3362 pending_nonempty_selection
3363 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3364 }
3365
3366 pub fn has_pending_selection(&self) -> bool {
3367 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3368 }
3369
3370 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3371 self.selection_mark_mode = false;
3372
3373 if self.clear_expanded_diff_hunks(cx) {
3374 cx.notify();
3375 return;
3376 }
3377 if self.dismiss_menus_and_popups(true, window, cx) {
3378 return;
3379 }
3380
3381 if self.mode.is_full()
3382 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3383 {
3384 return;
3385 }
3386
3387 cx.propagate();
3388 }
3389
3390 pub fn dismiss_menus_and_popups(
3391 &mut self,
3392 is_user_requested: bool,
3393 window: &mut Window,
3394 cx: &mut Context<Self>,
3395 ) -> bool {
3396 if self.take_rename(false, window, cx).is_some() {
3397 return true;
3398 }
3399
3400 if hide_hover(self, cx) {
3401 return true;
3402 }
3403
3404 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3405 return true;
3406 }
3407
3408 if self.hide_context_menu(window, cx).is_some() {
3409 return true;
3410 }
3411
3412 if self.mouse_context_menu.take().is_some() {
3413 return true;
3414 }
3415
3416 if is_user_requested && self.discard_inline_completion(true, cx) {
3417 return true;
3418 }
3419
3420 if self.snippet_stack.pop().is_some() {
3421 return true;
3422 }
3423
3424 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3425 self.dismiss_diagnostics(cx);
3426 return true;
3427 }
3428
3429 false
3430 }
3431
3432 fn linked_editing_ranges_for(
3433 &self,
3434 selection: Range<text::Anchor>,
3435 cx: &App,
3436 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3437 if self.linked_edit_ranges.is_empty() {
3438 return None;
3439 }
3440 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3441 selection.end.buffer_id.and_then(|end_buffer_id| {
3442 if selection.start.buffer_id != Some(end_buffer_id) {
3443 return None;
3444 }
3445 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3446 let snapshot = buffer.read(cx).snapshot();
3447 self.linked_edit_ranges
3448 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3449 .map(|ranges| (ranges, snapshot, buffer))
3450 })?;
3451 use text::ToOffset as TO;
3452 // find offset from the start of current range to current cursor position
3453 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3454
3455 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3456 let start_difference = start_offset - start_byte_offset;
3457 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3458 let end_difference = end_offset - start_byte_offset;
3459 // Current range has associated linked ranges.
3460 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3461 for range in linked_ranges.iter() {
3462 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3463 let end_offset = start_offset + end_difference;
3464 let start_offset = start_offset + start_difference;
3465 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3466 continue;
3467 }
3468 if self.selections.disjoint_anchor_ranges().any(|s| {
3469 if s.start.buffer_id != selection.start.buffer_id
3470 || s.end.buffer_id != selection.end.buffer_id
3471 {
3472 return false;
3473 }
3474 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3475 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3476 }) {
3477 continue;
3478 }
3479 let start = buffer_snapshot.anchor_after(start_offset);
3480 let end = buffer_snapshot.anchor_after(end_offset);
3481 linked_edits
3482 .entry(buffer.clone())
3483 .or_default()
3484 .push(start..end);
3485 }
3486 Some(linked_edits)
3487 }
3488
3489 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3490 let text: Arc<str> = text.into();
3491
3492 if self.read_only(cx) {
3493 return;
3494 }
3495
3496 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3497
3498 let selections = self.selections.all_adjusted(cx);
3499 let mut bracket_inserted = false;
3500 let mut edits = Vec::new();
3501 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3502 let mut new_selections = Vec::with_capacity(selections.len());
3503 let mut new_autoclose_regions = Vec::new();
3504 let snapshot = self.buffer.read(cx).read(cx);
3505 let mut clear_linked_edit_ranges = false;
3506
3507 for (selection, autoclose_region) in
3508 self.selections_with_autoclose_regions(selections, &snapshot)
3509 {
3510 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3511 // Determine if the inserted text matches the opening or closing
3512 // bracket of any of this language's bracket pairs.
3513 let mut bracket_pair = None;
3514 let mut is_bracket_pair_start = false;
3515 let mut is_bracket_pair_end = false;
3516 if !text.is_empty() {
3517 let mut bracket_pair_matching_end = None;
3518 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3519 // and they are removing the character that triggered IME popup.
3520 for (pair, enabled) in scope.brackets() {
3521 if !pair.close && !pair.surround {
3522 continue;
3523 }
3524
3525 if enabled && pair.start.ends_with(text.as_ref()) {
3526 let prefix_len = pair.start.len() - text.len();
3527 let preceding_text_matches_prefix = prefix_len == 0
3528 || (selection.start.column >= (prefix_len as u32)
3529 && snapshot.contains_str_at(
3530 Point::new(
3531 selection.start.row,
3532 selection.start.column - (prefix_len as u32),
3533 ),
3534 &pair.start[..prefix_len],
3535 ));
3536 if preceding_text_matches_prefix {
3537 bracket_pair = Some(pair.clone());
3538 is_bracket_pair_start = true;
3539 break;
3540 }
3541 }
3542 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3543 {
3544 // take first bracket pair matching end, but don't break in case a later bracket
3545 // pair matches start
3546 bracket_pair_matching_end = Some(pair.clone());
3547 }
3548 }
3549 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3550 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3551 is_bracket_pair_end = true;
3552 }
3553 }
3554
3555 if let Some(bracket_pair) = bracket_pair {
3556 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3557 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3558 let auto_surround =
3559 self.use_auto_surround && snapshot_settings.use_auto_surround;
3560 if selection.is_empty() {
3561 if is_bracket_pair_start {
3562 // If the inserted text is a suffix of an opening bracket and the
3563 // selection is preceded by the rest of the opening bracket, then
3564 // insert the closing bracket.
3565 let following_text_allows_autoclose = snapshot
3566 .chars_at(selection.start)
3567 .next()
3568 .map_or(true, |c| scope.should_autoclose_before(c));
3569
3570 let preceding_text_allows_autoclose = selection.start.column == 0
3571 || snapshot.reversed_chars_at(selection.start).next().map_or(
3572 true,
3573 |c| {
3574 bracket_pair.start != bracket_pair.end
3575 || !snapshot
3576 .char_classifier_at(selection.start)
3577 .is_word(c)
3578 },
3579 );
3580
3581 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3582 && bracket_pair.start.len() == 1
3583 {
3584 let target = bracket_pair.start.chars().next().unwrap();
3585 let current_line_count = snapshot
3586 .reversed_chars_at(selection.start)
3587 .take_while(|&c| c != '\n')
3588 .filter(|&c| c == target)
3589 .count();
3590 current_line_count % 2 == 1
3591 } else {
3592 false
3593 };
3594
3595 if autoclose
3596 && bracket_pair.close
3597 && following_text_allows_autoclose
3598 && preceding_text_allows_autoclose
3599 && !is_closing_quote
3600 {
3601 let anchor = snapshot.anchor_before(selection.end);
3602 new_selections.push((selection.map(|_| anchor), text.len()));
3603 new_autoclose_regions.push((
3604 anchor,
3605 text.len(),
3606 selection.id,
3607 bracket_pair.clone(),
3608 ));
3609 edits.push((
3610 selection.range(),
3611 format!("{}{}", text, bracket_pair.end).into(),
3612 ));
3613 bracket_inserted = true;
3614 continue;
3615 }
3616 }
3617
3618 if let Some(region) = autoclose_region {
3619 // If the selection is followed by an auto-inserted closing bracket,
3620 // then don't insert that closing bracket again; just move the selection
3621 // past the closing bracket.
3622 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3623 && text.as_ref() == region.pair.end.as_str();
3624 if should_skip {
3625 let anchor = snapshot.anchor_after(selection.end);
3626 new_selections
3627 .push((selection.map(|_| anchor), region.pair.end.len()));
3628 continue;
3629 }
3630 }
3631
3632 let always_treat_brackets_as_autoclosed = snapshot
3633 .language_settings_at(selection.start, cx)
3634 .always_treat_brackets_as_autoclosed;
3635 if always_treat_brackets_as_autoclosed
3636 && is_bracket_pair_end
3637 && snapshot.contains_str_at(selection.end, text.as_ref())
3638 {
3639 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3640 // and the inserted text is a closing bracket and the selection is followed
3641 // by the closing bracket then move the selection past the closing bracket.
3642 let anchor = snapshot.anchor_after(selection.end);
3643 new_selections.push((selection.map(|_| anchor), text.len()));
3644 continue;
3645 }
3646 }
3647 // If an opening bracket is 1 character long and is typed while
3648 // text is selected, then surround that text with the bracket pair.
3649 else if auto_surround
3650 && bracket_pair.surround
3651 && is_bracket_pair_start
3652 && bracket_pair.start.chars().count() == 1
3653 {
3654 edits.push((selection.start..selection.start, text.clone()));
3655 edits.push((
3656 selection.end..selection.end,
3657 bracket_pair.end.as_str().into(),
3658 ));
3659 bracket_inserted = true;
3660 new_selections.push((
3661 Selection {
3662 id: selection.id,
3663 start: snapshot.anchor_after(selection.start),
3664 end: snapshot.anchor_before(selection.end),
3665 reversed: selection.reversed,
3666 goal: selection.goal,
3667 },
3668 0,
3669 ));
3670 continue;
3671 }
3672 }
3673 }
3674
3675 if self.auto_replace_emoji_shortcode
3676 && selection.is_empty()
3677 && text.as_ref().ends_with(':')
3678 {
3679 if let Some(possible_emoji_short_code) =
3680 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3681 {
3682 if !possible_emoji_short_code.is_empty() {
3683 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3684 let emoji_shortcode_start = Point::new(
3685 selection.start.row,
3686 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3687 );
3688
3689 // Remove shortcode from buffer
3690 edits.push((
3691 emoji_shortcode_start..selection.start,
3692 "".to_string().into(),
3693 ));
3694 new_selections.push((
3695 Selection {
3696 id: selection.id,
3697 start: snapshot.anchor_after(emoji_shortcode_start),
3698 end: snapshot.anchor_before(selection.start),
3699 reversed: selection.reversed,
3700 goal: selection.goal,
3701 },
3702 0,
3703 ));
3704
3705 // Insert emoji
3706 let selection_start_anchor = snapshot.anchor_after(selection.start);
3707 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3708 edits.push((selection.start..selection.end, emoji.to_string().into()));
3709
3710 continue;
3711 }
3712 }
3713 }
3714 }
3715
3716 // If not handling any auto-close operation, then just replace the selected
3717 // text with the given input and move the selection to the end of the
3718 // newly inserted text.
3719 let anchor = snapshot.anchor_after(selection.end);
3720 if !self.linked_edit_ranges.is_empty() {
3721 let start_anchor = snapshot.anchor_before(selection.start);
3722
3723 let is_word_char = text.chars().next().map_or(true, |char| {
3724 let classifier = snapshot
3725 .char_classifier_at(start_anchor.to_offset(&snapshot))
3726 .ignore_punctuation(true);
3727 classifier.is_word(char)
3728 });
3729
3730 if is_word_char {
3731 if let Some(ranges) = self
3732 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3733 {
3734 for (buffer, edits) in ranges {
3735 linked_edits
3736 .entry(buffer.clone())
3737 .or_default()
3738 .extend(edits.into_iter().map(|range| (range, text.clone())));
3739 }
3740 }
3741 } else {
3742 clear_linked_edit_ranges = true;
3743 }
3744 }
3745
3746 new_selections.push((selection.map(|_| anchor), 0));
3747 edits.push((selection.start..selection.end, text.clone()));
3748 }
3749
3750 drop(snapshot);
3751
3752 self.transact(window, cx, |this, window, cx| {
3753 if clear_linked_edit_ranges {
3754 this.linked_edit_ranges.clear();
3755 }
3756 let initial_buffer_versions =
3757 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3758
3759 this.buffer.update(cx, |buffer, cx| {
3760 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3761 });
3762 for (buffer, edits) in linked_edits {
3763 buffer.update(cx, |buffer, cx| {
3764 let snapshot = buffer.snapshot();
3765 let edits = edits
3766 .into_iter()
3767 .map(|(range, text)| {
3768 use text::ToPoint as TP;
3769 let end_point = TP::to_point(&range.end, &snapshot);
3770 let start_point = TP::to_point(&range.start, &snapshot);
3771 (start_point..end_point, text)
3772 })
3773 .sorted_by_key(|(range, _)| range.start);
3774 buffer.edit(edits, None, cx);
3775 })
3776 }
3777 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3778 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3779 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3780 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3781 .zip(new_selection_deltas)
3782 .map(|(selection, delta)| Selection {
3783 id: selection.id,
3784 start: selection.start + delta,
3785 end: selection.end + delta,
3786 reversed: selection.reversed,
3787 goal: SelectionGoal::None,
3788 })
3789 .collect::<Vec<_>>();
3790
3791 let mut i = 0;
3792 for (position, delta, selection_id, pair) in new_autoclose_regions {
3793 let position = position.to_offset(&map.buffer_snapshot) + delta;
3794 let start = map.buffer_snapshot.anchor_before(position);
3795 let end = map.buffer_snapshot.anchor_after(position);
3796 while let Some(existing_state) = this.autoclose_regions.get(i) {
3797 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3798 Ordering::Less => i += 1,
3799 Ordering::Greater => break,
3800 Ordering::Equal => {
3801 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3802 Ordering::Less => i += 1,
3803 Ordering::Equal => break,
3804 Ordering::Greater => break,
3805 }
3806 }
3807 }
3808 }
3809 this.autoclose_regions.insert(
3810 i,
3811 AutocloseRegion {
3812 selection_id,
3813 range: start..end,
3814 pair,
3815 },
3816 );
3817 }
3818
3819 let had_active_inline_completion = this.has_active_inline_completion();
3820 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3821 s.select(new_selections)
3822 });
3823
3824 if !bracket_inserted {
3825 if let Some(on_type_format_task) =
3826 this.trigger_on_type_formatting(text.to_string(), window, cx)
3827 {
3828 on_type_format_task.detach_and_log_err(cx);
3829 }
3830 }
3831
3832 let editor_settings = EditorSettings::get_global(cx);
3833 if bracket_inserted
3834 && (editor_settings.auto_signature_help
3835 || editor_settings.show_signature_help_after_edits)
3836 {
3837 this.show_signature_help(&ShowSignatureHelp, window, cx);
3838 }
3839
3840 let trigger_in_words =
3841 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3842 if this.hard_wrap.is_some() {
3843 let latest: Range<Point> = this.selections.newest(cx).range();
3844 if latest.is_empty()
3845 && this
3846 .buffer()
3847 .read(cx)
3848 .snapshot(cx)
3849 .line_len(MultiBufferRow(latest.start.row))
3850 == latest.start.column
3851 {
3852 this.rewrap_impl(
3853 RewrapOptions {
3854 override_language_settings: true,
3855 preserve_existing_whitespace: true,
3856 },
3857 cx,
3858 )
3859 }
3860 }
3861 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3862 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3863 this.refresh_inline_completion(true, false, window, cx);
3864 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3865 });
3866 }
3867
3868 fn find_possible_emoji_shortcode_at_position(
3869 snapshot: &MultiBufferSnapshot,
3870 position: Point,
3871 ) -> Option<String> {
3872 let mut chars = Vec::new();
3873 let mut found_colon = false;
3874 for char in snapshot.reversed_chars_at(position).take(100) {
3875 // Found a possible emoji shortcode in the middle of the buffer
3876 if found_colon {
3877 if char.is_whitespace() {
3878 chars.reverse();
3879 return Some(chars.iter().collect());
3880 }
3881 // If the previous character is not a whitespace, we are in the middle of a word
3882 // and we only want to complete the shortcode if the word is made up of other emojis
3883 let mut containing_word = String::new();
3884 for ch in snapshot
3885 .reversed_chars_at(position)
3886 .skip(chars.len() + 1)
3887 .take(100)
3888 {
3889 if ch.is_whitespace() {
3890 break;
3891 }
3892 containing_word.push(ch);
3893 }
3894 let containing_word = containing_word.chars().rev().collect::<String>();
3895 if util::word_consists_of_emojis(containing_word.as_str()) {
3896 chars.reverse();
3897 return Some(chars.iter().collect());
3898 }
3899 }
3900
3901 if char.is_whitespace() || !char.is_ascii() {
3902 return None;
3903 }
3904 if char == ':' {
3905 found_colon = true;
3906 } else {
3907 chars.push(char);
3908 }
3909 }
3910 // Found a possible emoji shortcode at the beginning of the buffer
3911 chars.reverse();
3912 Some(chars.iter().collect())
3913 }
3914
3915 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3916 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3917 self.transact(window, cx, |this, window, cx| {
3918 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3919 let selections = this.selections.all::<usize>(cx);
3920 let multi_buffer = this.buffer.read(cx);
3921 let buffer = multi_buffer.snapshot(cx);
3922 selections
3923 .iter()
3924 .map(|selection| {
3925 let start_point = selection.start.to_point(&buffer);
3926 let mut existing_indent =
3927 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3928 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3929 let start = selection.start;
3930 let end = selection.end;
3931 let selection_is_empty = start == end;
3932 let language_scope = buffer.language_scope_at(start);
3933 let (
3934 comment_delimiter,
3935 doc_delimiter,
3936 insert_extra_newline,
3937 indent_on_newline,
3938 indent_on_extra_newline,
3939 ) = if let Some(language) = &language_scope {
3940 let mut insert_extra_newline =
3941 insert_extra_newline_brackets(&buffer, start..end, language)
3942 || insert_extra_newline_tree_sitter(&buffer, start..end);
3943
3944 // Comment extension on newline is allowed only for cursor selections
3945 let comment_delimiter = maybe!({
3946 if !selection_is_empty {
3947 return None;
3948 }
3949
3950 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3951 return None;
3952 }
3953
3954 let delimiters = language.line_comment_prefixes();
3955 let max_len_of_delimiter =
3956 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3957 let (snapshot, range) =
3958 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3959
3960 let num_of_whitespaces = snapshot
3961 .chars_for_range(range.clone())
3962 .take_while(|c| c.is_whitespace())
3963 .count();
3964 let comment_candidate = snapshot
3965 .chars_for_range(range)
3966 .skip(num_of_whitespaces)
3967 .take(max_len_of_delimiter)
3968 .collect::<String>();
3969 let (delimiter, trimmed_len) = delimiters
3970 .iter()
3971 .filter_map(|delimiter| {
3972 let prefix = delimiter.trim_end();
3973 if comment_candidate.starts_with(prefix) {
3974 Some((delimiter, prefix.len()))
3975 } else {
3976 None
3977 }
3978 })
3979 .max_by_key(|(_, len)| *len)?;
3980
3981 let cursor_is_placed_after_comment_marker =
3982 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3983 if cursor_is_placed_after_comment_marker {
3984 Some(delimiter.clone())
3985 } else {
3986 None
3987 }
3988 });
3989
3990 let mut indent_on_newline = IndentSize::spaces(0);
3991 let mut indent_on_extra_newline = IndentSize::spaces(0);
3992
3993 let doc_delimiter = maybe!({
3994 if !selection_is_empty {
3995 return None;
3996 }
3997
3998 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3999 return None;
4000 }
4001
4002 let DocumentationConfig {
4003 start: start_tag,
4004 end: end_tag,
4005 prefix: delimiter,
4006 tab_size: len,
4007 } = language.documentation()?;
4008
4009 let is_within_block_comment = buffer
4010 .language_scope_at(start_point)
4011 .is_some_and(|scope| scope.override_name() == Some("comment"));
4012 if !is_within_block_comment {
4013 return None;
4014 }
4015
4016 let (snapshot, range) =
4017 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4018
4019 let num_of_whitespaces = snapshot
4020 .chars_for_range(range.clone())
4021 .take_while(|c| c.is_whitespace())
4022 .count();
4023
4024 // 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.
4025 let column = start_point.column;
4026 let cursor_is_after_start_tag = {
4027 let start_tag_len = start_tag.len();
4028 let start_tag_line = snapshot
4029 .chars_for_range(range.clone())
4030 .skip(num_of_whitespaces)
4031 .take(start_tag_len)
4032 .collect::<String>();
4033 if start_tag_line.starts_with(start_tag.as_ref()) {
4034 num_of_whitespaces + start_tag_len <= column as usize
4035 } else {
4036 false
4037 }
4038 };
4039
4040 let cursor_is_after_delimiter = {
4041 let delimiter_trim = delimiter.trim_end();
4042 let delimiter_line = snapshot
4043 .chars_for_range(range.clone())
4044 .skip(num_of_whitespaces)
4045 .take(delimiter_trim.len())
4046 .collect::<String>();
4047 if delimiter_line.starts_with(delimiter_trim) {
4048 num_of_whitespaces + delimiter_trim.len() <= column as usize
4049 } else {
4050 false
4051 }
4052 };
4053
4054 let cursor_is_before_end_tag_if_exists = {
4055 let mut char_position = 0u32;
4056 let mut end_tag_offset = None;
4057
4058 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4059 if let Some(byte_pos) = chunk.find(&**end_tag) {
4060 let chars_before_match =
4061 chunk[..byte_pos].chars().count() as u32;
4062 end_tag_offset =
4063 Some(char_position + chars_before_match);
4064 break 'outer;
4065 }
4066 char_position += chunk.chars().count() as u32;
4067 }
4068
4069 if let Some(end_tag_offset) = end_tag_offset {
4070 let cursor_is_before_end_tag = column <= end_tag_offset;
4071 if cursor_is_after_start_tag {
4072 if cursor_is_before_end_tag {
4073 insert_extra_newline = true;
4074 }
4075 let cursor_is_at_start_of_end_tag =
4076 column == end_tag_offset;
4077 if cursor_is_at_start_of_end_tag {
4078 indent_on_extra_newline.len = (*len).into();
4079 }
4080 }
4081 cursor_is_before_end_tag
4082 } else {
4083 true
4084 }
4085 };
4086
4087 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4088 && cursor_is_before_end_tag_if_exists
4089 {
4090 if cursor_is_after_start_tag {
4091 indent_on_newline.len = (*len).into();
4092 }
4093 Some(delimiter.clone())
4094 } else {
4095 None
4096 }
4097 });
4098
4099 (
4100 comment_delimiter,
4101 doc_delimiter,
4102 insert_extra_newline,
4103 indent_on_newline,
4104 indent_on_extra_newline,
4105 )
4106 } else {
4107 (
4108 None,
4109 None,
4110 false,
4111 IndentSize::default(),
4112 IndentSize::default(),
4113 )
4114 };
4115
4116 let prevent_auto_indent = doc_delimiter.is_some();
4117 let delimiter = comment_delimiter.or(doc_delimiter);
4118
4119 let capacity_for_delimiter =
4120 delimiter.as_deref().map(str::len).unwrap_or_default();
4121 let mut new_text = String::with_capacity(
4122 1 + capacity_for_delimiter
4123 + existing_indent.len as usize
4124 + indent_on_newline.len as usize
4125 + indent_on_extra_newline.len as usize,
4126 );
4127 new_text.push('\n');
4128 new_text.extend(existing_indent.chars());
4129 new_text.extend(indent_on_newline.chars());
4130
4131 if let Some(delimiter) = &delimiter {
4132 new_text.push_str(delimiter);
4133 }
4134
4135 if insert_extra_newline {
4136 new_text.push('\n');
4137 new_text.extend(existing_indent.chars());
4138 new_text.extend(indent_on_extra_newline.chars());
4139 }
4140
4141 let anchor = buffer.anchor_after(end);
4142 let new_selection = selection.map(|_| anchor);
4143 (
4144 ((start..end, new_text), prevent_auto_indent),
4145 (insert_extra_newline, new_selection),
4146 )
4147 })
4148 .unzip()
4149 };
4150
4151 let mut auto_indent_edits = Vec::new();
4152 let mut edits = Vec::new();
4153 for (edit, prevent_auto_indent) in edits_with_flags {
4154 if prevent_auto_indent {
4155 edits.push(edit);
4156 } else {
4157 auto_indent_edits.push(edit);
4158 }
4159 }
4160 if !edits.is_empty() {
4161 this.edit(edits, cx);
4162 }
4163 if !auto_indent_edits.is_empty() {
4164 this.edit_with_autoindent(auto_indent_edits, cx);
4165 }
4166
4167 let buffer = this.buffer.read(cx).snapshot(cx);
4168 let new_selections = selection_info
4169 .into_iter()
4170 .map(|(extra_newline_inserted, new_selection)| {
4171 let mut cursor = new_selection.end.to_point(&buffer);
4172 if extra_newline_inserted {
4173 cursor.row -= 1;
4174 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4175 }
4176 new_selection.map(|_| cursor)
4177 })
4178 .collect();
4179
4180 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4181 s.select(new_selections)
4182 });
4183 this.refresh_inline_completion(true, false, window, cx);
4184 });
4185 }
4186
4187 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4188 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4189
4190 let buffer = self.buffer.read(cx);
4191 let snapshot = buffer.snapshot(cx);
4192
4193 let mut edits = Vec::new();
4194 let mut rows = Vec::new();
4195
4196 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4197 let cursor = selection.head();
4198 let row = cursor.row;
4199
4200 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4201
4202 let newline = "\n".to_string();
4203 edits.push((start_of_line..start_of_line, newline));
4204
4205 rows.push(row + rows_inserted as u32);
4206 }
4207
4208 self.transact(window, cx, |editor, window, cx| {
4209 editor.edit(edits, cx);
4210
4211 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4212 let mut index = 0;
4213 s.move_cursors_with(|map, _, _| {
4214 let row = rows[index];
4215 index += 1;
4216
4217 let point = Point::new(row, 0);
4218 let boundary = map.next_line_boundary(point).1;
4219 let clipped = map.clip_point(boundary, Bias::Left);
4220
4221 (clipped, SelectionGoal::None)
4222 });
4223 });
4224
4225 let mut indent_edits = Vec::new();
4226 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4227 for row in rows {
4228 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4229 for (row, indent) in indents {
4230 if indent.len == 0 {
4231 continue;
4232 }
4233
4234 let text = match indent.kind {
4235 IndentKind::Space => " ".repeat(indent.len as usize),
4236 IndentKind::Tab => "\t".repeat(indent.len as usize),
4237 };
4238 let point = Point::new(row.0, 0);
4239 indent_edits.push((point..point, text));
4240 }
4241 }
4242 editor.edit(indent_edits, cx);
4243 });
4244 }
4245
4246 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4247 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4248
4249 let buffer = self.buffer.read(cx);
4250 let snapshot = buffer.snapshot(cx);
4251
4252 let mut edits = Vec::new();
4253 let mut rows = Vec::new();
4254 let mut rows_inserted = 0;
4255
4256 for selection in self.selections.all_adjusted(cx) {
4257 let cursor = selection.head();
4258 let row = cursor.row;
4259
4260 let point = Point::new(row + 1, 0);
4261 let start_of_line = snapshot.clip_point(point, Bias::Left);
4262
4263 let newline = "\n".to_string();
4264 edits.push((start_of_line..start_of_line, newline));
4265
4266 rows_inserted += 1;
4267 rows.push(row + rows_inserted);
4268 }
4269
4270 self.transact(window, cx, |editor, window, cx| {
4271 editor.edit(edits, cx);
4272
4273 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4274 let mut index = 0;
4275 s.move_cursors_with(|map, _, _| {
4276 let row = rows[index];
4277 index += 1;
4278
4279 let point = Point::new(row, 0);
4280 let boundary = map.next_line_boundary(point).1;
4281 let clipped = map.clip_point(boundary, Bias::Left);
4282
4283 (clipped, SelectionGoal::None)
4284 });
4285 });
4286
4287 let mut indent_edits = Vec::new();
4288 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4289 for row in rows {
4290 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4291 for (row, indent) in indents {
4292 if indent.len == 0 {
4293 continue;
4294 }
4295
4296 let text = match indent.kind {
4297 IndentKind::Space => " ".repeat(indent.len as usize),
4298 IndentKind::Tab => "\t".repeat(indent.len as usize),
4299 };
4300 let point = Point::new(row.0, 0);
4301 indent_edits.push((point..point, text));
4302 }
4303 }
4304 editor.edit(indent_edits, cx);
4305 });
4306 }
4307
4308 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4309 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4310 original_indent_columns: Vec::new(),
4311 });
4312 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4313 }
4314
4315 fn insert_with_autoindent_mode(
4316 &mut self,
4317 text: &str,
4318 autoindent_mode: Option<AutoindentMode>,
4319 window: &mut Window,
4320 cx: &mut Context<Self>,
4321 ) {
4322 if self.read_only(cx) {
4323 return;
4324 }
4325
4326 let text: Arc<str> = text.into();
4327 self.transact(window, cx, |this, window, cx| {
4328 let old_selections = this.selections.all_adjusted(cx);
4329 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4330 let anchors = {
4331 let snapshot = buffer.read(cx);
4332 old_selections
4333 .iter()
4334 .map(|s| {
4335 let anchor = snapshot.anchor_after(s.head());
4336 s.map(|_| anchor)
4337 })
4338 .collect::<Vec<_>>()
4339 };
4340 buffer.edit(
4341 old_selections
4342 .iter()
4343 .map(|s| (s.start..s.end, text.clone())),
4344 autoindent_mode,
4345 cx,
4346 );
4347 anchors
4348 });
4349
4350 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4351 s.select_anchors(selection_anchors);
4352 });
4353
4354 cx.notify();
4355 });
4356 }
4357
4358 fn trigger_completion_on_input(
4359 &mut self,
4360 text: &str,
4361 trigger_in_words: bool,
4362 window: &mut Window,
4363 cx: &mut Context<Self>,
4364 ) {
4365 let ignore_completion_provider = self
4366 .context_menu
4367 .borrow()
4368 .as_ref()
4369 .map(|menu| match menu {
4370 CodeContextMenu::Completions(completions_menu) => {
4371 completions_menu.ignore_completion_provider
4372 }
4373 CodeContextMenu::CodeActions(_) => false,
4374 })
4375 .unwrap_or(false);
4376
4377 if ignore_completion_provider {
4378 self.show_word_completions(&ShowWordCompletions, window, cx);
4379 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4380 self.show_completions(
4381 &ShowCompletions {
4382 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4383 },
4384 window,
4385 cx,
4386 );
4387 } else {
4388 self.hide_context_menu(window, cx);
4389 }
4390 }
4391
4392 fn is_completion_trigger(
4393 &self,
4394 text: &str,
4395 trigger_in_words: bool,
4396 cx: &mut Context<Self>,
4397 ) -> bool {
4398 let position = self.selections.newest_anchor().head();
4399 let multibuffer = self.buffer.read(cx);
4400 let Some(buffer) = position
4401 .buffer_id
4402 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4403 else {
4404 return false;
4405 };
4406
4407 if let Some(completion_provider) = &self.completion_provider {
4408 completion_provider.is_completion_trigger(
4409 &buffer,
4410 position.text_anchor,
4411 text,
4412 trigger_in_words,
4413 cx,
4414 )
4415 } else {
4416 false
4417 }
4418 }
4419
4420 /// If any empty selections is touching the start of its innermost containing autoclose
4421 /// region, expand it to select the brackets.
4422 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4423 let selections = self.selections.all::<usize>(cx);
4424 let buffer = self.buffer.read(cx).read(cx);
4425 let new_selections = self
4426 .selections_with_autoclose_regions(selections, &buffer)
4427 .map(|(mut selection, region)| {
4428 if !selection.is_empty() {
4429 return selection;
4430 }
4431
4432 if let Some(region) = region {
4433 let mut range = region.range.to_offset(&buffer);
4434 if selection.start == range.start && range.start >= region.pair.start.len() {
4435 range.start -= region.pair.start.len();
4436 if buffer.contains_str_at(range.start, ®ion.pair.start)
4437 && buffer.contains_str_at(range.end, ®ion.pair.end)
4438 {
4439 range.end += region.pair.end.len();
4440 selection.start = range.start;
4441 selection.end = range.end;
4442
4443 return selection;
4444 }
4445 }
4446 }
4447
4448 let always_treat_brackets_as_autoclosed = buffer
4449 .language_settings_at(selection.start, cx)
4450 .always_treat_brackets_as_autoclosed;
4451
4452 if !always_treat_brackets_as_autoclosed {
4453 return selection;
4454 }
4455
4456 if let Some(scope) = buffer.language_scope_at(selection.start) {
4457 for (pair, enabled) in scope.brackets() {
4458 if !enabled || !pair.close {
4459 continue;
4460 }
4461
4462 if buffer.contains_str_at(selection.start, &pair.end) {
4463 let pair_start_len = pair.start.len();
4464 if buffer.contains_str_at(
4465 selection.start.saturating_sub(pair_start_len),
4466 &pair.start,
4467 ) {
4468 selection.start -= pair_start_len;
4469 selection.end += pair.end.len();
4470
4471 return selection;
4472 }
4473 }
4474 }
4475 }
4476
4477 selection
4478 })
4479 .collect();
4480
4481 drop(buffer);
4482 self.change_selections(None, window, cx, |selections| {
4483 selections.select(new_selections)
4484 });
4485 }
4486
4487 /// Iterate the given selections, and for each one, find the smallest surrounding
4488 /// autoclose region. This uses the ordering of the selections and the autoclose
4489 /// regions to avoid repeated comparisons.
4490 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4491 &'a self,
4492 selections: impl IntoIterator<Item = Selection<D>>,
4493 buffer: &'a MultiBufferSnapshot,
4494 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4495 let mut i = 0;
4496 let mut regions = self.autoclose_regions.as_slice();
4497 selections.into_iter().map(move |selection| {
4498 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4499
4500 let mut enclosing = None;
4501 while let Some(pair_state) = regions.get(i) {
4502 if pair_state.range.end.to_offset(buffer) < range.start {
4503 regions = ®ions[i + 1..];
4504 i = 0;
4505 } else if pair_state.range.start.to_offset(buffer) > range.end {
4506 break;
4507 } else {
4508 if pair_state.selection_id == selection.id {
4509 enclosing = Some(pair_state);
4510 }
4511 i += 1;
4512 }
4513 }
4514
4515 (selection, enclosing)
4516 })
4517 }
4518
4519 /// Remove any autoclose regions that no longer contain their selection.
4520 fn invalidate_autoclose_regions(
4521 &mut self,
4522 mut selections: &[Selection<Anchor>],
4523 buffer: &MultiBufferSnapshot,
4524 ) {
4525 self.autoclose_regions.retain(|state| {
4526 let mut i = 0;
4527 while let Some(selection) = selections.get(i) {
4528 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4529 selections = &selections[1..];
4530 continue;
4531 }
4532 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4533 break;
4534 }
4535 if selection.id == state.selection_id {
4536 return true;
4537 } else {
4538 i += 1;
4539 }
4540 }
4541 false
4542 });
4543 }
4544
4545 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4546 let offset = position.to_offset(buffer);
4547 let (word_range, kind) = buffer.surrounding_word(offset, true);
4548 if offset > word_range.start && kind == Some(CharKind::Word) {
4549 Some(
4550 buffer
4551 .text_for_range(word_range.start..offset)
4552 .collect::<String>(),
4553 )
4554 } else {
4555 None
4556 }
4557 }
4558
4559 pub fn toggle_inline_values(
4560 &mut self,
4561 _: &ToggleInlineValues,
4562 _: &mut Window,
4563 cx: &mut Context<Self>,
4564 ) {
4565 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4566
4567 self.refresh_inline_values(cx);
4568 }
4569
4570 pub fn toggle_inlay_hints(
4571 &mut self,
4572 _: &ToggleInlayHints,
4573 _: &mut Window,
4574 cx: &mut Context<Self>,
4575 ) {
4576 self.refresh_inlay_hints(
4577 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4578 cx,
4579 );
4580 }
4581
4582 pub fn inlay_hints_enabled(&self) -> bool {
4583 self.inlay_hint_cache.enabled
4584 }
4585
4586 pub fn inline_values_enabled(&self) -> bool {
4587 self.inline_value_cache.enabled
4588 }
4589
4590 #[cfg(any(test, feature = "test-support"))]
4591 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4592 self.display_map
4593 .read(cx)
4594 .current_inlays()
4595 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4596 .cloned()
4597 .collect()
4598 }
4599
4600 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4601 if self.semantics_provider.is_none() || !self.mode.is_full() {
4602 return;
4603 }
4604
4605 let reason_description = reason.description();
4606 let ignore_debounce = matches!(
4607 reason,
4608 InlayHintRefreshReason::SettingsChange(_)
4609 | InlayHintRefreshReason::Toggle(_)
4610 | InlayHintRefreshReason::ExcerptsRemoved(_)
4611 | InlayHintRefreshReason::ModifiersChanged(_)
4612 );
4613 let (invalidate_cache, required_languages) = match reason {
4614 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4615 match self.inlay_hint_cache.modifiers_override(enabled) {
4616 Some(enabled) => {
4617 if enabled {
4618 (InvalidationStrategy::RefreshRequested, None)
4619 } else {
4620 self.splice_inlays(
4621 &self
4622 .visible_inlay_hints(cx)
4623 .iter()
4624 .map(|inlay| inlay.id)
4625 .collect::<Vec<InlayId>>(),
4626 Vec::new(),
4627 cx,
4628 );
4629 return;
4630 }
4631 }
4632 None => return,
4633 }
4634 }
4635 InlayHintRefreshReason::Toggle(enabled) => {
4636 if self.inlay_hint_cache.toggle(enabled) {
4637 if enabled {
4638 (InvalidationStrategy::RefreshRequested, None)
4639 } else {
4640 self.splice_inlays(
4641 &self
4642 .visible_inlay_hints(cx)
4643 .iter()
4644 .map(|inlay| inlay.id)
4645 .collect::<Vec<InlayId>>(),
4646 Vec::new(),
4647 cx,
4648 );
4649 return;
4650 }
4651 } else {
4652 return;
4653 }
4654 }
4655 InlayHintRefreshReason::SettingsChange(new_settings) => {
4656 match self.inlay_hint_cache.update_settings(
4657 &self.buffer,
4658 new_settings,
4659 self.visible_inlay_hints(cx),
4660 cx,
4661 ) {
4662 ControlFlow::Break(Some(InlaySplice {
4663 to_remove,
4664 to_insert,
4665 })) => {
4666 self.splice_inlays(&to_remove, to_insert, cx);
4667 return;
4668 }
4669 ControlFlow::Break(None) => return,
4670 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4671 }
4672 }
4673 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4674 if let Some(InlaySplice {
4675 to_remove,
4676 to_insert,
4677 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4678 {
4679 self.splice_inlays(&to_remove, to_insert, cx);
4680 }
4681 self.display_map.update(cx, |display_map, _| {
4682 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4683 });
4684 return;
4685 }
4686 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4687 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4688 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4689 }
4690 InlayHintRefreshReason::RefreshRequested => {
4691 (InvalidationStrategy::RefreshRequested, None)
4692 }
4693 };
4694
4695 if let Some(InlaySplice {
4696 to_remove,
4697 to_insert,
4698 }) = self.inlay_hint_cache.spawn_hint_refresh(
4699 reason_description,
4700 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4701 invalidate_cache,
4702 ignore_debounce,
4703 cx,
4704 ) {
4705 self.splice_inlays(&to_remove, to_insert, cx);
4706 }
4707 }
4708
4709 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4710 self.display_map
4711 .read(cx)
4712 .current_inlays()
4713 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4714 .cloned()
4715 .collect()
4716 }
4717
4718 pub fn excerpts_for_inlay_hints_query(
4719 &self,
4720 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4721 cx: &mut Context<Editor>,
4722 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4723 let Some(project) = self.project.as_ref() else {
4724 return HashMap::default();
4725 };
4726 let project = project.read(cx);
4727 let multi_buffer = self.buffer().read(cx);
4728 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4729 let multi_buffer_visible_start = self
4730 .scroll_manager
4731 .anchor()
4732 .anchor
4733 .to_point(&multi_buffer_snapshot);
4734 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4735 multi_buffer_visible_start
4736 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4737 Bias::Left,
4738 );
4739 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4740 multi_buffer_snapshot
4741 .range_to_buffer_ranges(multi_buffer_visible_range)
4742 .into_iter()
4743 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4744 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4745 let buffer_file = project::File::from_dyn(buffer.file())?;
4746 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4747 let worktree_entry = buffer_worktree
4748 .read(cx)
4749 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4750 if worktree_entry.is_ignored {
4751 return None;
4752 }
4753
4754 let language = buffer.language()?;
4755 if let Some(restrict_to_languages) = restrict_to_languages {
4756 if !restrict_to_languages.contains(language) {
4757 return None;
4758 }
4759 }
4760 Some((
4761 excerpt_id,
4762 (
4763 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4764 buffer.version().clone(),
4765 excerpt_visible_range,
4766 ),
4767 ))
4768 })
4769 .collect()
4770 }
4771
4772 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4773 TextLayoutDetails {
4774 text_system: window.text_system().clone(),
4775 editor_style: self.style.clone().unwrap(),
4776 rem_size: window.rem_size(),
4777 scroll_anchor: self.scroll_manager.anchor(),
4778 visible_rows: self.visible_line_count(),
4779 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4780 }
4781 }
4782
4783 pub fn splice_inlays(
4784 &self,
4785 to_remove: &[InlayId],
4786 to_insert: Vec<Inlay>,
4787 cx: &mut Context<Self>,
4788 ) {
4789 self.display_map.update(cx, |display_map, cx| {
4790 display_map.splice_inlays(to_remove, to_insert, cx)
4791 });
4792 cx.notify();
4793 }
4794
4795 fn trigger_on_type_formatting(
4796 &self,
4797 input: String,
4798 window: &mut Window,
4799 cx: &mut Context<Self>,
4800 ) -> Option<Task<Result<()>>> {
4801 if input.len() != 1 {
4802 return None;
4803 }
4804
4805 let project = self.project.as_ref()?;
4806 let position = self.selections.newest_anchor().head();
4807 let (buffer, buffer_position) = self
4808 .buffer
4809 .read(cx)
4810 .text_anchor_for_position(position, cx)?;
4811
4812 let settings = language_settings::language_settings(
4813 buffer
4814 .read(cx)
4815 .language_at(buffer_position)
4816 .map(|l| l.name()),
4817 buffer.read(cx).file(),
4818 cx,
4819 );
4820 if !settings.use_on_type_format {
4821 return None;
4822 }
4823
4824 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4825 // hence we do LSP request & edit on host side only — add formats to host's history.
4826 let push_to_lsp_host_history = true;
4827 // If this is not the host, append its history with new edits.
4828 let push_to_client_history = project.read(cx).is_via_collab();
4829
4830 let on_type_formatting = project.update(cx, |project, cx| {
4831 project.on_type_format(
4832 buffer.clone(),
4833 buffer_position,
4834 input,
4835 push_to_lsp_host_history,
4836 cx,
4837 )
4838 });
4839 Some(cx.spawn_in(window, async move |editor, cx| {
4840 if let Some(transaction) = on_type_formatting.await? {
4841 if push_to_client_history {
4842 buffer
4843 .update(cx, |buffer, _| {
4844 buffer.push_transaction(transaction, Instant::now());
4845 buffer.finalize_last_transaction();
4846 })
4847 .ok();
4848 }
4849 editor.update(cx, |editor, cx| {
4850 editor.refresh_document_highlights(cx);
4851 })?;
4852 }
4853 Ok(())
4854 }))
4855 }
4856
4857 pub fn show_word_completions(
4858 &mut self,
4859 _: &ShowWordCompletions,
4860 window: &mut Window,
4861 cx: &mut Context<Self>,
4862 ) {
4863 self.open_completions_menu(true, None, window, cx);
4864 }
4865
4866 pub fn show_completions(
4867 &mut self,
4868 options: &ShowCompletions,
4869 window: &mut Window,
4870 cx: &mut Context<Self>,
4871 ) {
4872 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4873 }
4874
4875 fn open_completions_menu(
4876 &mut self,
4877 ignore_completion_provider: bool,
4878 trigger: Option<&str>,
4879 window: &mut Window,
4880 cx: &mut Context<Self>,
4881 ) {
4882 if self.pending_rename.is_some() {
4883 return;
4884 }
4885 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4886 return;
4887 }
4888
4889 let position = self.selections.newest_anchor().head();
4890 if position.diff_base_anchor.is_some() {
4891 return;
4892 }
4893 let (buffer, buffer_position) =
4894 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4895 output
4896 } else {
4897 return;
4898 };
4899 let buffer_snapshot = buffer.read(cx).snapshot();
4900 let show_completion_documentation = buffer_snapshot
4901 .settings_at(buffer_position, cx)
4902 .show_completion_documentation;
4903
4904 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4905
4906 let trigger_kind = match trigger {
4907 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4908 CompletionTriggerKind::TRIGGER_CHARACTER
4909 }
4910 _ => CompletionTriggerKind::INVOKED,
4911 };
4912 let completion_context = CompletionContext {
4913 trigger_character: trigger.and_then(|trigger| {
4914 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4915 Some(String::from(trigger))
4916 } else {
4917 None
4918 }
4919 }),
4920 trigger_kind,
4921 };
4922
4923 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4924 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4925 let word_to_exclude = buffer_snapshot
4926 .text_for_range(old_range.clone())
4927 .collect::<String>();
4928 (
4929 buffer_snapshot.anchor_before(old_range.start)
4930 ..buffer_snapshot.anchor_after(old_range.end),
4931 Some(word_to_exclude),
4932 )
4933 } else {
4934 (buffer_position..buffer_position, None)
4935 };
4936
4937 let completion_settings = language_settings(
4938 buffer_snapshot
4939 .language_at(buffer_position)
4940 .map(|language| language.name()),
4941 buffer_snapshot.file(),
4942 cx,
4943 )
4944 .completions;
4945
4946 // The document can be large, so stay in reasonable bounds when searching for words,
4947 // otherwise completion pop-up might be slow to appear.
4948 const WORD_LOOKUP_ROWS: u32 = 5_000;
4949 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4950 let min_word_search = buffer_snapshot.clip_point(
4951 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4952 Bias::Left,
4953 );
4954 let max_word_search = buffer_snapshot.clip_point(
4955 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4956 Bias::Right,
4957 );
4958 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4959 ..buffer_snapshot.point_to_offset(max_word_search);
4960
4961 let provider = self
4962 .completion_provider
4963 .as_ref()
4964 .filter(|_| !ignore_completion_provider);
4965 let skip_digits = query
4966 .as_ref()
4967 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4968
4969 let (mut words, provided_completions) = match provider {
4970 Some(provider) => {
4971 let completions = provider.completions(
4972 position.excerpt_id,
4973 &buffer,
4974 buffer_position,
4975 completion_context,
4976 window,
4977 cx,
4978 );
4979
4980 let words = match completion_settings.words {
4981 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4982 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4983 .background_spawn(async move {
4984 buffer_snapshot.words_in_range(WordsQuery {
4985 fuzzy_contents: None,
4986 range: word_search_range,
4987 skip_digits,
4988 })
4989 }),
4990 };
4991
4992 (words, completions)
4993 }
4994 None => (
4995 cx.background_spawn(async move {
4996 buffer_snapshot.words_in_range(WordsQuery {
4997 fuzzy_contents: None,
4998 range: word_search_range,
4999 skip_digits,
5000 })
5001 }),
5002 Task::ready(Ok(None)),
5003 ),
5004 };
5005
5006 let sort_completions = provider
5007 .as_ref()
5008 .map_or(false, |provider| provider.sort_completions());
5009
5010 let filter_completions = provider
5011 .as_ref()
5012 .map_or(true, |provider| provider.filter_completions());
5013
5014 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5015
5016 let id = post_inc(&mut self.next_completion_id);
5017 let task = cx.spawn_in(window, async move |editor, cx| {
5018 async move {
5019 editor.update(cx, |this, _| {
5020 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5021 })?;
5022
5023 let mut completions = Vec::new();
5024 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5025 completions.extend(provided_completions);
5026 if completion_settings.words == WordsCompletionMode::Fallback {
5027 words = Task::ready(BTreeMap::default());
5028 }
5029 }
5030
5031 let mut words = words.await;
5032 if let Some(word_to_exclude) = &word_to_exclude {
5033 words.remove(word_to_exclude);
5034 }
5035 for lsp_completion in &completions {
5036 words.remove(&lsp_completion.new_text);
5037 }
5038 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5039 replace_range: old_range.clone(),
5040 new_text: word.clone(),
5041 label: CodeLabel::plain(word, None),
5042 icon_path: None,
5043 documentation: None,
5044 source: CompletionSource::BufferWord {
5045 word_range,
5046 resolved: false,
5047 },
5048 insert_text_mode: Some(InsertTextMode::AS_IS),
5049 confirm: None,
5050 }));
5051
5052 let menu = if completions.is_empty() {
5053 None
5054 } else {
5055 let mut menu = CompletionsMenu::new(
5056 id,
5057 sort_completions,
5058 show_completion_documentation,
5059 ignore_completion_provider,
5060 position,
5061 buffer.clone(),
5062 completions.into(),
5063 snippet_sort_order,
5064 );
5065
5066 menu.filter(
5067 if filter_completions {
5068 query.as_deref()
5069 } else {
5070 None
5071 },
5072 cx.background_executor().clone(),
5073 )
5074 .await;
5075
5076 menu.visible().then_some(menu)
5077 };
5078
5079 editor.update_in(cx, |editor, window, cx| {
5080 match editor.context_menu.borrow().as_ref() {
5081 None => {}
5082 Some(CodeContextMenu::Completions(prev_menu)) => {
5083 if prev_menu.id > id {
5084 return;
5085 }
5086 }
5087 _ => return,
5088 }
5089
5090 if editor.focus_handle.is_focused(window) && menu.is_some() {
5091 let mut menu = menu.unwrap();
5092 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5093 crate::hover_popover::hide_hover(editor, cx);
5094 *editor.context_menu.borrow_mut() =
5095 Some(CodeContextMenu::Completions(menu));
5096
5097 if editor.show_edit_predictions_in_menu() {
5098 editor.update_visible_inline_completion(window, cx);
5099 } else {
5100 editor.discard_inline_completion(false, cx);
5101 }
5102
5103 cx.notify();
5104 } else if editor.completion_tasks.len() <= 1 {
5105 // If there are no more completion tasks and the last menu was
5106 // empty, we should hide it.
5107 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5108 // If it was already hidden and we don't show inline
5109 // completions in the menu, we should also show the
5110 // inline-completion when available.
5111 if was_hidden && editor.show_edit_predictions_in_menu() {
5112 editor.update_visible_inline_completion(window, cx);
5113 }
5114 }
5115 })?;
5116
5117 anyhow::Ok(())
5118 }
5119 .log_err()
5120 .await
5121 });
5122
5123 self.completion_tasks.push((id, task));
5124 }
5125
5126 #[cfg(feature = "test-support")]
5127 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5128 let menu = self.context_menu.borrow();
5129 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5130 let completions = menu.completions.borrow();
5131 Some(completions.to_vec())
5132 } else {
5133 None
5134 }
5135 }
5136
5137 pub fn confirm_completion(
5138 &mut self,
5139 action: &ConfirmCompletion,
5140 window: &mut Window,
5141 cx: &mut Context<Self>,
5142 ) -> Option<Task<Result<()>>> {
5143 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5144 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5145 }
5146
5147 pub fn confirm_completion_insert(
5148 &mut self,
5149 _: &ConfirmCompletionInsert,
5150 window: &mut Window,
5151 cx: &mut Context<Self>,
5152 ) -> Option<Task<Result<()>>> {
5153 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5154 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5155 }
5156
5157 pub fn confirm_completion_replace(
5158 &mut self,
5159 _: &ConfirmCompletionReplace,
5160 window: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) -> Option<Task<Result<()>>> {
5163 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5164 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5165 }
5166
5167 pub fn compose_completion(
5168 &mut self,
5169 action: &ComposeCompletion,
5170 window: &mut Window,
5171 cx: &mut Context<Self>,
5172 ) -> Option<Task<Result<()>>> {
5173 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5174 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5175 }
5176
5177 fn do_completion(
5178 &mut self,
5179 item_ix: Option<usize>,
5180 intent: CompletionIntent,
5181 window: &mut Window,
5182 cx: &mut Context<Editor>,
5183 ) -> Option<Task<Result<()>>> {
5184 use language::ToOffset as _;
5185
5186 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5187 else {
5188 return None;
5189 };
5190
5191 let candidate_id = {
5192 let entries = completions_menu.entries.borrow();
5193 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5194 if self.show_edit_predictions_in_menu() {
5195 self.discard_inline_completion(true, cx);
5196 }
5197 mat.candidate_id
5198 };
5199
5200 let buffer_handle = completions_menu.buffer;
5201 let completion = completions_menu
5202 .completions
5203 .borrow()
5204 .get(candidate_id)?
5205 .clone();
5206 cx.stop_propagation();
5207
5208 let snapshot = self.buffer.read(cx).snapshot(cx);
5209 let newest_anchor = self.selections.newest_anchor();
5210
5211 let snippet;
5212 let new_text;
5213 if completion.is_snippet() {
5214 let mut snippet_source = completion.new_text.clone();
5215 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5216 if scope.prefers_label_for_snippet_in_completion() {
5217 if let Some(label) = completion.label() {
5218 if matches!(
5219 completion.kind(),
5220 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5221 ) {
5222 snippet_source = label;
5223 }
5224 }
5225 }
5226 }
5227 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5228 new_text = snippet.as_ref().unwrap().text.clone();
5229 } else {
5230 snippet = None;
5231 new_text = completion.new_text.clone();
5232 };
5233
5234 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5235 let buffer = buffer_handle.read(cx);
5236 let replace_range_multibuffer = {
5237 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5238 let multibuffer_anchor = snapshot
5239 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5240 .unwrap()
5241 ..snapshot
5242 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5243 .unwrap();
5244 multibuffer_anchor.start.to_offset(&snapshot)
5245 ..multibuffer_anchor.end.to_offset(&snapshot)
5246 };
5247 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5248 return None;
5249 }
5250
5251 let old_text = buffer
5252 .text_for_range(replace_range.clone())
5253 .collect::<String>();
5254 let lookbehind = newest_anchor
5255 .start
5256 .text_anchor
5257 .to_offset(buffer)
5258 .saturating_sub(replace_range.start);
5259 let lookahead = replace_range
5260 .end
5261 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5262 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5263 let suffix = &old_text[lookbehind.min(old_text.len())..];
5264
5265 let selections = self.selections.all::<usize>(cx);
5266 let mut ranges = Vec::new();
5267 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5268
5269 for selection in &selections {
5270 let range = if selection.id == newest_anchor.id {
5271 replace_range_multibuffer.clone()
5272 } else {
5273 let mut range = selection.range();
5274
5275 // if prefix is present, don't duplicate it
5276 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5277 range.start = range.start.saturating_sub(lookbehind);
5278
5279 // if suffix is also present, mimic the newest cursor and replace it
5280 if selection.id != newest_anchor.id
5281 && snapshot.contains_str_at(range.end, suffix)
5282 {
5283 range.end += lookahead;
5284 }
5285 }
5286 range
5287 };
5288
5289 ranges.push(range.clone());
5290
5291 if !self.linked_edit_ranges.is_empty() {
5292 let start_anchor = snapshot.anchor_before(range.start);
5293 let end_anchor = snapshot.anchor_after(range.end);
5294 if let Some(ranges) = self
5295 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5296 {
5297 for (buffer, edits) in ranges {
5298 linked_edits
5299 .entry(buffer.clone())
5300 .or_default()
5301 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5302 }
5303 }
5304 }
5305 }
5306
5307 cx.emit(EditorEvent::InputHandled {
5308 utf16_range_to_replace: None,
5309 text: new_text.clone().into(),
5310 });
5311
5312 self.transact(window, cx, |this, window, cx| {
5313 if let Some(mut snippet) = snippet {
5314 snippet.text = new_text.to_string();
5315 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5316 } else {
5317 this.buffer.update(cx, |buffer, cx| {
5318 let auto_indent = match completion.insert_text_mode {
5319 Some(InsertTextMode::AS_IS) => None,
5320 _ => this.autoindent_mode.clone(),
5321 };
5322 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5323 buffer.edit(edits, auto_indent, cx);
5324 });
5325 }
5326 for (buffer, edits) in linked_edits {
5327 buffer.update(cx, |buffer, cx| {
5328 let snapshot = buffer.snapshot();
5329 let edits = edits
5330 .into_iter()
5331 .map(|(range, text)| {
5332 use text::ToPoint as TP;
5333 let end_point = TP::to_point(&range.end, &snapshot);
5334 let start_point = TP::to_point(&range.start, &snapshot);
5335 (start_point..end_point, text)
5336 })
5337 .sorted_by_key(|(range, _)| range.start);
5338 buffer.edit(edits, None, cx);
5339 })
5340 }
5341
5342 this.refresh_inline_completion(true, false, window, cx);
5343 });
5344
5345 let show_new_completions_on_confirm = completion
5346 .confirm
5347 .as_ref()
5348 .map_or(false, |confirm| confirm(intent, window, cx));
5349 if show_new_completions_on_confirm {
5350 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5351 }
5352
5353 let provider = self.completion_provider.as_ref()?;
5354 drop(completion);
5355 let apply_edits = provider.apply_additional_edits_for_completion(
5356 buffer_handle,
5357 completions_menu.completions.clone(),
5358 candidate_id,
5359 true,
5360 cx,
5361 );
5362
5363 let editor_settings = EditorSettings::get_global(cx);
5364 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5365 // After the code completion is finished, users often want to know what signatures are needed.
5366 // so we should automatically call signature_help
5367 self.show_signature_help(&ShowSignatureHelp, window, cx);
5368 }
5369
5370 Some(cx.foreground_executor().spawn(async move {
5371 apply_edits.await?;
5372 Ok(())
5373 }))
5374 }
5375
5376 pub fn toggle_code_actions(
5377 &mut self,
5378 action: &ToggleCodeActions,
5379 window: &mut Window,
5380 cx: &mut Context<Self>,
5381 ) {
5382 let quick_launch = action.quick_launch;
5383 let mut context_menu = self.context_menu.borrow_mut();
5384 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5385 if code_actions.deployed_from == action.deployed_from {
5386 // Toggle if we're selecting the same one
5387 *context_menu = None;
5388 cx.notify();
5389 return;
5390 } else {
5391 // Otherwise, clear it and start a new one
5392 *context_menu = None;
5393 cx.notify();
5394 }
5395 }
5396 drop(context_menu);
5397 let snapshot = self.snapshot(window, cx);
5398 let deployed_from = action.deployed_from.clone();
5399 let mut task = self.code_actions_task.take();
5400 let action = action.clone();
5401 cx.spawn_in(window, async move |editor, cx| {
5402 while let Some(prev_task) = task {
5403 prev_task.await.log_err();
5404 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5405 }
5406
5407 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5408 if editor.focus_handle.is_focused(window) {
5409 let multibuffer_point = match &action.deployed_from {
5410 Some(CodeActionSource::Indicator(row)) => {
5411 DisplayPoint::new(*row, 0).to_point(&snapshot)
5412 }
5413 _ => editor.selections.newest::<Point>(cx).head(),
5414 };
5415 let (buffer, buffer_row) = snapshot
5416 .buffer_snapshot
5417 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5418 .and_then(|(buffer_snapshot, range)| {
5419 editor
5420 .buffer
5421 .read(cx)
5422 .buffer(buffer_snapshot.remote_id())
5423 .map(|buffer| (buffer, range.start.row))
5424 })?;
5425 let (_, code_actions) = editor
5426 .available_code_actions
5427 .clone()
5428 .and_then(|(location, code_actions)| {
5429 let snapshot = location.buffer.read(cx).snapshot();
5430 let point_range = location.range.to_point(&snapshot);
5431 let point_range = point_range.start.row..=point_range.end.row;
5432 if point_range.contains(&buffer_row) {
5433 Some((location, code_actions))
5434 } else {
5435 None
5436 }
5437 })
5438 .unzip();
5439 let buffer_id = buffer.read(cx).remote_id();
5440 let tasks = editor
5441 .tasks
5442 .get(&(buffer_id, buffer_row))
5443 .map(|t| Arc::new(t.to_owned()));
5444 if tasks.is_none() && code_actions.is_none() {
5445 return None;
5446 }
5447
5448 editor.completion_tasks.clear();
5449 editor.discard_inline_completion(false, cx);
5450 let task_context =
5451 tasks
5452 .as_ref()
5453 .zip(editor.project.clone())
5454 .map(|(tasks, project)| {
5455 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5456 });
5457
5458 Some(cx.spawn_in(window, async move |editor, cx| {
5459 let task_context = match task_context {
5460 Some(task_context) => task_context.await,
5461 None => None,
5462 };
5463 let resolved_tasks =
5464 tasks
5465 .zip(task_context.clone())
5466 .map(|(tasks, task_context)| ResolvedTasks {
5467 templates: tasks.resolve(&task_context).collect(),
5468 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5469 multibuffer_point.row,
5470 tasks.column,
5471 )),
5472 });
5473 let debug_scenarios = editor.update(cx, |editor, cx| {
5474 if cx.has_flag::<DebuggerFeatureFlag>() {
5475 maybe!({
5476 let project = editor.project.as_ref()?;
5477 let dap_store = project.read(cx).dap_store();
5478 let mut scenarios = vec![];
5479 let resolved_tasks = resolved_tasks.as_ref()?;
5480 let buffer = buffer.read(cx);
5481 let language = buffer.language()?;
5482 let file = buffer.file();
5483 let debug_adapter =
5484 language_settings(language.name().into(), file, cx)
5485 .debuggers
5486 .first()
5487 .map(SharedString::from)
5488 .or_else(|| {
5489 language
5490 .config()
5491 .debuggers
5492 .first()
5493 .map(SharedString::from)
5494 })?;
5495
5496 dap_store.update(cx, |dap_store, cx| {
5497 for (_, task) in &resolved_tasks.templates {
5498 if let Some(scenario) = dap_store
5499 .debug_scenario_for_build_task(
5500 task.original_task().clone(),
5501 debug_adapter.clone().into(),
5502 task.display_label().to_owned().into(),
5503 cx,
5504 )
5505 {
5506 scenarios.push(scenario);
5507 }
5508 }
5509 });
5510 Some(scenarios)
5511 })
5512 .unwrap_or_default()
5513 } else {
5514 vec![]
5515 }
5516 })?;
5517 let spawn_straight_away = quick_launch
5518 && resolved_tasks
5519 .as_ref()
5520 .map_or(false, |tasks| tasks.templates.len() == 1)
5521 && code_actions
5522 .as_ref()
5523 .map_or(true, |actions| actions.is_empty())
5524 && debug_scenarios.is_empty();
5525 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5526 crate::hover_popover::hide_hover(editor, cx);
5527 *editor.context_menu.borrow_mut() =
5528 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5529 buffer,
5530 actions: CodeActionContents::new(
5531 resolved_tasks,
5532 code_actions,
5533 debug_scenarios,
5534 task_context.unwrap_or_default(),
5535 ),
5536 selected_item: Default::default(),
5537 scroll_handle: UniformListScrollHandle::default(),
5538 deployed_from,
5539 }));
5540 if spawn_straight_away {
5541 if let Some(task) = editor.confirm_code_action(
5542 &ConfirmCodeAction { item_ix: Some(0) },
5543 window,
5544 cx,
5545 ) {
5546 cx.notify();
5547 return task;
5548 }
5549 }
5550 cx.notify();
5551 Task::ready(Ok(()))
5552 }) {
5553 task.await
5554 } else {
5555 Ok(())
5556 }
5557 }))
5558 } else {
5559 Some(Task::ready(Ok(())))
5560 }
5561 })?;
5562 if let Some(task) = spawned_test_task {
5563 task.await?;
5564 }
5565
5566 anyhow::Ok(())
5567 })
5568 .detach_and_log_err(cx);
5569 }
5570
5571 pub fn confirm_code_action(
5572 &mut self,
5573 action: &ConfirmCodeAction,
5574 window: &mut Window,
5575 cx: &mut Context<Self>,
5576 ) -> Option<Task<Result<()>>> {
5577 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5578
5579 let actions_menu =
5580 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5581 menu
5582 } else {
5583 return None;
5584 };
5585
5586 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5587 let action = actions_menu.actions.get(action_ix)?;
5588 let title = action.label();
5589 let buffer = actions_menu.buffer;
5590 let workspace = self.workspace()?;
5591
5592 match action {
5593 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5594 workspace.update(cx, |workspace, cx| {
5595 workspace.schedule_resolved_task(
5596 task_source_kind,
5597 resolved_task,
5598 false,
5599 window,
5600 cx,
5601 );
5602
5603 Some(Task::ready(Ok(())))
5604 })
5605 }
5606 CodeActionsItem::CodeAction {
5607 excerpt_id,
5608 action,
5609 provider,
5610 } => {
5611 let apply_code_action =
5612 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5613 let workspace = workspace.downgrade();
5614 Some(cx.spawn_in(window, async move |editor, cx| {
5615 let project_transaction = apply_code_action.await?;
5616 Self::open_project_transaction(
5617 &editor,
5618 workspace,
5619 project_transaction,
5620 title,
5621 cx,
5622 )
5623 .await
5624 }))
5625 }
5626 CodeActionsItem::DebugScenario(scenario) => {
5627 let context = actions_menu.actions.context.clone();
5628
5629 workspace.update(cx, |workspace, cx| {
5630 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5631 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5632 });
5633 Some(Task::ready(Ok(())))
5634 }
5635 }
5636 }
5637
5638 pub async fn open_project_transaction(
5639 this: &WeakEntity<Editor>,
5640 workspace: WeakEntity<Workspace>,
5641 transaction: ProjectTransaction,
5642 title: String,
5643 cx: &mut AsyncWindowContext,
5644 ) -> Result<()> {
5645 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5646 cx.update(|_, cx| {
5647 entries.sort_unstable_by_key(|(buffer, _)| {
5648 buffer.read(cx).file().map(|f| f.path().clone())
5649 });
5650 })?;
5651
5652 // If the project transaction's edits are all contained within this editor, then
5653 // avoid opening a new editor to display them.
5654
5655 if let Some((buffer, transaction)) = entries.first() {
5656 if entries.len() == 1 {
5657 let excerpt = this.update(cx, |editor, cx| {
5658 editor
5659 .buffer()
5660 .read(cx)
5661 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5662 })?;
5663 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5664 if excerpted_buffer == *buffer {
5665 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5666 let excerpt_range = excerpt_range.to_offset(buffer);
5667 buffer
5668 .edited_ranges_for_transaction::<usize>(transaction)
5669 .all(|range| {
5670 excerpt_range.start <= range.start
5671 && excerpt_range.end >= range.end
5672 })
5673 })?;
5674
5675 if all_edits_within_excerpt {
5676 return Ok(());
5677 }
5678 }
5679 }
5680 }
5681 } else {
5682 return Ok(());
5683 }
5684
5685 let mut ranges_to_highlight = Vec::new();
5686 let excerpt_buffer = cx.new(|cx| {
5687 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5688 for (buffer_handle, transaction) in &entries {
5689 let edited_ranges = buffer_handle
5690 .read(cx)
5691 .edited_ranges_for_transaction::<Point>(transaction)
5692 .collect::<Vec<_>>();
5693 let (ranges, _) = multibuffer.set_excerpts_for_path(
5694 PathKey::for_buffer(buffer_handle, cx),
5695 buffer_handle.clone(),
5696 edited_ranges,
5697 DEFAULT_MULTIBUFFER_CONTEXT,
5698 cx,
5699 );
5700
5701 ranges_to_highlight.extend(ranges);
5702 }
5703 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5704 multibuffer
5705 })?;
5706
5707 workspace.update_in(cx, |workspace, window, cx| {
5708 let project = workspace.project().clone();
5709 let editor =
5710 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5711 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5712 editor.update(cx, |editor, cx| {
5713 editor.highlight_background::<Self>(
5714 &ranges_to_highlight,
5715 |theme| theme.editor_highlighted_line_background,
5716 cx,
5717 );
5718 });
5719 })?;
5720
5721 Ok(())
5722 }
5723
5724 pub fn clear_code_action_providers(&mut self) {
5725 self.code_action_providers.clear();
5726 self.available_code_actions.take();
5727 }
5728
5729 pub fn add_code_action_provider(
5730 &mut self,
5731 provider: Rc<dyn CodeActionProvider>,
5732 window: &mut Window,
5733 cx: &mut Context<Self>,
5734 ) {
5735 if self
5736 .code_action_providers
5737 .iter()
5738 .any(|existing_provider| existing_provider.id() == provider.id())
5739 {
5740 return;
5741 }
5742
5743 self.code_action_providers.push(provider);
5744 self.refresh_code_actions(window, cx);
5745 }
5746
5747 pub fn remove_code_action_provider(
5748 &mut self,
5749 id: Arc<str>,
5750 window: &mut Window,
5751 cx: &mut Context<Self>,
5752 ) {
5753 self.code_action_providers
5754 .retain(|provider| provider.id() != id);
5755 self.refresh_code_actions(window, cx);
5756 }
5757
5758 pub fn code_actions_enabled(&self, cx: &App) -> bool {
5759 !self.code_action_providers.is_empty()
5760 && EditorSettings::get_global(cx).toolbar.code_actions
5761 }
5762
5763 pub fn has_available_code_actions(&self) -> bool {
5764 self.available_code_actions
5765 .as_ref()
5766 .is_some_and(|(_, actions)| !actions.is_empty())
5767 }
5768
5769 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5770 &self.context_menu
5771 }
5772
5773 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5774 let newest_selection = self.selections.newest_anchor().clone();
5775 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5776 let buffer = self.buffer.read(cx);
5777 if newest_selection.head().diff_base_anchor.is_some() {
5778 return None;
5779 }
5780 let (start_buffer, start) =
5781 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5782 let (end_buffer, end) =
5783 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5784 if start_buffer != end_buffer {
5785 return None;
5786 }
5787
5788 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5789 cx.background_executor()
5790 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5791 .await;
5792
5793 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5794 let providers = this.code_action_providers.clone();
5795 let tasks = this
5796 .code_action_providers
5797 .iter()
5798 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5799 .collect::<Vec<_>>();
5800 (providers, tasks)
5801 })?;
5802
5803 let mut actions = Vec::new();
5804 for (provider, provider_actions) in
5805 providers.into_iter().zip(future::join_all(tasks).await)
5806 {
5807 if let Some(provider_actions) = provider_actions.log_err() {
5808 actions.extend(provider_actions.into_iter().map(|action| {
5809 AvailableCodeAction {
5810 excerpt_id: newest_selection.start.excerpt_id,
5811 action,
5812 provider: provider.clone(),
5813 }
5814 }));
5815 }
5816 }
5817
5818 this.update(cx, |this, cx| {
5819 this.available_code_actions = if actions.is_empty() {
5820 None
5821 } else {
5822 Some((
5823 Location {
5824 buffer: start_buffer,
5825 range: start..end,
5826 },
5827 actions.into(),
5828 ))
5829 };
5830 cx.notify();
5831 })
5832 }));
5833 None
5834 }
5835
5836 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5837 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5838 self.show_git_blame_inline = false;
5839
5840 self.show_git_blame_inline_delay_task =
5841 Some(cx.spawn_in(window, async move |this, cx| {
5842 cx.background_executor().timer(delay).await;
5843
5844 this.update(cx, |this, cx| {
5845 this.show_git_blame_inline = true;
5846 cx.notify();
5847 })
5848 .log_err();
5849 }));
5850 }
5851 }
5852
5853 fn show_blame_popover(
5854 &mut self,
5855 blame_entry: &BlameEntry,
5856 position: gpui::Point<Pixels>,
5857 cx: &mut Context<Self>,
5858 ) {
5859 if let Some(state) = &mut self.inline_blame_popover {
5860 state.hide_task.take();
5861 cx.notify();
5862 } else {
5863 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5864 let show_task = cx.spawn(async move |editor, cx| {
5865 cx.background_executor()
5866 .timer(std::time::Duration::from_millis(delay))
5867 .await;
5868 editor
5869 .update(cx, |editor, cx| {
5870 if let Some(state) = &mut editor.inline_blame_popover {
5871 state.show_task = None;
5872 cx.notify();
5873 }
5874 })
5875 .ok();
5876 });
5877 let Some(blame) = self.blame.as_ref() else {
5878 return;
5879 };
5880 let blame = blame.read(cx);
5881 let details = blame.details_for_entry(&blame_entry);
5882 let markdown = cx.new(|cx| {
5883 Markdown::new(
5884 details
5885 .as_ref()
5886 .map(|message| message.message.clone())
5887 .unwrap_or_default(),
5888 None,
5889 None,
5890 cx,
5891 )
5892 });
5893 self.inline_blame_popover = Some(InlineBlamePopover {
5894 position,
5895 show_task: Some(show_task),
5896 hide_task: None,
5897 popover_bounds: None,
5898 popover_state: InlineBlamePopoverState {
5899 scroll_handle: ScrollHandle::new(),
5900 commit_message: details,
5901 markdown,
5902 },
5903 });
5904 }
5905 }
5906
5907 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5908 if let Some(state) = &mut self.inline_blame_popover {
5909 if state.show_task.is_some() {
5910 self.inline_blame_popover.take();
5911 cx.notify();
5912 } else {
5913 let hide_task = cx.spawn(async move |editor, cx| {
5914 cx.background_executor()
5915 .timer(std::time::Duration::from_millis(100))
5916 .await;
5917 editor
5918 .update(cx, |editor, cx| {
5919 editor.inline_blame_popover.take();
5920 cx.notify();
5921 })
5922 .ok();
5923 });
5924 state.hide_task = Some(hide_task);
5925 }
5926 }
5927 }
5928
5929 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5930 if self.pending_rename.is_some() {
5931 return None;
5932 }
5933
5934 let provider = self.semantics_provider.clone()?;
5935 let buffer = self.buffer.read(cx);
5936 let newest_selection = self.selections.newest_anchor().clone();
5937 let cursor_position = newest_selection.head();
5938 let (cursor_buffer, cursor_buffer_position) =
5939 buffer.text_anchor_for_position(cursor_position, cx)?;
5940 let (tail_buffer, tail_buffer_position) =
5941 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5942 if cursor_buffer != tail_buffer {
5943 return None;
5944 }
5945
5946 let snapshot = cursor_buffer.read(cx).snapshot();
5947 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5948 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5949 if start_word_range != end_word_range {
5950 self.document_highlights_task.take();
5951 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5952 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5953 return None;
5954 }
5955
5956 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5957 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5958 cx.background_executor()
5959 .timer(Duration::from_millis(debounce))
5960 .await;
5961
5962 let highlights = if let Some(highlights) = cx
5963 .update(|cx| {
5964 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5965 })
5966 .ok()
5967 .flatten()
5968 {
5969 highlights.await.log_err()
5970 } else {
5971 None
5972 };
5973
5974 if let Some(highlights) = highlights {
5975 this.update(cx, |this, cx| {
5976 if this.pending_rename.is_some() {
5977 return;
5978 }
5979
5980 let buffer_id = cursor_position.buffer_id;
5981 let buffer = this.buffer.read(cx);
5982 if !buffer
5983 .text_anchor_for_position(cursor_position, cx)
5984 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5985 {
5986 return;
5987 }
5988
5989 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5990 let mut write_ranges = Vec::new();
5991 let mut read_ranges = Vec::new();
5992 for highlight in highlights {
5993 for (excerpt_id, excerpt_range) in
5994 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5995 {
5996 let start = highlight
5997 .range
5998 .start
5999 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6000 let end = highlight
6001 .range
6002 .end
6003 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6004 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6005 continue;
6006 }
6007
6008 let range = Anchor {
6009 buffer_id,
6010 excerpt_id,
6011 text_anchor: start,
6012 diff_base_anchor: None,
6013 }..Anchor {
6014 buffer_id,
6015 excerpt_id,
6016 text_anchor: end,
6017 diff_base_anchor: None,
6018 };
6019 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6020 write_ranges.push(range);
6021 } else {
6022 read_ranges.push(range);
6023 }
6024 }
6025 }
6026
6027 this.highlight_background::<DocumentHighlightRead>(
6028 &read_ranges,
6029 |theme| theme.editor_document_highlight_read_background,
6030 cx,
6031 );
6032 this.highlight_background::<DocumentHighlightWrite>(
6033 &write_ranges,
6034 |theme| theme.editor_document_highlight_write_background,
6035 cx,
6036 );
6037 cx.notify();
6038 })
6039 .log_err();
6040 }
6041 }));
6042 None
6043 }
6044
6045 fn prepare_highlight_query_from_selection(
6046 &mut self,
6047 cx: &mut Context<Editor>,
6048 ) -> Option<(String, Range<Anchor>)> {
6049 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6050 return None;
6051 }
6052 if !EditorSettings::get_global(cx).selection_highlight {
6053 return None;
6054 }
6055 if self.selections.count() != 1 || self.selections.line_mode {
6056 return None;
6057 }
6058 let selection = self.selections.newest::<Point>(cx);
6059 if selection.is_empty() || selection.start.row != selection.end.row {
6060 return None;
6061 }
6062 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6063 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6064 let query = multi_buffer_snapshot
6065 .text_for_range(selection_anchor_range.clone())
6066 .collect::<String>();
6067 if query.trim().is_empty() {
6068 return None;
6069 }
6070 Some((query, selection_anchor_range))
6071 }
6072
6073 fn update_selection_occurrence_highlights(
6074 &mut self,
6075 query_text: String,
6076 query_range: Range<Anchor>,
6077 multi_buffer_range_to_query: Range<Point>,
6078 use_debounce: bool,
6079 window: &mut Window,
6080 cx: &mut Context<Editor>,
6081 ) -> Task<()> {
6082 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6083 cx.spawn_in(window, async move |editor, cx| {
6084 if use_debounce {
6085 cx.background_executor()
6086 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6087 .await;
6088 }
6089 let match_task = cx.background_spawn(async move {
6090 let buffer_ranges = multi_buffer_snapshot
6091 .range_to_buffer_ranges(multi_buffer_range_to_query)
6092 .into_iter()
6093 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6094 let mut match_ranges = Vec::new();
6095 let Ok(regex) = project::search::SearchQuery::text(
6096 query_text.clone(),
6097 false,
6098 false,
6099 false,
6100 Default::default(),
6101 Default::default(),
6102 false,
6103 None,
6104 ) else {
6105 return Vec::default();
6106 };
6107 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6108 match_ranges.extend(
6109 regex
6110 .search(&buffer_snapshot, Some(search_range.clone()))
6111 .await
6112 .into_iter()
6113 .filter_map(|match_range| {
6114 let match_start = buffer_snapshot
6115 .anchor_after(search_range.start + match_range.start);
6116 let match_end = buffer_snapshot
6117 .anchor_before(search_range.start + match_range.end);
6118 let match_anchor_range = Anchor::range_in_buffer(
6119 excerpt_id,
6120 buffer_snapshot.remote_id(),
6121 match_start..match_end,
6122 );
6123 (match_anchor_range != query_range).then_some(match_anchor_range)
6124 }),
6125 );
6126 }
6127 match_ranges
6128 });
6129 let match_ranges = match_task.await;
6130 editor
6131 .update_in(cx, |editor, _, cx| {
6132 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6133 if !match_ranges.is_empty() {
6134 editor.highlight_background::<SelectedTextHighlight>(
6135 &match_ranges,
6136 |theme| theme.editor_document_highlight_bracket_background,
6137 cx,
6138 )
6139 }
6140 })
6141 .log_err();
6142 })
6143 }
6144
6145 fn refresh_selected_text_highlights(
6146 &mut self,
6147 on_buffer_edit: bool,
6148 window: &mut Window,
6149 cx: &mut Context<Editor>,
6150 ) {
6151 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6152 else {
6153 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6154 self.quick_selection_highlight_task.take();
6155 self.debounced_selection_highlight_task.take();
6156 return;
6157 };
6158 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6159 if on_buffer_edit
6160 || self
6161 .quick_selection_highlight_task
6162 .as_ref()
6163 .map_or(true, |(prev_anchor_range, _)| {
6164 prev_anchor_range != &query_range
6165 })
6166 {
6167 let multi_buffer_visible_start = self
6168 .scroll_manager
6169 .anchor()
6170 .anchor
6171 .to_point(&multi_buffer_snapshot);
6172 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6173 multi_buffer_visible_start
6174 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6175 Bias::Left,
6176 );
6177 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6178 self.quick_selection_highlight_task = Some((
6179 query_range.clone(),
6180 self.update_selection_occurrence_highlights(
6181 query_text.clone(),
6182 query_range.clone(),
6183 multi_buffer_visible_range,
6184 false,
6185 window,
6186 cx,
6187 ),
6188 ));
6189 }
6190 if on_buffer_edit
6191 || self
6192 .debounced_selection_highlight_task
6193 .as_ref()
6194 .map_or(true, |(prev_anchor_range, _)| {
6195 prev_anchor_range != &query_range
6196 })
6197 {
6198 let multi_buffer_start = multi_buffer_snapshot
6199 .anchor_before(0)
6200 .to_point(&multi_buffer_snapshot);
6201 let multi_buffer_end = multi_buffer_snapshot
6202 .anchor_after(multi_buffer_snapshot.len())
6203 .to_point(&multi_buffer_snapshot);
6204 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6205 self.debounced_selection_highlight_task = Some((
6206 query_range.clone(),
6207 self.update_selection_occurrence_highlights(
6208 query_text,
6209 query_range,
6210 multi_buffer_full_range,
6211 true,
6212 window,
6213 cx,
6214 ),
6215 ));
6216 }
6217 }
6218
6219 pub fn refresh_inline_completion(
6220 &mut self,
6221 debounce: bool,
6222 user_requested: bool,
6223 window: &mut Window,
6224 cx: &mut Context<Self>,
6225 ) -> Option<()> {
6226 let provider = self.edit_prediction_provider()?;
6227 let cursor = self.selections.newest_anchor().head();
6228 let (buffer, cursor_buffer_position) =
6229 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6230
6231 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6232 self.discard_inline_completion(false, cx);
6233 return None;
6234 }
6235
6236 if !user_requested
6237 && (!self.should_show_edit_predictions()
6238 || !self.is_focused(window)
6239 || buffer.read(cx).is_empty())
6240 {
6241 self.discard_inline_completion(false, cx);
6242 return None;
6243 }
6244
6245 self.update_visible_inline_completion(window, cx);
6246 provider.refresh(
6247 self.project.clone(),
6248 buffer,
6249 cursor_buffer_position,
6250 debounce,
6251 cx,
6252 );
6253 Some(())
6254 }
6255
6256 fn show_edit_predictions_in_menu(&self) -> bool {
6257 match self.edit_prediction_settings {
6258 EditPredictionSettings::Disabled => false,
6259 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6260 }
6261 }
6262
6263 pub fn edit_predictions_enabled(&self) -> bool {
6264 match self.edit_prediction_settings {
6265 EditPredictionSettings::Disabled => false,
6266 EditPredictionSettings::Enabled { .. } => true,
6267 }
6268 }
6269
6270 fn edit_prediction_requires_modifier(&self) -> bool {
6271 match self.edit_prediction_settings {
6272 EditPredictionSettings::Disabled => false,
6273 EditPredictionSettings::Enabled {
6274 preview_requires_modifier,
6275 ..
6276 } => preview_requires_modifier,
6277 }
6278 }
6279
6280 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6281 if self.edit_prediction_provider.is_none() {
6282 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6283 } else {
6284 let selection = self.selections.newest_anchor();
6285 let cursor = selection.head();
6286
6287 if let Some((buffer, cursor_buffer_position)) =
6288 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6289 {
6290 self.edit_prediction_settings =
6291 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6292 }
6293 }
6294 }
6295
6296 fn edit_prediction_settings_at_position(
6297 &self,
6298 buffer: &Entity<Buffer>,
6299 buffer_position: language::Anchor,
6300 cx: &App,
6301 ) -> EditPredictionSettings {
6302 if !self.mode.is_full()
6303 || !self.show_inline_completions_override.unwrap_or(true)
6304 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6305 {
6306 return EditPredictionSettings::Disabled;
6307 }
6308
6309 let buffer = buffer.read(cx);
6310
6311 let file = buffer.file();
6312
6313 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6314 return EditPredictionSettings::Disabled;
6315 };
6316
6317 let by_provider = matches!(
6318 self.menu_inline_completions_policy,
6319 MenuInlineCompletionsPolicy::ByProvider
6320 );
6321
6322 let show_in_menu = by_provider
6323 && self
6324 .edit_prediction_provider
6325 .as_ref()
6326 .map_or(false, |provider| {
6327 provider.provider.show_completions_in_menu()
6328 });
6329
6330 let preview_requires_modifier =
6331 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6332
6333 EditPredictionSettings::Enabled {
6334 show_in_menu,
6335 preview_requires_modifier,
6336 }
6337 }
6338
6339 fn should_show_edit_predictions(&self) -> bool {
6340 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6341 }
6342
6343 pub fn edit_prediction_preview_is_active(&self) -> bool {
6344 matches!(
6345 self.edit_prediction_preview,
6346 EditPredictionPreview::Active { .. }
6347 )
6348 }
6349
6350 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6351 let cursor = self.selections.newest_anchor().head();
6352 if let Some((buffer, cursor_position)) =
6353 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6354 {
6355 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6356 } else {
6357 false
6358 }
6359 }
6360
6361 pub fn supports_minimap(&self, cx: &App) -> bool {
6362 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6363 }
6364
6365 fn edit_predictions_enabled_in_buffer(
6366 &self,
6367 buffer: &Entity<Buffer>,
6368 buffer_position: language::Anchor,
6369 cx: &App,
6370 ) -> bool {
6371 maybe!({
6372 if self.read_only(cx) {
6373 return Some(false);
6374 }
6375 let provider = self.edit_prediction_provider()?;
6376 if !provider.is_enabled(&buffer, buffer_position, cx) {
6377 return Some(false);
6378 }
6379 let buffer = buffer.read(cx);
6380 let Some(file) = buffer.file() else {
6381 return Some(true);
6382 };
6383 let settings = all_language_settings(Some(file), cx);
6384 Some(settings.edit_predictions_enabled_for_file(file, cx))
6385 })
6386 .unwrap_or(false)
6387 }
6388
6389 fn cycle_inline_completion(
6390 &mut self,
6391 direction: Direction,
6392 window: &mut Window,
6393 cx: &mut Context<Self>,
6394 ) -> Option<()> {
6395 let provider = self.edit_prediction_provider()?;
6396 let cursor = self.selections.newest_anchor().head();
6397 let (buffer, cursor_buffer_position) =
6398 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6399 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6400 return None;
6401 }
6402
6403 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6404 self.update_visible_inline_completion(window, cx);
6405
6406 Some(())
6407 }
6408
6409 pub fn show_inline_completion(
6410 &mut self,
6411 _: &ShowEditPrediction,
6412 window: &mut Window,
6413 cx: &mut Context<Self>,
6414 ) {
6415 if !self.has_active_inline_completion() {
6416 self.refresh_inline_completion(false, true, window, cx);
6417 return;
6418 }
6419
6420 self.update_visible_inline_completion(window, cx);
6421 }
6422
6423 pub fn display_cursor_names(
6424 &mut self,
6425 _: &DisplayCursorNames,
6426 window: &mut Window,
6427 cx: &mut Context<Self>,
6428 ) {
6429 self.show_cursor_names(window, cx);
6430 }
6431
6432 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6433 self.show_cursor_names = true;
6434 cx.notify();
6435 cx.spawn_in(window, async move |this, cx| {
6436 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6437 this.update(cx, |this, cx| {
6438 this.show_cursor_names = false;
6439 cx.notify()
6440 })
6441 .ok()
6442 })
6443 .detach();
6444 }
6445
6446 pub fn next_edit_prediction(
6447 &mut self,
6448 _: &NextEditPrediction,
6449 window: &mut Window,
6450 cx: &mut Context<Self>,
6451 ) {
6452 if self.has_active_inline_completion() {
6453 self.cycle_inline_completion(Direction::Next, window, cx);
6454 } else {
6455 let is_copilot_disabled = self
6456 .refresh_inline_completion(false, true, window, cx)
6457 .is_none();
6458 if is_copilot_disabled {
6459 cx.propagate();
6460 }
6461 }
6462 }
6463
6464 pub fn previous_edit_prediction(
6465 &mut self,
6466 _: &PreviousEditPrediction,
6467 window: &mut Window,
6468 cx: &mut Context<Self>,
6469 ) {
6470 if self.has_active_inline_completion() {
6471 self.cycle_inline_completion(Direction::Prev, window, cx);
6472 } else {
6473 let is_copilot_disabled = self
6474 .refresh_inline_completion(false, true, window, cx)
6475 .is_none();
6476 if is_copilot_disabled {
6477 cx.propagate();
6478 }
6479 }
6480 }
6481
6482 pub fn accept_edit_prediction(
6483 &mut self,
6484 _: &AcceptEditPrediction,
6485 window: &mut Window,
6486 cx: &mut Context<Self>,
6487 ) {
6488 if self.show_edit_predictions_in_menu() {
6489 self.hide_context_menu(window, cx);
6490 }
6491
6492 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6493 return;
6494 };
6495
6496 self.report_inline_completion_event(
6497 active_inline_completion.completion_id.clone(),
6498 true,
6499 cx,
6500 );
6501
6502 match &active_inline_completion.completion {
6503 InlineCompletion::Move { target, .. } => {
6504 let target = *target;
6505
6506 if let Some(position_map) = &self.last_position_map {
6507 if position_map
6508 .visible_row_range
6509 .contains(&target.to_display_point(&position_map.snapshot).row())
6510 || !self.edit_prediction_requires_modifier()
6511 {
6512 self.unfold_ranges(&[target..target], true, false, cx);
6513 // Note that this is also done in vim's handler of the Tab action.
6514 self.change_selections(
6515 Some(Autoscroll::newest()),
6516 window,
6517 cx,
6518 |selections| {
6519 selections.select_anchor_ranges([target..target]);
6520 },
6521 );
6522 self.clear_row_highlights::<EditPredictionPreview>();
6523
6524 self.edit_prediction_preview
6525 .set_previous_scroll_position(None);
6526 } else {
6527 self.edit_prediction_preview
6528 .set_previous_scroll_position(Some(
6529 position_map.snapshot.scroll_anchor,
6530 ));
6531
6532 self.highlight_rows::<EditPredictionPreview>(
6533 target..target,
6534 cx.theme().colors().editor_highlighted_line_background,
6535 RowHighlightOptions {
6536 autoscroll: true,
6537 ..Default::default()
6538 },
6539 cx,
6540 );
6541 self.request_autoscroll(Autoscroll::fit(), cx);
6542 }
6543 }
6544 }
6545 InlineCompletion::Edit { edits, .. } => {
6546 if let Some(provider) = self.edit_prediction_provider() {
6547 provider.accept(cx);
6548 }
6549
6550 // Store the transaction ID and selections before applying the edit
6551 let transaction_id_prev =
6552 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
6553
6554 let snapshot = self.buffer.read(cx).snapshot(cx);
6555 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6556
6557 self.buffer.update(cx, |buffer, cx| {
6558 buffer.edit(edits.iter().cloned(), None, cx)
6559 });
6560
6561 self.change_selections(None, window, cx, |s| {
6562 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6563 });
6564
6565 let selections = self.selections.disjoint_anchors();
6566 if let Some(transaction_id_now) =
6567 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
6568 {
6569 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6570 if has_new_transaction {
6571 self.selection_history
6572 .insert_transaction(transaction_id_now, selections);
6573 }
6574 }
6575
6576 self.update_visible_inline_completion(window, cx);
6577 if self.active_inline_completion.is_none() {
6578 self.refresh_inline_completion(true, true, window, cx);
6579 }
6580
6581 cx.notify();
6582 }
6583 }
6584
6585 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6586 }
6587
6588 pub fn accept_partial_inline_completion(
6589 &mut self,
6590 _: &AcceptPartialEditPrediction,
6591 window: &mut Window,
6592 cx: &mut Context<Self>,
6593 ) {
6594 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6595 return;
6596 };
6597 if self.selections.count() != 1 {
6598 return;
6599 }
6600
6601 self.report_inline_completion_event(
6602 active_inline_completion.completion_id.clone(),
6603 true,
6604 cx,
6605 );
6606
6607 match &active_inline_completion.completion {
6608 InlineCompletion::Move { target, .. } => {
6609 let target = *target;
6610 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6611 selections.select_anchor_ranges([target..target]);
6612 });
6613 }
6614 InlineCompletion::Edit { edits, .. } => {
6615 // Find an insertion that starts at the cursor position.
6616 let snapshot = self.buffer.read(cx).snapshot(cx);
6617 let cursor_offset = self.selections.newest::<usize>(cx).head();
6618 let insertion = edits.iter().find_map(|(range, text)| {
6619 let range = range.to_offset(&snapshot);
6620 if range.is_empty() && range.start == cursor_offset {
6621 Some(text)
6622 } else {
6623 None
6624 }
6625 });
6626
6627 if let Some(text) = insertion {
6628 let mut partial_completion = text
6629 .chars()
6630 .by_ref()
6631 .take_while(|c| c.is_alphabetic())
6632 .collect::<String>();
6633 if partial_completion.is_empty() {
6634 partial_completion = text
6635 .chars()
6636 .by_ref()
6637 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6638 .collect::<String>();
6639 }
6640
6641 cx.emit(EditorEvent::InputHandled {
6642 utf16_range_to_replace: None,
6643 text: partial_completion.clone().into(),
6644 });
6645
6646 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6647
6648 self.refresh_inline_completion(true, true, window, cx);
6649 cx.notify();
6650 } else {
6651 self.accept_edit_prediction(&Default::default(), window, cx);
6652 }
6653 }
6654 }
6655 }
6656
6657 fn discard_inline_completion(
6658 &mut self,
6659 should_report_inline_completion_event: bool,
6660 cx: &mut Context<Self>,
6661 ) -> bool {
6662 if should_report_inline_completion_event {
6663 let completion_id = self
6664 .active_inline_completion
6665 .as_ref()
6666 .and_then(|active_completion| active_completion.completion_id.clone());
6667
6668 self.report_inline_completion_event(completion_id, false, cx);
6669 }
6670
6671 if let Some(provider) = self.edit_prediction_provider() {
6672 provider.discard(cx);
6673 }
6674
6675 self.take_active_inline_completion(cx)
6676 }
6677
6678 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6679 let Some(provider) = self.edit_prediction_provider() else {
6680 return;
6681 };
6682
6683 let Some((_, buffer, _)) = self
6684 .buffer
6685 .read(cx)
6686 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6687 else {
6688 return;
6689 };
6690
6691 let extension = buffer
6692 .read(cx)
6693 .file()
6694 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6695
6696 let event_type = match accepted {
6697 true => "Edit Prediction Accepted",
6698 false => "Edit Prediction Discarded",
6699 };
6700 telemetry::event!(
6701 event_type,
6702 provider = provider.name(),
6703 prediction_id = id,
6704 suggestion_accepted = accepted,
6705 file_extension = extension,
6706 );
6707 }
6708
6709 pub fn has_active_inline_completion(&self) -> bool {
6710 self.active_inline_completion.is_some()
6711 }
6712
6713 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6714 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6715 return false;
6716 };
6717
6718 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6719 self.clear_highlights::<InlineCompletionHighlight>(cx);
6720 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6721 true
6722 }
6723
6724 /// Returns true when we're displaying the edit prediction popover below the cursor
6725 /// like we are not previewing and the LSP autocomplete menu is visible
6726 /// or we are in `when_holding_modifier` mode.
6727 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6728 if self.edit_prediction_preview_is_active()
6729 || !self.show_edit_predictions_in_menu()
6730 || !self.edit_predictions_enabled()
6731 {
6732 return false;
6733 }
6734
6735 if self.has_visible_completions_menu() {
6736 return true;
6737 }
6738
6739 has_completion && self.edit_prediction_requires_modifier()
6740 }
6741
6742 fn handle_modifiers_changed(
6743 &mut self,
6744 modifiers: Modifiers,
6745 position_map: &PositionMap,
6746 window: &mut Window,
6747 cx: &mut Context<Self>,
6748 ) {
6749 if self.show_edit_predictions_in_menu() {
6750 self.update_edit_prediction_preview(&modifiers, window, cx);
6751 }
6752
6753 self.update_selection_mode(&modifiers, position_map, window, cx);
6754
6755 let mouse_position = window.mouse_position();
6756 if !position_map.text_hitbox.is_hovered(window) {
6757 return;
6758 }
6759
6760 self.update_hovered_link(
6761 position_map.point_for_position(mouse_position),
6762 &position_map.snapshot,
6763 modifiers,
6764 window,
6765 cx,
6766 )
6767 }
6768
6769 fn update_selection_mode(
6770 &mut self,
6771 modifiers: &Modifiers,
6772 position_map: &PositionMap,
6773 window: &mut Window,
6774 cx: &mut Context<Self>,
6775 ) {
6776 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6777 return;
6778 }
6779
6780 let mouse_position = window.mouse_position();
6781 let point_for_position = position_map.point_for_position(mouse_position);
6782 let position = point_for_position.previous_valid;
6783
6784 self.select(
6785 SelectPhase::BeginColumnar {
6786 position,
6787 reset: false,
6788 goal_column: point_for_position.exact_unclipped.column(),
6789 },
6790 window,
6791 cx,
6792 );
6793 }
6794
6795 fn update_edit_prediction_preview(
6796 &mut self,
6797 modifiers: &Modifiers,
6798 window: &mut Window,
6799 cx: &mut Context<Self>,
6800 ) {
6801 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6802 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6803 return;
6804 };
6805
6806 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6807 if matches!(
6808 self.edit_prediction_preview,
6809 EditPredictionPreview::Inactive { .. }
6810 ) {
6811 self.edit_prediction_preview = EditPredictionPreview::Active {
6812 previous_scroll_position: None,
6813 since: Instant::now(),
6814 };
6815
6816 self.update_visible_inline_completion(window, cx);
6817 cx.notify();
6818 }
6819 } else if let EditPredictionPreview::Active {
6820 previous_scroll_position,
6821 since,
6822 } = self.edit_prediction_preview
6823 {
6824 if let (Some(previous_scroll_position), Some(position_map)) =
6825 (previous_scroll_position, self.last_position_map.as_ref())
6826 {
6827 self.set_scroll_position(
6828 previous_scroll_position
6829 .scroll_position(&position_map.snapshot.display_snapshot),
6830 window,
6831 cx,
6832 );
6833 }
6834
6835 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6836 released_too_fast: since.elapsed() < Duration::from_millis(200),
6837 };
6838 self.clear_row_highlights::<EditPredictionPreview>();
6839 self.update_visible_inline_completion(window, cx);
6840 cx.notify();
6841 }
6842 }
6843
6844 fn update_visible_inline_completion(
6845 &mut self,
6846 _window: &mut Window,
6847 cx: &mut Context<Self>,
6848 ) -> Option<()> {
6849 let selection = self.selections.newest_anchor();
6850 let cursor = selection.head();
6851 let multibuffer = self.buffer.read(cx).snapshot(cx);
6852 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6853 let excerpt_id = cursor.excerpt_id;
6854
6855 let show_in_menu = self.show_edit_predictions_in_menu();
6856 let completions_menu_has_precedence = !show_in_menu
6857 && (self.context_menu.borrow().is_some()
6858 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6859
6860 if completions_menu_has_precedence
6861 || !offset_selection.is_empty()
6862 || self
6863 .active_inline_completion
6864 .as_ref()
6865 .map_or(false, |completion| {
6866 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6867 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6868 !invalidation_range.contains(&offset_selection.head())
6869 })
6870 {
6871 self.discard_inline_completion(false, cx);
6872 return None;
6873 }
6874
6875 self.take_active_inline_completion(cx);
6876 let Some(provider) = self.edit_prediction_provider() else {
6877 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6878 return None;
6879 };
6880
6881 let (buffer, cursor_buffer_position) =
6882 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6883
6884 self.edit_prediction_settings =
6885 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6886
6887 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6888
6889 if self.edit_prediction_indent_conflict {
6890 let cursor_point = cursor.to_point(&multibuffer);
6891
6892 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6893
6894 if let Some((_, indent)) = indents.iter().next() {
6895 if indent.len == cursor_point.column {
6896 self.edit_prediction_indent_conflict = false;
6897 }
6898 }
6899 }
6900
6901 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6902 let edits = inline_completion
6903 .edits
6904 .into_iter()
6905 .flat_map(|(range, new_text)| {
6906 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6907 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6908 Some((start..end, new_text))
6909 })
6910 .collect::<Vec<_>>();
6911 if edits.is_empty() {
6912 return None;
6913 }
6914
6915 let first_edit_start = edits.first().unwrap().0.start;
6916 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6917 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6918
6919 let last_edit_end = edits.last().unwrap().0.end;
6920 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6921 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6922
6923 let cursor_row = cursor.to_point(&multibuffer).row;
6924
6925 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6926
6927 let mut inlay_ids = Vec::new();
6928 let invalidation_row_range;
6929 let move_invalidation_row_range = if cursor_row < edit_start_row {
6930 Some(cursor_row..edit_end_row)
6931 } else if cursor_row > edit_end_row {
6932 Some(edit_start_row..cursor_row)
6933 } else {
6934 None
6935 };
6936 let is_move =
6937 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6938 let completion = if is_move {
6939 invalidation_row_range =
6940 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6941 let target = first_edit_start;
6942 InlineCompletion::Move { target, snapshot }
6943 } else {
6944 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6945 && !self.inline_completions_hidden_for_vim_mode;
6946
6947 if show_completions_in_buffer {
6948 if edits
6949 .iter()
6950 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6951 {
6952 let mut inlays = Vec::new();
6953 for (range, new_text) in &edits {
6954 let inlay = Inlay::inline_completion(
6955 post_inc(&mut self.next_inlay_id),
6956 range.start,
6957 new_text.as_str(),
6958 );
6959 inlay_ids.push(inlay.id);
6960 inlays.push(inlay);
6961 }
6962
6963 self.splice_inlays(&[], inlays, cx);
6964 } else {
6965 let background_color = cx.theme().status().deleted_background;
6966 self.highlight_text::<InlineCompletionHighlight>(
6967 edits.iter().map(|(range, _)| range.clone()).collect(),
6968 HighlightStyle {
6969 background_color: Some(background_color),
6970 ..Default::default()
6971 },
6972 cx,
6973 );
6974 }
6975 }
6976
6977 invalidation_row_range = edit_start_row..edit_end_row;
6978
6979 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6980 if provider.show_tab_accept_marker() {
6981 EditDisplayMode::TabAccept
6982 } else {
6983 EditDisplayMode::Inline
6984 }
6985 } else {
6986 EditDisplayMode::DiffPopover
6987 };
6988
6989 InlineCompletion::Edit {
6990 edits,
6991 edit_preview: inline_completion.edit_preview,
6992 display_mode,
6993 snapshot,
6994 }
6995 };
6996
6997 let invalidation_range = multibuffer
6998 .anchor_before(Point::new(invalidation_row_range.start, 0))
6999 ..multibuffer.anchor_after(Point::new(
7000 invalidation_row_range.end,
7001 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7002 ));
7003
7004 self.stale_inline_completion_in_menu = None;
7005 self.active_inline_completion = Some(InlineCompletionState {
7006 inlay_ids,
7007 completion,
7008 completion_id: inline_completion.id,
7009 invalidation_range,
7010 });
7011
7012 cx.notify();
7013
7014 Some(())
7015 }
7016
7017 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7018 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7019 }
7020
7021 fn clear_tasks(&mut self) {
7022 self.tasks.clear()
7023 }
7024
7025 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7026 if self.tasks.insert(key, value).is_some() {
7027 // This case should hopefully be rare, but just in case...
7028 log::error!(
7029 "multiple different run targets found on a single line, only the last target will be rendered"
7030 )
7031 }
7032 }
7033
7034 /// Get all display points of breakpoints that will be rendered within editor
7035 ///
7036 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7037 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7038 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7039 fn active_breakpoints(
7040 &self,
7041 range: Range<DisplayRow>,
7042 window: &mut Window,
7043 cx: &mut Context<Self>,
7044 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7045 let mut breakpoint_display_points = HashMap::default();
7046
7047 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7048 return breakpoint_display_points;
7049 };
7050
7051 let snapshot = self.snapshot(window, cx);
7052
7053 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7054 let Some(project) = self.project.as_ref() else {
7055 return breakpoint_display_points;
7056 };
7057
7058 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7059 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7060
7061 for (buffer_snapshot, range, excerpt_id) in
7062 multi_buffer_snapshot.range_to_buffer_ranges(range)
7063 {
7064 let Some(buffer) = project.read_with(cx, |this, cx| {
7065 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7066 }) else {
7067 continue;
7068 };
7069 let breakpoints = breakpoint_store.read(cx).breakpoints(
7070 &buffer,
7071 Some(
7072 buffer_snapshot.anchor_before(range.start)
7073 ..buffer_snapshot.anchor_after(range.end),
7074 ),
7075 buffer_snapshot,
7076 cx,
7077 );
7078 for (breakpoint, state) in breakpoints {
7079 let multi_buffer_anchor =
7080 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7081 let position = multi_buffer_anchor
7082 .to_point(&multi_buffer_snapshot)
7083 .to_display_point(&snapshot);
7084
7085 breakpoint_display_points.insert(
7086 position.row(),
7087 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7088 );
7089 }
7090 }
7091
7092 breakpoint_display_points
7093 }
7094
7095 fn breakpoint_context_menu(
7096 &self,
7097 anchor: Anchor,
7098 window: &mut Window,
7099 cx: &mut Context<Self>,
7100 ) -> Entity<ui::ContextMenu> {
7101 let weak_editor = cx.weak_entity();
7102 let focus_handle = self.focus_handle(cx);
7103
7104 let row = self
7105 .buffer
7106 .read(cx)
7107 .snapshot(cx)
7108 .summary_for_anchor::<Point>(&anchor)
7109 .row;
7110
7111 let breakpoint = self
7112 .breakpoint_at_row(row, window, cx)
7113 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7114
7115 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7116 "Edit Log Breakpoint"
7117 } else {
7118 "Set Log Breakpoint"
7119 };
7120
7121 let condition_breakpoint_msg = if breakpoint
7122 .as_ref()
7123 .is_some_and(|bp| bp.1.condition.is_some())
7124 {
7125 "Edit Condition Breakpoint"
7126 } else {
7127 "Set Condition Breakpoint"
7128 };
7129
7130 let hit_condition_breakpoint_msg = if breakpoint
7131 .as_ref()
7132 .is_some_and(|bp| bp.1.hit_condition.is_some())
7133 {
7134 "Edit Hit Condition Breakpoint"
7135 } else {
7136 "Set Hit Condition Breakpoint"
7137 };
7138
7139 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7140 "Unset Breakpoint"
7141 } else {
7142 "Set Breakpoint"
7143 };
7144
7145 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7146 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7147
7148 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7149 BreakpointState::Enabled => Some("Disable"),
7150 BreakpointState::Disabled => Some("Enable"),
7151 });
7152
7153 let (anchor, breakpoint) =
7154 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7155
7156 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7157 menu.on_blur_subscription(Subscription::new(|| {}))
7158 .context(focus_handle)
7159 .when(run_to_cursor, |this| {
7160 let weak_editor = weak_editor.clone();
7161 this.entry("Run to cursor", None, move |window, cx| {
7162 weak_editor
7163 .update(cx, |editor, cx| {
7164 editor.change_selections(None, window, cx, |s| {
7165 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7166 });
7167 })
7168 .ok();
7169
7170 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7171 })
7172 .separator()
7173 })
7174 .when_some(toggle_state_msg, |this, msg| {
7175 this.entry(msg, None, {
7176 let weak_editor = weak_editor.clone();
7177 let breakpoint = breakpoint.clone();
7178 move |_window, cx| {
7179 weak_editor
7180 .update(cx, |this, cx| {
7181 this.edit_breakpoint_at_anchor(
7182 anchor,
7183 breakpoint.as_ref().clone(),
7184 BreakpointEditAction::InvertState,
7185 cx,
7186 );
7187 })
7188 .log_err();
7189 }
7190 })
7191 })
7192 .entry(set_breakpoint_msg, None, {
7193 let weak_editor = weak_editor.clone();
7194 let breakpoint = breakpoint.clone();
7195 move |_window, cx| {
7196 weak_editor
7197 .update(cx, |this, cx| {
7198 this.edit_breakpoint_at_anchor(
7199 anchor,
7200 breakpoint.as_ref().clone(),
7201 BreakpointEditAction::Toggle,
7202 cx,
7203 );
7204 })
7205 .log_err();
7206 }
7207 })
7208 .entry(log_breakpoint_msg, None, {
7209 let breakpoint = breakpoint.clone();
7210 let weak_editor = weak_editor.clone();
7211 move |window, cx| {
7212 weak_editor
7213 .update(cx, |this, cx| {
7214 this.add_edit_breakpoint_block(
7215 anchor,
7216 breakpoint.as_ref(),
7217 BreakpointPromptEditAction::Log,
7218 window,
7219 cx,
7220 );
7221 })
7222 .log_err();
7223 }
7224 })
7225 .entry(condition_breakpoint_msg, None, {
7226 let breakpoint = breakpoint.clone();
7227 let weak_editor = weak_editor.clone();
7228 move |window, cx| {
7229 weak_editor
7230 .update(cx, |this, cx| {
7231 this.add_edit_breakpoint_block(
7232 anchor,
7233 breakpoint.as_ref(),
7234 BreakpointPromptEditAction::Condition,
7235 window,
7236 cx,
7237 );
7238 })
7239 .log_err();
7240 }
7241 })
7242 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7243 weak_editor
7244 .update(cx, |this, cx| {
7245 this.add_edit_breakpoint_block(
7246 anchor,
7247 breakpoint.as_ref(),
7248 BreakpointPromptEditAction::HitCondition,
7249 window,
7250 cx,
7251 );
7252 })
7253 .log_err();
7254 })
7255 })
7256 }
7257
7258 fn render_breakpoint(
7259 &self,
7260 position: Anchor,
7261 row: DisplayRow,
7262 breakpoint: &Breakpoint,
7263 state: Option<BreakpointSessionState>,
7264 cx: &mut Context<Self>,
7265 ) -> IconButton {
7266 let is_rejected = state.is_some_and(|s| !s.verified);
7267 // Is it a breakpoint that shows up when hovering over gutter?
7268 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7269 (false, false),
7270 |PhantomBreakpointIndicator {
7271 is_active,
7272 display_row,
7273 collides_with_existing_breakpoint,
7274 }| {
7275 (
7276 is_active && display_row == row,
7277 collides_with_existing_breakpoint,
7278 )
7279 },
7280 );
7281
7282 let (color, icon) = {
7283 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7284 (false, false) => ui::IconName::DebugBreakpoint,
7285 (true, false) => ui::IconName::DebugLogBreakpoint,
7286 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7287 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7288 };
7289
7290 let color = if is_phantom {
7291 Color::Hint
7292 } else if is_rejected {
7293 Color::Disabled
7294 } else {
7295 Color::Debugger
7296 };
7297
7298 (color, icon)
7299 };
7300
7301 let breakpoint = Arc::from(breakpoint.clone());
7302
7303 let alt_as_text = gpui::Keystroke {
7304 modifiers: Modifiers::secondary_key(),
7305 ..Default::default()
7306 };
7307 let primary_action_text = if breakpoint.is_disabled() {
7308 "Enable breakpoint"
7309 } else if is_phantom && !collides_with_existing {
7310 "Set breakpoint"
7311 } else {
7312 "Unset breakpoint"
7313 };
7314 let focus_handle = self.focus_handle.clone();
7315
7316 let meta = if is_rejected {
7317 SharedString::from("No executable code is associated with this line.")
7318 } else if collides_with_existing && !breakpoint.is_disabled() {
7319 SharedString::from(format!(
7320 "{alt_as_text}-click to disable,\nright-click for more options."
7321 ))
7322 } else {
7323 SharedString::from("Right-click for more options.")
7324 };
7325 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7326 .icon_size(IconSize::XSmall)
7327 .size(ui::ButtonSize::None)
7328 .when(is_rejected, |this| {
7329 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7330 })
7331 .icon_color(color)
7332 .style(ButtonStyle::Transparent)
7333 .on_click(cx.listener({
7334 let breakpoint = breakpoint.clone();
7335
7336 move |editor, event: &ClickEvent, window, cx| {
7337 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7338 BreakpointEditAction::InvertState
7339 } else {
7340 BreakpointEditAction::Toggle
7341 };
7342
7343 window.focus(&editor.focus_handle(cx));
7344 editor.edit_breakpoint_at_anchor(
7345 position,
7346 breakpoint.as_ref().clone(),
7347 edit_action,
7348 cx,
7349 );
7350 }
7351 }))
7352 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7353 editor.set_breakpoint_context_menu(
7354 row,
7355 Some(position),
7356 event.down.position,
7357 window,
7358 cx,
7359 );
7360 }))
7361 .tooltip(move |window, cx| {
7362 Tooltip::with_meta_in(
7363 primary_action_text,
7364 Some(&ToggleBreakpoint),
7365 meta.clone(),
7366 &focus_handle,
7367 window,
7368 cx,
7369 )
7370 })
7371 }
7372
7373 fn build_tasks_context(
7374 project: &Entity<Project>,
7375 buffer: &Entity<Buffer>,
7376 buffer_row: u32,
7377 tasks: &Arc<RunnableTasks>,
7378 cx: &mut Context<Self>,
7379 ) -> Task<Option<task::TaskContext>> {
7380 let position = Point::new(buffer_row, tasks.column);
7381 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7382 let location = Location {
7383 buffer: buffer.clone(),
7384 range: range_start..range_start,
7385 };
7386 // Fill in the environmental variables from the tree-sitter captures
7387 let mut captured_task_variables = TaskVariables::default();
7388 for (capture_name, value) in tasks.extra_variables.clone() {
7389 captured_task_variables.insert(
7390 task::VariableName::Custom(capture_name.into()),
7391 value.clone(),
7392 );
7393 }
7394 project.update(cx, |project, cx| {
7395 project.task_store().update(cx, |task_store, cx| {
7396 task_store.task_context_for_location(captured_task_variables, location, cx)
7397 })
7398 })
7399 }
7400
7401 pub fn spawn_nearest_task(
7402 &mut self,
7403 action: &SpawnNearestTask,
7404 window: &mut Window,
7405 cx: &mut Context<Self>,
7406 ) {
7407 let Some((workspace, _)) = self.workspace.clone() else {
7408 return;
7409 };
7410 let Some(project) = self.project.clone() else {
7411 return;
7412 };
7413
7414 // Try to find a closest, enclosing node using tree-sitter that has a
7415 // task
7416 let Some((buffer, buffer_row, tasks)) = self
7417 .find_enclosing_node_task(cx)
7418 // Or find the task that's closest in row-distance.
7419 .or_else(|| self.find_closest_task(cx))
7420 else {
7421 return;
7422 };
7423
7424 let reveal_strategy = action.reveal;
7425 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7426 cx.spawn_in(window, async move |_, cx| {
7427 let context = task_context.await?;
7428 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7429
7430 let resolved = &mut resolved_task.resolved;
7431 resolved.reveal = reveal_strategy;
7432
7433 workspace
7434 .update_in(cx, |workspace, window, cx| {
7435 workspace.schedule_resolved_task(
7436 task_source_kind,
7437 resolved_task,
7438 false,
7439 window,
7440 cx,
7441 );
7442 })
7443 .ok()
7444 })
7445 .detach();
7446 }
7447
7448 fn find_closest_task(
7449 &mut self,
7450 cx: &mut Context<Self>,
7451 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7452 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7453
7454 let ((buffer_id, row), tasks) = self
7455 .tasks
7456 .iter()
7457 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7458
7459 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7460 let tasks = Arc::new(tasks.to_owned());
7461 Some((buffer, *row, tasks))
7462 }
7463
7464 fn find_enclosing_node_task(
7465 &mut self,
7466 cx: &mut Context<Self>,
7467 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7468 let snapshot = self.buffer.read(cx).snapshot(cx);
7469 let offset = self.selections.newest::<usize>(cx).head();
7470 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7471 let buffer_id = excerpt.buffer().remote_id();
7472
7473 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7474 let mut cursor = layer.node().walk();
7475
7476 while cursor.goto_first_child_for_byte(offset).is_some() {
7477 if cursor.node().end_byte() == offset {
7478 cursor.goto_next_sibling();
7479 }
7480 }
7481
7482 // Ascend to the smallest ancestor that contains the range and has a task.
7483 loop {
7484 let node = cursor.node();
7485 let node_range = node.byte_range();
7486 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7487
7488 // Check if this node contains our offset
7489 if node_range.start <= offset && node_range.end >= offset {
7490 // If it contains offset, check for task
7491 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7492 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7493 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7494 }
7495 }
7496
7497 if !cursor.goto_parent() {
7498 break;
7499 }
7500 }
7501 None
7502 }
7503
7504 fn render_run_indicator(
7505 &self,
7506 _style: &EditorStyle,
7507 is_active: bool,
7508 row: DisplayRow,
7509 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7510 cx: &mut Context<Self>,
7511 ) -> IconButton {
7512 let color = Color::Muted;
7513 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7514
7515 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7516 .shape(ui::IconButtonShape::Square)
7517 .icon_size(IconSize::XSmall)
7518 .icon_color(color)
7519 .toggle_state(is_active)
7520 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7521 let quick_launch = e.down.button == MouseButton::Left;
7522 window.focus(&editor.focus_handle(cx));
7523 editor.toggle_code_actions(
7524 &ToggleCodeActions {
7525 deployed_from: Some(CodeActionSource::Indicator(row)),
7526 quick_launch,
7527 },
7528 window,
7529 cx,
7530 );
7531 }))
7532 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7533 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7534 }))
7535 }
7536
7537 pub fn context_menu_visible(&self) -> bool {
7538 !self.edit_prediction_preview_is_active()
7539 && self
7540 .context_menu
7541 .borrow()
7542 .as_ref()
7543 .map_or(false, |menu| menu.visible())
7544 }
7545
7546 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7547 self.context_menu
7548 .borrow()
7549 .as_ref()
7550 .map(|menu| menu.origin())
7551 }
7552
7553 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7554 self.context_menu_options = Some(options);
7555 }
7556
7557 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7558 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7559
7560 fn render_edit_prediction_popover(
7561 &mut self,
7562 text_bounds: &Bounds<Pixels>,
7563 content_origin: gpui::Point<Pixels>,
7564 right_margin: Pixels,
7565 editor_snapshot: &EditorSnapshot,
7566 visible_row_range: Range<DisplayRow>,
7567 scroll_top: f32,
7568 scroll_bottom: f32,
7569 line_layouts: &[LineWithInvisibles],
7570 line_height: Pixels,
7571 scroll_pixel_position: gpui::Point<Pixels>,
7572 newest_selection_head: Option<DisplayPoint>,
7573 editor_width: Pixels,
7574 style: &EditorStyle,
7575 window: &mut Window,
7576 cx: &mut App,
7577 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7578 if self.mode().is_minimap() {
7579 return None;
7580 }
7581 let active_inline_completion = self.active_inline_completion.as_ref()?;
7582
7583 if self.edit_prediction_visible_in_cursor_popover(true) {
7584 return None;
7585 }
7586
7587 match &active_inline_completion.completion {
7588 InlineCompletion::Move { target, .. } => {
7589 let target_display_point = target.to_display_point(editor_snapshot);
7590
7591 if self.edit_prediction_requires_modifier() {
7592 if !self.edit_prediction_preview_is_active() {
7593 return None;
7594 }
7595
7596 self.render_edit_prediction_modifier_jump_popover(
7597 text_bounds,
7598 content_origin,
7599 visible_row_range,
7600 line_layouts,
7601 line_height,
7602 scroll_pixel_position,
7603 newest_selection_head,
7604 target_display_point,
7605 window,
7606 cx,
7607 )
7608 } else {
7609 self.render_edit_prediction_eager_jump_popover(
7610 text_bounds,
7611 content_origin,
7612 editor_snapshot,
7613 visible_row_range,
7614 scroll_top,
7615 scroll_bottom,
7616 line_height,
7617 scroll_pixel_position,
7618 target_display_point,
7619 editor_width,
7620 window,
7621 cx,
7622 )
7623 }
7624 }
7625 InlineCompletion::Edit {
7626 display_mode: EditDisplayMode::Inline,
7627 ..
7628 } => None,
7629 InlineCompletion::Edit {
7630 display_mode: EditDisplayMode::TabAccept,
7631 edits,
7632 ..
7633 } => {
7634 let range = &edits.first()?.0;
7635 let target_display_point = range.end.to_display_point(editor_snapshot);
7636
7637 self.render_edit_prediction_end_of_line_popover(
7638 "Accept",
7639 editor_snapshot,
7640 visible_row_range,
7641 target_display_point,
7642 line_height,
7643 scroll_pixel_position,
7644 content_origin,
7645 editor_width,
7646 window,
7647 cx,
7648 )
7649 }
7650 InlineCompletion::Edit {
7651 edits,
7652 edit_preview,
7653 display_mode: EditDisplayMode::DiffPopover,
7654 snapshot,
7655 } => self.render_edit_prediction_diff_popover(
7656 text_bounds,
7657 content_origin,
7658 right_margin,
7659 editor_snapshot,
7660 visible_row_range,
7661 line_layouts,
7662 line_height,
7663 scroll_pixel_position,
7664 newest_selection_head,
7665 editor_width,
7666 style,
7667 edits,
7668 edit_preview,
7669 snapshot,
7670 window,
7671 cx,
7672 ),
7673 }
7674 }
7675
7676 fn render_edit_prediction_modifier_jump_popover(
7677 &mut self,
7678 text_bounds: &Bounds<Pixels>,
7679 content_origin: gpui::Point<Pixels>,
7680 visible_row_range: Range<DisplayRow>,
7681 line_layouts: &[LineWithInvisibles],
7682 line_height: Pixels,
7683 scroll_pixel_position: gpui::Point<Pixels>,
7684 newest_selection_head: Option<DisplayPoint>,
7685 target_display_point: DisplayPoint,
7686 window: &mut Window,
7687 cx: &mut App,
7688 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7689 let scrolled_content_origin =
7690 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7691
7692 const SCROLL_PADDING_Y: Pixels = px(12.);
7693
7694 if target_display_point.row() < visible_row_range.start {
7695 return self.render_edit_prediction_scroll_popover(
7696 |_| SCROLL_PADDING_Y,
7697 IconName::ArrowUp,
7698 visible_row_range,
7699 line_layouts,
7700 newest_selection_head,
7701 scrolled_content_origin,
7702 window,
7703 cx,
7704 );
7705 } else if target_display_point.row() >= visible_row_range.end {
7706 return self.render_edit_prediction_scroll_popover(
7707 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7708 IconName::ArrowDown,
7709 visible_row_range,
7710 line_layouts,
7711 newest_selection_head,
7712 scrolled_content_origin,
7713 window,
7714 cx,
7715 );
7716 }
7717
7718 const POLE_WIDTH: Pixels = px(2.);
7719
7720 let line_layout =
7721 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7722 let target_column = target_display_point.column() as usize;
7723
7724 let target_x = line_layout.x_for_index(target_column);
7725 let target_y =
7726 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7727
7728 let flag_on_right = target_x < text_bounds.size.width / 2.;
7729
7730 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7731 border_color.l += 0.001;
7732
7733 let mut element = v_flex()
7734 .items_end()
7735 .when(flag_on_right, |el| el.items_start())
7736 .child(if flag_on_right {
7737 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7738 .rounded_bl(px(0.))
7739 .rounded_tl(px(0.))
7740 .border_l_2()
7741 .border_color(border_color)
7742 } else {
7743 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7744 .rounded_br(px(0.))
7745 .rounded_tr(px(0.))
7746 .border_r_2()
7747 .border_color(border_color)
7748 })
7749 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7750 .into_any();
7751
7752 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7753
7754 let mut origin = scrolled_content_origin + point(target_x, target_y)
7755 - point(
7756 if flag_on_right {
7757 POLE_WIDTH
7758 } else {
7759 size.width - POLE_WIDTH
7760 },
7761 size.height - line_height,
7762 );
7763
7764 origin.x = origin.x.max(content_origin.x);
7765
7766 element.prepaint_at(origin, window, cx);
7767
7768 Some((element, origin))
7769 }
7770
7771 fn render_edit_prediction_scroll_popover(
7772 &mut self,
7773 to_y: impl Fn(Size<Pixels>) -> Pixels,
7774 scroll_icon: IconName,
7775 visible_row_range: Range<DisplayRow>,
7776 line_layouts: &[LineWithInvisibles],
7777 newest_selection_head: Option<DisplayPoint>,
7778 scrolled_content_origin: gpui::Point<Pixels>,
7779 window: &mut Window,
7780 cx: &mut App,
7781 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7782 let mut element = self
7783 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7784 .into_any();
7785
7786 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7787
7788 let cursor = newest_selection_head?;
7789 let cursor_row_layout =
7790 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7791 let cursor_column = cursor.column() as usize;
7792
7793 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7794
7795 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7796
7797 element.prepaint_at(origin, window, cx);
7798 Some((element, origin))
7799 }
7800
7801 fn render_edit_prediction_eager_jump_popover(
7802 &mut self,
7803 text_bounds: &Bounds<Pixels>,
7804 content_origin: gpui::Point<Pixels>,
7805 editor_snapshot: &EditorSnapshot,
7806 visible_row_range: Range<DisplayRow>,
7807 scroll_top: f32,
7808 scroll_bottom: f32,
7809 line_height: Pixels,
7810 scroll_pixel_position: gpui::Point<Pixels>,
7811 target_display_point: DisplayPoint,
7812 editor_width: Pixels,
7813 window: &mut Window,
7814 cx: &mut App,
7815 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7816 if target_display_point.row().as_f32() < scroll_top {
7817 let mut element = self
7818 .render_edit_prediction_line_popover(
7819 "Jump to Edit",
7820 Some(IconName::ArrowUp),
7821 window,
7822 cx,
7823 )?
7824 .into_any();
7825
7826 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7827 let offset = point(
7828 (text_bounds.size.width - size.width) / 2.,
7829 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7830 );
7831
7832 let origin = text_bounds.origin + offset;
7833 element.prepaint_at(origin, window, cx);
7834 Some((element, origin))
7835 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7836 let mut element = self
7837 .render_edit_prediction_line_popover(
7838 "Jump to Edit",
7839 Some(IconName::ArrowDown),
7840 window,
7841 cx,
7842 )?
7843 .into_any();
7844
7845 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7846 let offset = point(
7847 (text_bounds.size.width - size.width) / 2.,
7848 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7849 );
7850
7851 let origin = text_bounds.origin + offset;
7852 element.prepaint_at(origin, window, cx);
7853 Some((element, origin))
7854 } else {
7855 self.render_edit_prediction_end_of_line_popover(
7856 "Jump to Edit",
7857 editor_snapshot,
7858 visible_row_range,
7859 target_display_point,
7860 line_height,
7861 scroll_pixel_position,
7862 content_origin,
7863 editor_width,
7864 window,
7865 cx,
7866 )
7867 }
7868 }
7869
7870 fn render_edit_prediction_end_of_line_popover(
7871 self: &mut Editor,
7872 label: &'static str,
7873 editor_snapshot: &EditorSnapshot,
7874 visible_row_range: Range<DisplayRow>,
7875 target_display_point: DisplayPoint,
7876 line_height: Pixels,
7877 scroll_pixel_position: gpui::Point<Pixels>,
7878 content_origin: gpui::Point<Pixels>,
7879 editor_width: Pixels,
7880 window: &mut Window,
7881 cx: &mut App,
7882 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7883 let target_line_end = DisplayPoint::new(
7884 target_display_point.row(),
7885 editor_snapshot.line_len(target_display_point.row()),
7886 );
7887
7888 let mut element = self
7889 .render_edit_prediction_line_popover(label, None, window, cx)?
7890 .into_any();
7891
7892 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7893
7894 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7895
7896 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7897 let mut origin = start_point
7898 + line_origin
7899 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7900 origin.x = origin.x.max(content_origin.x);
7901
7902 let max_x = content_origin.x + editor_width - size.width;
7903
7904 if origin.x > max_x {
7905 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7906
7907 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7908 origin.y += offset;
7909 IconName::ArrowUp
7910 } else {
7911 origin.y -= offset;
7912 IconName::ArrowDown
7913 };
7914
7915 element = self
7916 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7917 .into_any();
7918
7919 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7920
7921 origin.x = content_origin.x + editor_width - size.width - px(2.);
7922 }
7923
7924 element.prepaint_at(origin, window, cx);
7925 Some((element, origin))
7926 }
7927
7928 fn render_edit_prediction_diff_popover(
7929 self: &Editor,
7930 text_bounds: &Bounds<Pixels>,
7931 content_origin: gpui::Point<Pixels>,
7932 right_margin: Pixels,
7933 editor_snapshot: &EditorSnapshot,
7934 visible_row_range: Range<DisplayRow>,
7935 line_layouts: &[LineWithInvisibles],
7936 line_height: Pixels,
7937 scroll_pixel_position: gpui::Point<Pixels>,
7938 newest_selection_head: Option<DisplayPoint>,
7939 editor_width: Pixels,
7940 style: &EditorStyle,
7941 edits: &Vec<(Range<Anchor>, String)>,
7942 edit_preview: &Option<language::EditPreview>,
7943 snapshot: &language::BufferSnapshot,
7944 window: &mut Window,
7945 cx: &mut App,
7946 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7947 let edit_start = edits
7948 .first()
7949 .unwrap()
7950 .0
7951 .start
7952 .to_display_point(editor_snapshot);
7953 let edit_end = edits
7954 .last()
7955 .unwrap()
7956 .0
7957 .end
7958 .to_display_point(editor_snapshot);
7959
7960 let is_visible = visible_row_range.contains(&edit_start.row())
7961 || visible_row_range.contains(&edit_end.row());
7962 if !is_visible {
7963 return None;
7964 }
7965
7966 let highlighted_edits =
7967 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7968
7969 let styled_text = highlighted_edits.to_styled_text(&style.text);
7970 let line_count = highlighted_edits.text.lines().count();
7971
7972 const BORDER_WIDTH: Pixels = px(1.);
7973
7974 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7975 let has_keybind = keybind.is_some();
7976
7977 let mut element = h_flex()
7978 .items_start()
7979 .child(
7980 h_flex()
7981 .bg(cx.theme().colors().editor_background)
7982 .border(BORDER_WIDTH)
7983 .shadow_sm()
7984 .border_color(cx.theme().colors().border)
7985 .rounded_l_lg()
7986 .when(line_count > 1, |el| el.rounded_br_lg())
7987 .pr_1()
7988 .child(styled_text),
7989 )
7990 .child(
7991 h_flex()
7992 .h(line_height + BORDER_WIDTH * 2.)
7993 .px_1p5()
7994 .gap_1()
7995 // Workaround: For some reason, there's a gap if we don't do this
7996 .ml(-BORDER_WIDTH)
7997 .shadow(vec![gpui::BoxShadow {
7998 color: gpui::black().opacity(0.05),
7999 offset: point(px(1.), px(1.)),
8000 blur_radius: px(2.),
8001 spread_radius: px(0.),
8002 }])
8003 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8004 .border(BORDER_WIDTH)
8005 .border_color(cx.theme().colors().border)
8006 .rounded_r_lg()
8007 .id("edit_prediction_diff_popover_keybind")
8008 .when(!has_keybind, |el| {
8009 let status_colors = cx.theme().status();
8010
8011 el.bg(status_colors.error_background)
8012 .border_color(status_colors.error.opacity(0.6))
8013 .child(Icon::new(IconName::Info).color(Color::Error))
8014 .cursor_default()
8015 .hoverable_tooltip(move |_window, cx| {
8016 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8017 })
8018 })
8019 .children(keybind),
8020 )
8021 .into_any();
8022
8023 let longest_row =
8024 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8025 let longest_line_width = if visible_row_range.contains(&longest_row) {
8026 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8027 } else {
8028 layout_line(
8029 longest_row,
8030 editor_snapshot,
8031 style,
8032 editor_width,
8033 |_| false,
8034 window,
8035 cx,
8036 )
8037 .width
8038 };
8039
8040 let viewport_bounds =
8041 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8042 right: -right_margin,
8043 ..Default::default()
8044 });
8045
8046 let x_after_longest =
8047 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8048 - scroll_pixel_position.x;
8049
8050 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8051
8052 // Fully visible if it can be displayed within the window (allow overlapping other
8053 // panes). However, this is only allowed if the popover starts within text_bounds.
8054 let can_position_to_the_right = x_after_longest < text_bounds.right()
8055 && x_after_longest + element_bounds.width < viewport_bounds.right();
8056
8057 let mut origin = if can_position_to_the_right {
8058 point(
8059 x_after_longest,
8060 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8061 - scroll_pixel_position.y,
8062 )
8063 } else {
8064 let cursor_row = newest_selection_head.map(|head| head.row());
8065 let above_edit = edit_start
8066 .row()
8067 .0
8068 .checked_sub(line_count as u32)
8069 .map(DisplayRow);
8070 let below_edit = Some(edit_end.row() + 1);
8071 let above_cursor =
8072 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8073 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8074
8075 // Place the edit popover adjacent to the edit if there is a location
8076 // available that is onscreen and does not obscure the cursor. Otherwise,
8077 // place it adjacent to the cursor.
8078 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8079 .into_iter()
8080 .flatten()
8081 .find(|&start_row| {
8082 let end_row = start_row + line_count as u32;
8083 visible_row_range.contains(&start_row)
8084 && visible_row_range.contains(&end_row)
8085 && cursor_row.map_or(true, |cursor_row| {
8086 !((start_row..end_row).contains(&cursor_row))
8087 })
8088 })?;
8089
8090 content_origin
8091 + point(
8092 -scroll_pixel_position.x,
8093 row_target.as_f32() * line_height - scroll_pixel_position.y,
8094 )
8095 };
8096
8097 origin.x -= BORDER_WIDTH;
8098
8099 window.defer_draw(element, origin, 1);
8100
8101 // Do not return an element, since it will already be drawn due to defer_draw.
8102 None
8103 }
8104
8105 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8106 px(30.)
8107 }
8108
8109 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8110 if self.read_only(cx) {
8111 cx.theme().players().read_only()
8112 } else {
8113 self.style.as_ref().unwrap().local_player
8114 }
8115 }
8116
8117 fn render_edit_prediction_accept_keybind(
8118 &self,
8119 window: &mut Window,
8120 cx: &App,
8121 ) -> Option<AnyElement> {
8122 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8123 let accept_keystroke = accept_binding.keystroke()?;
8124
8125 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8126
8127 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8128 Color::Accent
8129 } else {
8130 Color::Muted
8131 };
8132
8133 h_flex()
8134 .px_0p5()
8135 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8136 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8137 .text_size(TextSize::XSmall.rems(cx))
8138 .child(h_flex().children(ui::render_modifiers(
8139 &accept_keystroke.modifiers,
8140 PlatformStyle::platform(),
8141 Some(modifiers_color),
8142 Some(IconSize::XSmall.rems().into()),
8143 true,
8144 )))
8145 .when(is_platform_style_mac, |parent| {
8146 parent.child(accept_keystroke.key.clone())
8147 })
8148 .when(!is_platform_style_mac, |parent| {
8149 parent.child(
8150 Key::new(
8151 util::capitalize(&accept_keystroke.key),
8152 Some(Color::Default),
8153 )
8154 .size(Some(IconSize::XSmall.rems().into())),
8155 )
8156 })
8157 .into_any()
8158 .into()
8159 }
8160
8161 fn render_edit_prediction_line_popover(
8162 &self,
8163 label: impl Into<SharedString>,
8164 icon: Option<IconName>,
8165 window: &mut Window,
8166 cx: &App,
8167 ) -> Option<Stateful<Div>> {
8168 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8169
8170 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8171 let has_keybind = keybind.is_some();
8172
8173 let result = h_flex()
8174 .id("ep-line-popover")
8175 .py_0p5()
8176 .pl_1()
8177 .pr(padding_right)
8178 .gap_1()
8179 .rounded_md()
8180 .border_1()
8181 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8182 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8183 .shadow_sm()
8184 .when(!has_keybind, |el| {
8185 let status_colors = cx.theme().status();
8186
8187 el.bg(status_colors.error_background)
8188 .border_color(status_colors.error.opacity(0.6))
8189 .pl_2()
8190 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8191 .cursor_default()
8192 .hoverable_tooltip(move |_window, cx| {
8193 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8194 })
8195 })
8196 .children(keybind)
8197 .child(
8198 Label::new(label)
8199 .size(LabelSize::Small)
8200 .when(!has_keybind, |el| {
8201 el.color(cx.theme().status().error.into()).strikethrough()
8202 }),
8203 )
8204 .when(!has_keybind, |el| {
8205 el.child(
8206 h_flex().ml_1().child(
8207 Icon::new(IconName::Info)
8208 .size(IconSize::Small)
8209 .color(cx.theme().status().error.into()),
8210 ),
8211 )
8212 })
8213 .when_some(icon, |element, icon| {
8214 element.child(
8215 div()
8216 .mt(px(1.5))
8217 .child(Icon::new(icon).size(IconSize::Small)),
8218 )
8219 });
8220
8221 Some(result)
8222 }
8223
8224 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8225 let accent_color = cx.theme().colors().text_accent;
8226 let editor_bg_color = cx.theme().colors().editor_background;
8227 editor_bg_color.blend(accent_color.opacity(0.1))
8228 }
8229
8230 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8231 let accent_color = cx.theme().colors().text_accent;
8232 let editor_bg_color = cx.theme().colors().editor_background;
8233 editor_bg_color.blend(accent_color.opacity(0.6))
8234 }
8235
8236 fn render_edit_prediction_cursor_popover(
8237 &self,
8238 min_width: Pixels,
8239 max_width: Pixels,
8240 cursor_point: Point,
8241 style: &EditorStyle,
8242 accept_keystroke: Option<&gpui::Keystroke>,
8243 _window: &Window,
8244 cx: &mut Context<Editor>,
8245 ) -> Option<AnyElement> {
8246 let provider = self.edit_prediction_provider.as_ref()?;
8247
8248 if provider.provider.needs_terms_acceptance(cx) {
8249 return Some(
8250 h_flex()
8251 .min_w(min_width)
8252 .flex_1()
8253 .px_2()
8254 .py_1()
8255 .gap_3()
8256 .elevation_2(cx)
8257 .hover(|style| style.bg(cx.theme().colors().element_hover))
8258 .id("accept-terms")
8259 .cursor_pointer()
8260 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8261 .on_click(cx.listener(|this, _event, window, cx| {
8262 cx.stop_propagation();
8263 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8264 window.dispatch_action(
8265 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8266 cx,
8267 );
8268 }))
8269 .child(
8270 h_flex()
8271 .flex_1()
8272 .gap_2()
8273 .child(Icon::new(IconName::ZedPredict))
8274 .child(Label::new("Accept Terms of Service"))
8275 .child(div().w_full())
8276 .child(
8277 Icon::new(IconName::ArrowUpRight)
8278 .color(Color::Muted)
8279 .size(IconSize::Small),
8280 )
8281 .into_any_element(),
8282 )
8283 .into_any(),
8284 );
8285 }
8286
8287 let is_refreshing = provider.provider.is_refreshing(cx);
8288
8289 fn pending_completion_container() -> Div {
8290 h_flex()
8291 .h_full()
8292 .flex_1()
8293 .gap_2()
8294 .child(Icon::new(IconName::ZedPredict))
8295 }
8296
8297 let completion = match &self.active_inline_completion {
8298 Some(prediction) => {
8299 if !self.has_visible_completions_menu() {
8300 const RADIUS: Pixels = px(6.);
8301 const BORDER_WIDTH: Pixels = px(1.);
8302
8303 return Some(
8304 h_flex()
8305 .elevation_2(cx)
8306 .border(BORDER_WIDTH)
8307 .border_color(cx.theme().colors().border)
8308 .when(accept_keystroke.is_none(), |el| {
8309 el.border_color(cx.theme().status().error)
8310 })
8311 .rounded(RADIUS)
8312 .rounded_tl(px(0.))
8313 .overflow_hidden()
8314 .child(div().px_1p5().child(match &prediction.completion {
8315 InlineCompletion::Move { target, snapshot } => {
8316 use text::ToPoint as _;
8317 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8318 {
8319 Icon::new(IconName::ZedPredictDown)
8320 } else {
8321 Icon::new(IconName::ZedPredictUp)
8322 }
8323 }
8324 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8325 }))
8326 .child(
8327 h_flex()
8328 .gap_1()
8329 .py_1()
8330 .px_2()
8331 .rounded_r(RADIUS - BORDER_WIDTH)
8332 .border_l_1()
8333 .border_color(cx.theme().colors().border)
8334 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8335 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8336 el.child(
8337 Label::new("Hold")
8338 .size(LabelSize::Small)
8339 .when(accept_keystroke.is_none(), |el| {
8340 el.strikethrough()
8341 })
8342 .line_height_style(LineHeightStyle::UiLabel),
8343 )
8344 })
8345 .id("edit_prediction_cursor_popover_keybind")
8346 .when(accept_keystroke.is_none(), |el| {
8347 let status_colors = cx.theme().status();
8348
8349 el.bg(status_colors.error_background)
8350 .border_color(status_colors.error.opacity(0.6))
8351 .child(Icon::new(IconName::Info).color(Color::Error))
8352 .cursor_default()
8353 .hoverable_tooltip(move |_window, cx| {
8354 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8355 .into()
8356 })
8357 })
8358 .when_some(
8359 accept_keystroke.as_ref(),
8360 |el, accept_keystroke| {
8361 el.child(h_flex().children(ui::render_modifiers(
8362 &accept_keystroke.modifiers,
8363 PlatformStyle::platform(),
8364 Some(Color::Default),
8365 Some(IconSize::XSmall.rems().into()),
8366 false,
8367 )))
8368 },
8369 ),
8370 )
8371 .into_any(),
8372 );
8373 }
8374
8375 self.render_edit_prediction_cursor_popover_preview(
8376 prediction,
8377 cursor_point,
8378 style,
8379 cx,
8380 )?
8381 }
8382
8383 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8384 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8385 stale_completion,
8386 cursor_point,
8387 style,
8388 cx,
8389 )?,
8390
8391 None => {
8392 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8393 }
8394 },
8395
8396 None => pending_completion_container().child(Label::new("No Prediction")),
8397 };
8398
8399 let completion = if is_refreshing {
8400 completion
8401 .with_animation(
8402 "loading-completion",
8403 Animation::new(Duration::from_secs(2))
8404 .repeat()
8405 .with_easing(pulsating_between(0.4, 0.8)),
8406 |label, delta| label.opacity(delta),
8407 )
8408 .into_any_element()
8409 } else {
8410 completion.into_any_element()
8411 };
8412
8413 let has_completion = self.active_inline_completion.is_some();
8414
8415 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8416 Some(
8417 h_flex()
8418 .min_w(min_width)
8419 .max_w(max_width)
8420 .flex_1()
8421 .elevation_2(cx)
8422 .border_color(cx.theme().colors().border)
8423 .child(
8424 div()
8425 .flex_1()
8426 .py_1()
8427 .px_2()
8428 .overflow_hidden()
8429 .child(completion),
8430 )
8431 .when_some(accept_keystroke, |el, accept_keystroke| {
8432 if !accept_keystroke.modifiers.modified() {
8433 return el;
8434 }
8435
8436 el.child(
8437 h_flex()
8438 .h_full()
8439 .border_l_1()
8440 .rounded_r_lg()
8441 .border_color(cx.theme().colors().border)
8442 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8443 .gap_1()
8444 .py_1()
8445 .px_2()
8446 .child(
8447 h_flex()
8448 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8449 .when(is_platform_style_mac, |parent| parent.gap_1())
8450 .child(h_flex().children(ui::render_modifiers(
8451 &accept_keystroke.modifiers,
8452 PlatformStyle::platform(),
8453 Some(if !has_completion {
8454 Color::Muted
8455 } else {
8456 Color::Default
8457 }),
8458 None,
8459 false,
8460 ))),
8461 )
8462 .child(Label::new("Preview").into_any_element())
8463 .opacity(if has_completion { 1.0 } else { 0.4 }),
8464 )
8465 })
8466 .into_any(),
8467 )
8468 }
8469
8470 fn render_edit_prediction_cursor_popover_preview(
8471 &self,
8472 completion: &InlineCompletionState,
8473 cursor_point: Point,
8474 style: &EditorStyle,
8475 cx: &mut Context<Editor>,
8476 ) -> Option<Div> {
8477 use text::ToPoint as _;
8478
8479 fn render_relative_row_jump(
8480 prefix: impl Into<String>,
8481 current_row: u32,
8482 target_row: u32,
8483 ) -> Div {
8484 let (row_diff, arrow) = if target_row < current_row {
8485 (current_row - target_row, IconName::ArrowUp)
8486 } else {
8487 (target_row - current_row, IconName::ArrowDown)
8488 };
8489
8490 h_flex()
8491 .child(
8492 Label::new(format!("{}{}", prefix.into(), row_diff))
8493 .color(Color::Muted)
8494 .size(LabelSize::Small),
8495 )
8496 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8497 }
8498
8499 match &completion.completion {
8500 InlineCompletion::Move {
8501 target, snapshot, ..
8502 } => Some(
8503 h_flex()
8504 .px_2()
8505 .gap_2()
8506 .flex_1()
8507 .child(
8508 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8509 Icon::new(IconName::ZedPredictDown)
8510 } else {
8511 Icon::new(IconName::ZedPredictUp)
8512 },
8513 )
8514 .child(Label::new("Jump to Edit")),
8515 ),
8516
8517 InlineCompletion::Edit {
8518 edits,
8519 edit_preview,
8520 snapshot,
8521 display_mode: _,
8522 } => {
8523 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8524
8525 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8526 &snapshot,
8527 &edits,
8528 edit_preview.as_ref()?,
8529 true,
8530 cx,
8531 )
8532 .first_line_preview();
8533
8534 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8535 .with_default_highlights(&style.text, highlighted_edits.highlights);
8536
8537 let preview = h_flex()
8538 .gap_1()
8539 .min_w_16()
8540 .child(styled_text)
8541 .when(has_more_lines, |parent| parent.child("…"));
8542
8543 let left = if first_edit_row != cursor_point.row {
8544 render_relative_row_jump("", cursor_point.row, first_edit_row)
8545 .into_any_element()
8546 } else {
8547 Icon::new(IconName::ZedPredict).into_any_element()
8548 };
8549
8550 Some(
8551 h_flex()
8552 .h_full()
8553 .flex_1()
8554 .gap_2()
8555 .pr_1()
8556 .overflow_x_hidden()
8557 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8558 .child(left)
8559 .child(preview),
8560 )
8561 }
8562 }
8563 }
8564
8565 pub fn render_context_menu(
8566 &self,
8567 style: &EditorStyle,
8568 max_height_in_lines: u32,
8569 window: &mut Window,
8570 cx: &mut Context<Editor>,
8571 ) -> Option<AnyElement> {
8572 let menu = self.context_menu.borrow();
8573 let menu = menu.as_ref()?;
8574 if !menu.visible() {
8575 return None;
8576 };
8577 Some(menu.render(style, max_height_in_lines, window, cx))
8578 }
8579
8580 fn render_context_menu_aside(
8581 &mut self,
8582 max_size: Size<Pixels>,
8583 window: &mut Window,
8584 cx: &mut Context<Editor>,
8585 ) -> Option<AnyElement> {
8586 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8587 if menu.visible() {
8588 menu.render_aside(self, max_size, window, cx)
8589 } else {
8590 None
8591 }
8592 })
8593 }
8594
8595 fn hide_context_menu(
8596 &mut self,
8597 window: &mut Window,
8598 cx: &mut Context<Self>,
8599 ) -> Option<CodeContextMenu> {
8600 cx.notify();
8601 self.completion_tasks.clear();
8602 let context_menu = self.context_menu.borrow_mut().take();
8603 self.stale_inline_completion_in_menu.take();
8604 self.update_visible_inline_completion(window, cx);
8605 context_menu
8606 }
8607
8608 fn show_snippet_choices(
8609 &mut self,
8610 choices: &Vec<String>,
8611 selection: Range<Anchor>,
8612 cx: &mut Context<Self>,
8613 ) {
8614 if selection.start.buffer_id.is_none() {
8615 return;
8616 }
8617 let buffer_id = selection.start.buffer_id.unwrap();
8618 let buffer = self.buffer().read(cx).buffer(buffer_id);
8619 let id = post_inc(&mut self.next_completion_id);
8620 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8621
8622 if let Some(buffer) = buffer {
8623 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8624 CompletionsMenu::new_snippet_choices(
8625 id,
8626 true,
8627 choices,
8628 selection,
8629 buffer,
8630 snippet_sort_order,
8631 ),
8632 ));
8633 }
8634 }
8635
8636 pub fn insert_snippet(
8637 &mut self,
8638 insertion_ranges: &[Range<usize>],
8639 snippet: Snippet,
8640 window: &mut Window,
8641 cx: &mut Context<Self>,
8642 ) -> Result<()> {
8643 struct Tabstop<T> {
8644 is_end_tabstop: bool,
8645 ranges: Vec<Range<T>>,
8646 choices: Option<Vec<String>>,
8647 }
8648
8649 let tabstops = self.buffer.update(cx, |buffer, cx| {
8650 let snippet_text: Arc<str> = snippet.text.clone().into();
8651 let edits = insertion_ranges
8652 .iter()
8653 .cloned()
8654 .map(|range| (range, snippet_text.clone()));
8655 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8656
8657 let snapshot = &*buffer.read(cx);
8658 let snippet = &snippet;
8659 snippet
8660 .tabstops
8661 .iter()
8662 .map(|tabstop| {
8663 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8664 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8665 });
8666 let mut tabstop_ranges = tabstop
8667 .ranges
8668 .iter()
8669 .flat_map(|tabstop_range| {
8670 let mut delta = 0_isize;
8671 insertion_ranges.iter().map(move |insertion_range| {
8672 let insertion_start = insertion_range.start as isize + delta;
8673 delta +=
8674 snippet.text.len() as isize - insertion_range.len() as isize;
8675
8676 let start = ((insertion_start + tabstop_range.start) as usize)
8677 .min(snapshot.len());
8678 let end = ((insertion_start + tabstop_range.end) as usize)
8679 .min(snapshot.len());
8680 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8681 })
8682 })
8683 .collect::<Vec<_>>();
8684 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8685
8686 Tabstop {
8687 is_end_tabstop,
8688 ranges: tabstop_ranges,
8689 choices: tabstop.choices.clone(),
8690 }
8691 })
8692 .collect::<Vec<_>>()
8693 });
8694 if let Some(tabstop) = tabstops.first() {
8695 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8696 s.select_ranges(tabstop.ranges.iter().cloned());
8697 });
8698
8699 if let Some(choices) = &tabstop.choices {
8700 if let Some(selection) = tabstop.ranges.first() {
8701 self.show_snippet_choices(choices, selection.clone(), cx)
8702 }
8703 }
8704
8705 // If we're already at the last tabstop and it's at the end of the snippet,
8706 // we're done, we don't need to keep the state around.
8707 if !tabstop.is_end_tabstop {
8708 let choices = tabstops
8709 .iter()
8710 .map(|tabstop| tabstop.choices.clone())
8711 .collect();
8712
8713 let ranges = tabstops
8714 .into_iter()
8715 .map(|tabstop| tabstop.ranges)
8716 .collect::<Vec<_>>();
8717
8718 self.snippet_stack.push(SnippetState {
8719 active_index: 0,
8720 ranges,
8721 choices,
8722 });
8723 }
8724
8725 // Check whether the just-entered snippet ends with an auto-closable bracket.
8726 if self.autoclose_regions.is_empty() {
8727 let snapshot = self.buffer.read(cx).snapshot(cx);
8728 for selection in &mut self.selections.all::<Point>(cx) {
8729 let selection_head = selection.head();
8730 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8731 continue;
8732 };
8733
8734 let mut bracket_pair = None;
8735 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8736 let prev_chars = snapshot
8737 .reversed_chars_at(selection_head)
8738 .collect::<String>();
8739 for (pair, enabled) in scope.brackets() {
8740 if enabled
8741 && pair.close
8742 && prev_chars.starts_with(pair.start.as_str())
8743 && next_chars.starts_with(pair.end.as_str())
8744 {
8745 bracket_pair = Some(pair.clone());
8746 break;
8747 }
8748 }
8749 if let Some(pair) = bracket_pair {
8750 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8751 let autoclose_enabled =
8752 self.use_autoclose && snapshot_settings.use_autoclose;
8753 if autoclose_enabled {
8754 let start = snapshot.anchor_after(selection_head);
8755 let end = snapshot.anchor_after(selection_head);
8756 self.autoclose_regions.push(AutocloseRegion {
8757 selection_id: selection.id,
8758 range: start..end,
8759 pair,
8760 });
8761 }
8762 }
8763 }
8764 }
8765 }
8766 Ok(())
8767 }
8768
8769 pub fn move_to_next_snippet_tabstop(
8770 &mut self,
8771 window: &mut Window,
8772 cx: &mut Context<Self>,
8773 ) -> bool {
8774 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8775 }
8776
8777 pub fn move_to_prev_snippet_tabstop(
8778 &mut self,
8779 window: &mut Window,
8780 cx: &mut Context<Self>,
8781 ) -> bool {
8782 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8783 }
8784
8785 pub fn move_to_snippet_tabstop(
8786 &mut self,
8787 bias: Bias,
8788 window: &mut Window,
8789 cx: &mut Context<Self>,
8790 ) -> bool {
8791 if let Some(mut snippet) = self.snippet_stack.pop() {
8792 match bias {
8793 Bias::Left => {
8794 if snippet.active_index > 0 {
8795 snippet.active_index -= 1;
8796 } else {
8797 self.snippet_stack.push(snippet);
8798 return false;
8799 }
8800 }
8801 Bias::Right => {
8802 if snippet.active_index + 1 < snippet.ranges.len() {
8803 snippet.active_index += 1;
8804 } else {
8805 self.snippet_stack.push(snippet);
8806 return false;
8807 }
8808 }
8809 }
8810 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8811 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8812 s.select_anchor_ranges(current_ranges.iter().cloned())
8813 });
8814
8815 if let Some(choices) = &snippet.choices[snippet.active_index] {
8816 if let Some(selection) = current_ranges.first() {
8817 self.show_snippet_choices(&choices, selection.clone(), cx);
8818 }
8819 }
8820
8821 // If snippet state is not at the last tabstop, push it back on the stack
8822 if snippet.active_index + 1 < snippet.ranges.len() {
8823 self.snippet_stack.push(snippet);
8824 }
8825 return true;
8826 }
8827 }
8828
8829 false
8830 }
8831
8832 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8833 self.transact(window, cx, |this, window, cx| {
8834 this.select_all(&SelectAll, window, cx);
8835 this.insert("", window, cx);
8836 });
8837 }
8838
8839 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8840 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8841 self.transact(window, cx, |this, window, cx| {
8842 this.select_autoclose_pair(window, cx);
8843 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8844 if !this.linked_edit_ranges.is_empty() {
8845 let selections = this.selections.all::<MultiBufferPoint>(cx);
8846 let snapshot = this.buffer.read(cx).snapshot(cx);
8847
8848 for selection in selections.iter() {
8849 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8850 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8851 if selection_start.buffer_id != selection_end.buffer_id {
8852 continue;
8853 }
8854 if let Some(ranges) =
8855 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8856 {
8857 for (buffer, entries) in ranges {
8858 linked_ranges.entry(buffer).or_default().extend(entries);
8859 }
8860 }
8861 }
8862 }
8863
8864 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8865 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8866 for selection in &mut selections {
8867 if selection.is_empty() {
8868 let old_head = selection.head();
8869 let mut new_head =
8870 movement::left(&display_map, old_head.to_display_point(&display_map))
8871 .to_point(&display_map);
8872 if let Some((buffer, line_buffer_range)) = display_map
8873 .buffer_snapshot
8874 .buffer_line_for_row(MultiBufferRow(old_head.row))
8875 {
8876 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8877 let indent_len = match indent_size.kind {
8878 IndentKind::Space => {
8879 buffer.settings_at(line_buffer_range.start, cx).tab_size
8880 }
8881 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8882 };
8883 if old_head.column <= indent_size.len && old_head.column > 0 {
8884 let indent_len = indent_len.get();
8885 new_head = cmp::min(
8886 new_head,
8887 MultiBufferPoint::new(
8888 old_head.row,
8889 ((old_head.column - 1) / indent_len) * indent_len,
8890 ),
8891 );
8892 }
8893 }
8894
8895 selection.set_head(new_head, SelectionGoal::None);
8896 }
8897 }
8898
8899 this.signature_help_state.set_backspace_pressed(true);
8900 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8901 s.select(selections)
8902 });
8903 this.insert("", window, cx);
8904 let empty_str: Arc<str> = Arc::from("");
8905 for (buffer, edits) in linked_ranges {
8906 let snapshot = buffer.read(cx).snapshot();
8907 use text::ToPoint as TP;
8908
8909 let edits = edits
8910 .into_iter()
8911 .map(|range| {
8912 let end_point = TP::to_point(&range.end, &snapshot);
8913 let mut start_point = TP::to_point(&range.start, &snapshot);
8914
8915 if end_point == start_point {
8916 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8917 .saturating_sub(1);
8918 start_point =
8919 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8920 };
8921
8922 (start_point..end_point, empty_str.clone())
8923 })
8924 .sorted_by_key(|(range, _)| range.start)
8925 .collect::<Vec<_>>();
8926 buffer.update(cx, |this, cx| {
8927 this.edit(edits, None, cx);
8928 })
8929 }
8930 this.refresh_inline_completion(true, false, window, cx);
8931 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8932 });
8933 }
8934
8935 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8936 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8937 self.transact(window, cx, |this, window, cx| {
8938 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8939 s.move_with(|map, selection| {
8940 if selection.is_empty() {
8941 let cursor = movement::right(map, selection.head());
8942 selection.end = cursor;
8943 selection.reversed = true;
8944 selection.goal = SelectionGoal::None;
8945 }
8946 })
8947 });
8948 this.insert("", window, cx);
8949 this.refresh_inline_completion(true, false, window, cx);
8950 });
8951 }
8952
8953 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8954 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8955 if self.move_to_prev_snippet_tabstop(window, cx) {
8956 return;
8957 }
8958 self.outdent(&Outdent, window, cx);
8959 }
8960
8961 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8962 if self.move_to_next_snippet_tabstop(window, cx) {
8963 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8964 return;
8965 }
8966 if self.read_only(cx) {
8967 return;
8968 }
8969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8970 let mut selections = self.selections.all_adjusted(cx);
8971 let buffer = self.buffer.read(cx);
8972 let snapshot = buffer.snapshot(cx);
8973 let rows_iter = selections.iter().map(|s| s.head().row);
8974 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8975
8976 let has_some_cursor_in_whitespace = selections
8977 .iter()
8978 .filter(|selection| selection.is_empty())
8979 .any(|selection| {
8980 let cursor = selection.head();
8981 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8982 cursor.column < current_indent.len
8983 });
8984
8985 let mut edits = Vec::new();
8986 let mut prev_edited_row = 0;
8987 let mut row_delta = 0;
8988 for selection in &mut selections {
8989 if selection.start.row != prev_edited_row {
8990 row_delta = 0;
8991 }
8992 prev_edited_row = selection.end.row;
8993
8994 // If the selection is non-empty, then increase the indentation of the selected lines.
8995 if !selection.is_empty() {
8996 row_delta =
8997 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8998 continue;
8999 }
9000
9001 let cursor = selection.head();
9002 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9003 if let Some(suggested_indent) =
9004 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9005 {
9006 // Don't do anything if already at suggested indent
9007 // and there is any other cursor which is not
9008 if has_some_cursor_in_whitespace
9009 && cursor.column == current_indent.len
9010 && current_indent.len == suggested_indent.len
9011 {
9012 continue;
9013 }
9014
9015 // Adjust line and move cursor to suggested indent
9016 // if cursor is not at suggested indent
9017 if cursor.column < suggested_indent.len
9018 && cursor.column <= current_indent.len
9019 && current_indent.len <= suggested_indent.len
9020 {
9021 selection.start = Point::new(cursor.row, suggested_indent.len);
9022 selection.end = selection.start;
9023 if row_delta == 0 {
9024 edits.extend(Buffer::edit_for_indent_size_adjustment(
9025 cursor.row,
9026 current_indent,
9027 suggested_indent,
9028 ));
9029 row_delta = suggested_indent.len - current_indent.len;
9030 }
9031 continue;
9032 }
9033
9034 // If current indent is more than suggested indent
9035 // only move cursor to current indent and skip indent
9036 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9037 selection.start = Point::new(cursor.row, current_indent.len);
9038 selection.end = selection.start;
9039 continue;
9040 }
9041 }
9042
9043 // Otherwise, insert a hard or soft tab.
9044 let settings = buffer.language_settings_at(cursor, cx);
9045 let tab_size = if settings.hard_tabs {
9046 IndentSize::tab()
9047 } else {
9048 let tab_size = settings.tab_size.get();
9049 let indent_remainder = snapshot
9050 .text_for_range(Point::new(cursor.row, 0)..cursor)
9051 .flat_map(str::chars)
9052 .fold(row_delta % tab_size, |counter: u32, c| {
9053 if c == '\t' {
9054 0
9055 } else {
9056 (counter + 1) % tab_size
9057 }
9058 });
9059
9060 let chars_to_next_tab_stop = tab_size - indent_remainder;
9061 IndentSize::spaces(chars_to_next_tab_stop)
9062 };
9063 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9064 selection.end = selection.start;
9065 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9066 row_delta += tab_size.len;
9067 }
9068
9069 self.transact(window, cx, |this, window, cx| {
9070 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9071 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9072 s.select(selections)
9073 });
9074 this.refresh_inline_completion(true, false, window, cx);
9075 });
9076 }
9077
9078 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9079 if self.read_only(cx) {
9080 return;
9081 }
9082 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9083 let mut selections = self.selections.all::<Point>(cx);
9084 let mut prev_edited_row = 0;
9085 let mut row_delta = 0;
9086 let mut edits = Vec::new();
9087 let buffer = self.buffer.read(cx);
9088 let snapshot = buffer.snapshot(cx);
9089 for selection in &mut selections {
9090 if selection.start.row != prev_edited_row {
9091 row_delta = 0;
9092 }
9093 prev_edited_row = selection.end.row;
9094
9095 row_delta =
9096 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9097 }
9098
9099 self.transact(window, cx, |this, window, cx| {
9100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9101 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9102 s.select(selections)
9103 });
9104 });
9105 }
9106
9107 fn indent_selection(
9108 buffer: &MultiBuffer,
9109 snapshot: &MultiBufferSnapshot,
9110 selection: &mut Selection<Point>,
9111 edits: &mut Vec<(Range<Point>, String)>,
9112 delta_for_start_row: u32,
9113 cx: &App,
9114 ) -> u32 {
9115 let settings = buffer.language_settings_at(selection.start, cx);
9116 let tab_size = settings.tab_size.get();
9117 let indent_kind = if settings.hard_tabs {
9118 IndentKind::Tab
9119 } else {
9120 IndentKind::Space
9121 };
9122 let mut start_row = selection.start.row;
9123 let mut end_row = selection.end.row + 1;
9124
9125 // If a selection ends at the beginning of a line, don't indent
9126 // that last line.
9127 if selection.end.column == 0 && selection.end.row > selection.start.row {
9128 end_row -= 1;
9129 }
9130
9131 // Avoid re-indenting a row that has already been indented by a
9132 // previous selection, but still update this selection's column
9133 // to reflect that indentation.
9134 if delta_for_start_row > 0 {
9135 start_row += 1;
9136 selection.start.column += delta_for_start_row;
9137 if selection.end.row == selection.start.row {
9138 selection.end.column += delta_for_start_row;
9139 }
9140 }
9141
9142 let mut delta_for_end_row = 0;
9143 let has_multiple_rows = start_row + 1 != end_row;
9144 for row in start_row..end_row {
9145 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9146 let indent_delta = match (current_indent.kind, indent_kind) {
9147 (IndentKind::Space, IndentKind::Space) => {
9148 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9149 IndentSize::spaces(columns_to_next_tab_stop)
9150 }
9151 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9152 (_, IndentKind::Tab) => IndentSize::tab(),
9153 };
9154
9155 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9156 0
9157 } else {
9158 selection.start.column
9159 };
9160 let row_start = Point::new(row, start);
9161 edits.push((
9162 row_start..row_start,
9163 indent_delta.chars().collect::<String>(),
9164 ));
9165
9166 // Update this selection's endpoints to reflect the indentation.
9167 if row == selection.start.row {
9168 selection.start.column += indent_delta.len;
9169 }
9170 if row == selection.end.row {
9171 selection.end.column += indent_delta.len;
9172 delta_for_end_row = indent_delta.len;
9173 }
9174 }
9175
9176 if selection.start.row == selection.end.row {
9177 delta_for_start_row + delta_for_end_row
9178 } else {
9179 delta_for_end_row
9180 }
9181 }
9182
9183 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9184 if self.read_only(cx) {
9185 return;
9186 }
9187 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9189 let selections = self.selections.all::<Point>(cx);
9190 let mut deletion_ranges = Vec::new();
9191 let mut last_outdent = None;
9192 {
9193 let buffer = self.buffer.read(cx);
9194 let snapshot = buffer.snapshot(cx);
9195 for selection in &selections {
9196 let settings = buffer.language_settings_at(selection.start, cx);
9197 let tab_size = settings.tab_size.get();
9198 let mut rows = selection.spanned_rows(false, &display_map);
9199
9200 // Avoid re-outdenting a row that has already been outdented by a
9201 // previous selection.
9202 if let Some(last_row) = last_outdent {
9203 if last_row == rows.start {
9204 rows.start = rows.start.next_row();
9205 }
9206 }
9207 let has_multiple_rows = rows.len() > 1;
9208 for row in rows.iter_rows() {
9209 let indent_size = snapshot.indent_size_for_line(row);
9210 if indent_size.len > 0 {
9211 let deletion_len = match indent_size.kind {
9212 IndentKind::Space => {
9213 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9214 if columns_to_prev_tab_stop == 0 {
9215 tab_size
9216 } else {
9217 columns_to_prev_tab_stop
9218 }
9219 }
9220 IndentKind::Tab => 1,
9221 };
9222 let start = if has_multiple_rows
9223 || deletion_len > selection.start.column
9224 || indent_size.len < selection.start.column
9225 {
9226 0
9227 } else {
9228 selection.start.column - deletion_len
9229 };
9230 deletion_ranges.push(
9231 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9232 );
9233 last_outdent = Some(row);
9234 }
9235 }
9236 }
9237 }
9238
9239 self.transact(window, cx, |this, window, cx| {
9240 this.buffer.update(cx, |buffer, cx| {
9241 let empty_str: Arc<str> = Arc::default();
9242 buffer.edit(
9243 deletion_ranges
9244 .into_iter()
9245 .map(|range| (range, empty_str.clone())),
9246 None,
9247 cx,
9248 );
9249 });
9250 let selections = this.selections.all::<usize>(cx);
9251 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9252 s.select(selections)
9253 });
9254 });
9255 }
9256
9257 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9258 if self.read_only(cx) {
9259 return;
9260 }
9261 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9262 let selections = self
9263 .selections
9264 .all::<usize>(cx)
9265 .into_iter()
9266 .map(|s| s.range());
9267
9268 self.transact(window, cx, |this, window, cx| {
9269 this.buffer.update(cx, |buffer, cx| {
9270 buffer.autoindent_ranges(selections, cx);
9271 });
9272 let selections = this.selections.all::<usize>(cx);
9273 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9274 s.select(selections)
9275 });
9276 });
9277 }
9278
9279 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9280 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9282 let selections = self.selections.all::<Point>(cx);
9283
9284 let mut new_cursors = Vec::new();
9285 let mut edit_ranges = Vec::new();
9286 let mut selections = selections.iter().peekable();
9287 while let Some(selection) = selections.next() {
9288 let mut rows = selection.spanned_rows(false, &display_map);
9289 let goal_display_column = selection.head().to_display_point(&display_map).column();
9290
9291 // Accumulate contiguous regions of rows that we want to delete.
9292 while let Some(next_selection) = selections.peek() {
9293 let next_rows = next_selection.spanned_rows(false, &display_map);
9294 if next_rows.start <= rows.end {
9295 rows.end = next_rows.end;
9296 selections.next().unwrap();
9297 } else {
9298 break;
9299 }
9300 }
9301
9302 let buffer = &display_map.buffer_snapshot;
9303 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9304 let edit_end;
9305 let cursor_buffer_row;
9306 if buffer.max_point().row >= rows.end.0 {
9307 // If there's a line after the range, delete the \n from the end of the row range
9308 // and position the cursor on the next line.
9309 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9310 cursor_buffer_row = rows.end;
9311 } else {
9312 // If there isn't a line after the range, delete the \n from the line before the
9313 // start of the row range and position the cursor there.
9314 edit_start = edit_start.saturating_sub(1);
9315 edit_end = buffer.len();
9316 cursor_buffer_row = rows.start.previous_row();
9317 }
9318
9319 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9320 *cursor.column_mut() =
9321 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9322
9323 new_cursors.push((
9324 selection.id,
9325 buffer.anchor_after(cursor.to_point(&display_map)),
9326 ));
9327 edit_ranges.push(edit_start..edit_end);
9328 }
9329
9330 self.transact(window, cx, |this, window, cx| {
9331 let buffer = this.buffer.update(cx, |buffer, cx| {
9332 let empty_str: Arc<str> = Arc::default();
9333 buffer.edit(
9334 edit_ranges
9335 .into_iter()
9336 .map(|range| (range, empty_str.clone())),
9337 None,
9338 cx,
9339 );
9340 buffer.snapshot(cx)
9341 });
9342 let new_selections = new_cursors
9343 .into_iter()
9344 .map(|(id, cursor)| {
9345 let cursor = cursor.to_point(&buffer);
9346 Selection {
9347 id,
9348 start: cursor,
9349 end: cursor,
9350 reversed: false,
9351 goal: SelectionGoal::None,
9352 }
9353 })
9354 .collect();
9355
9356 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9357 s.select(new_selections);
9358 });
9359 });
9360 }
9361
9362 pub fn join_lines_impl(
9363 &mut self,
9364 insert_whitespace: bool,
9365 window: &mut Window,
9366 cx: &mut Context<Self>,
9367 ) {
9368 if self.read_only(cx) {
9369 return;
9370 }
9371 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9372 for selection in self.selections.all::<Point>(cx) {
9373 let start = MultiBufferRow(selection.start.row);
9374 // Treat single line selections as if they include the next line. Otherwise this action
9375 // would do nothing for single line selections individual cursors.
9376 let end = if selection.start.row == selection.end.row {
9377 MultiBufferRow(selection.start.row + 1)
9378 } else {
9379 MultiBufferRow(selection.end.row)
9380 };
9381
9382 if let Some(last_row_range) = row_ranges.last_mut() {
9383 if start <= last_row_range.end {
9384 last_row_range.end = end;
9385 continue;
9386 }
9387 }
9388 row_ranges.push(start..end);
9389 }
9390
9391 let snapshot = self.buffer.read(cx).snapshot(cx);
9392 let mut cursor_positions = Vec::new();
9393 for row_range in &row_ranges {
9394 let anchor = snapshot.anchor_before(Point::new(
9395 row_range.end.previous_row().0,
9396 snapshot.line_len(row_range.end.previous_row()),
9397 ));
9398 cursor_positions.push(anchor..anchor);
9399 }
9400
9401 self.transact(window, cx, |this, window, cx| {
9402 for row_range in row_ranges.into_iter().rev() {
9403 for row in row_range.iter_rows().rev() {
9404 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9405 let next_line_row = row.next_row();
9406 let indent = snapshot.indent_size_for_line(next_line_row);
9407 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9408
9409 let replace =
9410 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9411 " "
9412 } else {
9413 ""
9414 };
9415
9416 this.buffer.update(cx, |buffer, cx| {
9417 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9418 });
9419 }
9420 }
9421
9422 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9423 s.select_anchor_ranges(cursor_positions)
9424 });
9425 });
9426 }
9427
9428 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9430 self.join_lines_impl(true, window, cx);
9431 }
9432
9433 pub fn sort_lines_case_sensitive(
9434 &mut self,
9435 _: &SortLinesCaseSensitive,
9436 window: &mut Window,
9437 cx: &mut Context<Self>,
9438 ) {
9439 self.manipulate_lines(window, cx, |lines| lines.sort())
9440 }
9441
9442 pub fn sort_lines_case_insensitive(
9443 &mut self,
9444 _: &SortLinesCaseInsensitive,
9445 window: &mut Window,
9446 cx: &mut Context<Self>,
9447 ) {
9448 self.manipulate_lines(window, cx, |lines| {
9449 lines.sort_by_key(|line| line.to_lowercase())
9450 })
9451 }
9452
9453 pub fn unique_lines_case_insensitive(
9454 &mut self,
9455 _: &UniqueLinesCaseInsensitive,
9456 window: &mut Window,
9457 cx: &mut Context<Self>,
9458 ) {
9459 self.manipulate_lines(window, cx, |lines| {
9460 let mut seen = HashSet::default();
9461 lines.retain(|line| seen.insert(line.to_lowercase()));
9462 })
9463 }
9464
9465 pub fn unique_lines_case_sensitive(
9466 &mut self,
9467 _: &UniqueLinesCaseSensitive,
9468 window: &mut Window,
9469 cx: &mut Context<Self>,
9470 ) {
9471 self.manipulate_lines(window, cx, |lines| {
9472 let mut seen = HashSet::default();
9473 lines.retain(|line| seen.insert(*line));
9474 })
9475 }
9476
9477 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9478 let Some(project) = self.project.clone() else {
9479 return;
9480 };
9481 self.reload(project, window, cx)
9482 .detach_and_notify_err(window, cx);
9483 }
9484
9485 pub fn restore_file(
9486 &mut self,
9487 _: &::git::RestoreFile,
9488 window: &mut Window,
9489 cx: &mut Context<Self>,
9490 ) {
9491 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9492 let mut buffer_ids = HashSet::default();
9493 let snapshot = self.buffer().read(cx).snapshot(cx);
9494 for selection in self.selections.all::<usize>(cx) {
9495 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9496 }
9497
9498 let buffer = self.buffer().read(cx);
9499 let ranges = buffer_ids
9500 .into_iter()
9501 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9502 .collect::<Vec<_>>();
9503
9504 self.restore_hunks_in_ranges(ranges, window, cx);
9505 }
9506
9507 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9508 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9509 let selections = self
9510 .selections
9511 .all(cx)
9512 .into_iter()
9513 .map(|s| s.range())
9514 .collect();
9515 self.restore_hunks_in_ranges(selections, window, cx);
9516 }
9517
9518 pub fn restore_hunks_in_ranges(
9519 &mut self,
9520 ranges: Vec<Range<Point>>,
9521 window: &mut Window,
9522 cx: &mut Context<Editor>,
9523 ) {
9524 let mut revert_changes = HashMap::default();
9525 let chunk_by = self
9526 .snapshot(window, cx)
9527 .hunks_for_ranges(ranges)
9528 .into_iter()
9529 .chunk_by(|hunk| hunk.buffer_id);
9530 for (buffer_id, hunks) in &chunk_by {
9531 let hunks = hunks.collect::<Vec<_>>();
9532 for hunk in &hunks {
9533 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9534 }
9535 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9536 }
9537 drop(chunk_by);
9538 if !revert_changes.is_empty() {
9539 self.transact(window, cx, |editor, window, cx| {
9540 editor.restore(revert_changes, window, cx);
9541 });
9542 }
9543 }
9544
9545 pub fn open_active_item_in_terminal(
9546 &mut self,
9547 _: &OpenInTerminal,
9548 window: &mut Window,
9549 cx: &mut Context<Self>,
9550 ) {
9551 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9552 let project_path = buffer.read(cx).project_path(cx)?;
9553 let project = self.project.as_ref()?.read(cx);
9554 let entry = project.entry_for_path(&project_path, cx)?;
9555 let parent = match &entry.canonical_path {
9556 Some(canonical_path) => canonical_path.to_path_buf(),
9557 None => project.absolute_path(&project_path, cx)?,
9558 }
9559 .parent()?
9560 .to_path_buf();
9561 Some(parent)
9562 }) {
9563 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9564 }
9565 }
9566
9567 fn set_breakpoint_context_menu(
9568 &mut self,
9569 display_row: DisplayRow,
9570 position: Option<Anchor>,
9571 clicked_point: gpui::Point<Pixels>,
9572 window: &mut Window,
9573 cx: &mut Context<Self>,
9574 ) {
9575 if !cx.has_flag::<DebuggerFeatureFlag>() {
9576 return;
9577 }
9578 let source = self
9579 .buffer
9580 .read(cx)
9581 .snapshot(cx)
9582 .anchor_before(Point::new(display_row.0, 0u32));
9583
9584 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9585
9586 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9587 self,
9588 source,
9589 clicked_point,
9590 context_menu,
9591 window,
9592 cx,
9593 );
9594 }
9595
9596 fn add_edit_breakpoint_block(
9597 &mut self,
9598 anchor: Anchor,
9599 breakpoint: &Breakpoint,
9600 edit_action: BreakpointPromptEditAction,
9601 window: &mut Window,
9602 cx: &mut Context<Self>,
9603 ) {
9604 let weak_editor = cx.weak_entity();
9605 let bp_prompt = cx.new(|cx| {
9606 BreakpointPromptEditor::new(
9607 weak_editor,
9608 anchor,
9609 breakpoint.clone(),
9610 edit_action,
9611 window,
9612 cx,
9613 )
9614 });
9615
9616 let height = bp_prompt.update(cx, |this, cx| {
9617 this.prompt
9618 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9619 });
9620 let cloned_prompt = bp_prompt.clone();
9621 let blocks = vec![BlockProperties {
9622 style: BlockStyle::Sticky,
9623 placement: BlockPlacement::Above(anchor),
9624 height: Some(height),
9625 render: Arc::new(move |cx| {
9626 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9627 cloned_prompt.clone().into_any_element()
9628 }),
9629 priority: 0,
9630 render_in_minimap: true,
9631 }];
9632
9633 let focus_handle = bp_prompt.focus_handle(cx);
9634 window.focus(&focus_handle);
9635
9636 let block_ids = self.insert_blocks(blocks, None, cx);
9637 bp_prompt.update(cx, |prompt, _| {
9638 prompt.add_block_ids(block_ids);
9639 });
9640 }
9641
9642 pub(crate) fn breakpoint_at_row(
9643 &self,
9644 row: u32,
9645 window: &mut Window,
9646 cx: &mut Context<Self>,
9647 ) -> Option<(Anchor, Breakpoint)> {
9648 let snapshot = self.snapshot(window, cx);
9649 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9650
9651 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9652 }
9653
9654 pub(crate) fn breakpoint_at_anchor(
9655 &self,
9656 breakpoint_position: Anchor,
9657 snapshot: &EditorSnapshot,
9658 cx: &mut Context<Self>,
9659 ) -> Option<(Anchor, Breakpoint)> {
9660 let project = self.project.clone()?;
9661
9662 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9663 snapshot
9664 .buffer_snapshot
9665 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9666 })?;
9667
9668 let enclosing_excerpt = breakpoint_position.excerpt_id;
9669 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9670 let buffer_snapshot = buffer.read(cx).snapshot();
9671
9672 let row = buffer_snapshot
9673 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9674 .row;
9675
9676 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9677 let anchor_end = snapshot
9678 .buffer_snapshot
9679 .anchor_after(Point::new(row, line_len));
9680
9681 let bp = self
9682 .breakpoint_store
9683 .as_ref()?
9684 .read_with(cx, |breakpoint_store, cx| {
9685 breakpoint_store
9686 .breakpoints(
9687 &buffer,
9688 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9689 &buffer_snapshot,
9690 cx,
9691 )
9692 .next()
9693 .and_then(|(bp, _)| {
9694 let breakpoint_row = buffer_snapshot
9695 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9696 .row;
9697
9698 if breakpoint_row == row {
9699 snapshot
9700 .buffer_snapshot
9701 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9702 .map(|position| (position, bp.bp.clone()))
9703 } else {
9704 None
9705 }
9706 })
9707 });
9708 bp
9709 }
9710
9711 pub fn edit_log_breakpoint(
9712 &mut self,
9713 _: &EditLogBreakpoint,
9714 window: &mut Window,
9715 cx: &mut Context<Self>,
9716 ) {
9717 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9718 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9719 message: None,
9720 state: BreakpointState::Enabled,
9721 condition: None,
9722 hit_condition: None,
9723 });
9724
9725 self.add_edit_breakpoint_block(
9726 anchor,
9727 &breakpoint,
9728 BreakpointPromptEditAction::Log,
9729 window,
9730 cx,
9731 );
9732 }
9733 }
9734
9735 fn breakpoints_at_cursors(
9736 &self,
9737 window: &mut Window,
9738 cx: &mut Context<Self>,
9739 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9740 let snapshot = self.snapshot(window, cx);
9741 let cursors = self
9742 .selections
9743 .disjoint_anchors()
9744 .into_iter()
9745 .map(|selection| {
9746 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9747
9748 let breakpoint_position = self
9749 .breakpoint_at_row(cursor_position.row, window, cx)
9750 .map(|bp| bp.0)
9751 .unwrap_or_else(|| {
9752 snapshot
9753 .display_snapshot
9754 .buffer_snapshot
9755 .anchor_after(Point::new(cursor_position.row, 0))
9756 });
9757
9758 let breakpoint = self
9759 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9760 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9761
9762 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9763 })
9764 // 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.
9765 .collect::<HashMap<Anchor, _>>();
9766
9767 cursors.into_iter().collect()
9768 }
9769
9770 pub fn enable_breakpoint(
9771 &mut self,
9772 _: &crate::actions::EnableBreakpoint,
9773 window: &mut Window,
9774 cx: &mut Context<Self>,
9775 ) {
9776 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9777 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9778 continue;
9779 };
9780 self.edit_breakpoint_at_anchor(
9781 anchor,
9782 breakpoint,
9783 BreakpointEditAction::InvertState,
9784 cx,
9785 );
9786 }
9787 }
9788
9789 pub fn disable_breakpoint(
9790 &mut self,
9791 _: &crate::actions::DisableBreakpoint,
9792 window: &mut Window,
9793 cx: &mut Context<Self>,
9794 ) {
9795 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9796 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9797 continue;
9798 };
9799 self.edit_breakpoint_at_anchor(
9800 anchor,
9801 breakpoint,
9802 BreakpointEditAction::InvertState,
9803 cx,
9804 );
9805 }
9806 }
9807
9808 pub fn toggle_breakpoint(
9809 &mut self,
9810 _: &crate::actions::ToggleBreakpoint,
9811 window: &mut Window,
9812 cx: &mut Context<Self>,
9813 ) {
9814 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9815 if let Some(breakpoint) = breakpoint {
9816 self.edit_breakpoint_at_anchor(
9817 anchor,
9818 breakpoint,
9819 BreakpointEditAction::Toggle,
9820 cx,
9821 );
9822 } else {
9823 self.edit_breakpoint_at_anchor(
9824 anchor,
9825 Breakpoint::new_standard(),
9826 BreakpointEditAction::Toggle,
9827 cx,
9828 );
9829 }
9830 }
9831 }
9832
9833 pub fn edit_breakpoint_at_anchor(
9834 &mut self,
9835 breakpoint_position: Anchor,
9836 breakpoint: Breakpoint,
9837 edit_action: BreakpointEditAction,
9838 cx: &mut Context<Self>,
9839 ) {
9840 let Some(breakpoint_store) = &self.breakpoint_store else {
9841 return;
9842 };
9843
9844 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9845 if breakpoint_position == Anchor::min() {
9846 self.buffer()
9847 .read(cx)
9848 .excerpt_buffer_ids()
9849 .into_iter()
9850 .next()
9851 } else {
9852 None
9853 }
9854 }) else {
9855 return;
9856 };
9857
9858 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9859 return;
9860 };
9861
9862 breakpoint_store.update(cx, |breakpoint_store, cx| {
9863 breakpoint_store.toggle_breakpoint(
9864 buffer,
9865 BreakpointWithPosition {
9866 position: breakpoint_position.text_anchor,
9867 bp: breakpoint,
9868 },
9869 edit_action,
9870 cx,
9871 );
9872 });
9873
9874 cx.notify();
9875 }
9876
9877 #[cfg(any(test, feature = "test-support"))]
9878 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9879 self.breakpoint_store.clone()
9880 }
9881
9882 pub fn prepare_restore_change(
9883 &self,
9884 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9885 hunk: &MultiBufferDiffHunk,
9886 cx: &mut App,
9887 ) -> Option<()> {
9888 if hunk.is_created_file() {
9889 return None;
9890 }
9891 let buffer = self.buffer.read(cx);
9892 let diff = buffer.diff_for(hunk.buffer_id)?;
9893 let buffer = buffer.buffer(hunk.buffer_id)?;
9894 let buffer = buffer.read(cx);
9895 let original_text = diff
9896 .read(cx)
9897 .base_text()
9898 .as_rope()
9899 .slice(hunk.diff_base_byte_range.clone());
9900 let buffer_snapshot = buffer.snapshot();
9901 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9902 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9903 probe
9904 .0
9905 .start
9906 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9907 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9908 }) {
9909 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9910 Some(())
9911 } else {
9912 None
9913 }
9914 }
9915
9916 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9917 self.manipulate_lines(window, cx, |lines| lines.reverse())
9918 }
9919
9920 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9921 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9922 }
9923
9924 fn manipulate_lines<Fn>(
9925 &mut self,
9926 window: &mut Window,
9927 cx: &mut Context<Self>,
9928 mut callback: Fn,
9929 ) where
9930 Fn: FnMut(&mut Vec<&str>),
9931 {
9932 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9933
9934 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9935 let buffer = self.buffer.read(cx).snapshot(cx);
9936
9937 let mut edits = Vec::new();
9938
9939 let selections = self.selections.all::<Point>(cx);
9940 let mut selections = selections.iter().peekable();
9941 let mut contiguous_row_selections = Vec::new();
9942 let mut new_selections = Vec::new();
9943 let mut added_lines = 0;
9944 let mut removed_lines = 0;
9945
9946 while let Some(selection) = selections.next() {
9947 let (start_row, end_row) = consume_contiguous_rows(
9948 &mut contiguous_row_selections,
9949 selection,
9950 &display_map,
9951 &mut selections,
9952 );
9953
9954 let start_point = Point::new(start_row.0, 0);
9955 let end_point = Point::new(
9956 end_row.previous_row().0,
9957 buffer.line_len(end_row.previous_row()),
9958 );
9959 let text = buffer
9960 .text_for_range(start_point..end_point)
9961 .collect::<String>();
9962
9963 let mut lines = text.split('\n').collect_vec();
9964
9965 let lines_before = lines.len();
9966 callback(&mut lines);
9967 let lines_after = lines.len();
9968
9969 edits.push((start_point..end_point, lines.join("\n")));
9970
9971 // Selections must change based on added and removed line count
9972 let start_row =
9973 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9974 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9975 new_selections.push(Selection {
9976 id: selection.id,
9977 start: start_row,
9978 end: end_row,
9979 goal: SelectionGoal::None,
9980 reversed: selection.reversed,
9981 });
9982
9983 if lines_after > lines_before {
9984 added_lines += lines_after - lines_before;
9985 } else if lines_before > lines_after {
9986 removed_lines += lines_before - lines_after;
9987 }
9988 }
9989
9990 self.transact(window, cx, |this, window, cx| {
9991 let buffer = this.buffer.update(cx, |buffer, cx| {
9992 buffer.edit(edits, None, cx);
9993 buffer.snapshot(cx)
9994 });
9995
9996 // Recalculate offsets on newly edited buffer
9997 let new_selections = new_selections
9998 .iter()
9999 .map(|s| {
10000 let start_point = Point::new(s.start.0, 0);
10001 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10002 Selection {
10003 id: s.id,
10004 start: buffer.point_to_offset(start_point),
10005 end: buffer.point_to_offset(end_point),
10006 goal: s.goal,
10007 reversed: s.reversed,
10008 }
10009 })
10010 .collect();
10011
10012 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10013 s.select(new_selections);
10014 });
10015
10016 this.request_autoscroll(Autoscroll::fit(), cx);
10017 });
10018 }
10019
10020 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10021 self.manipulate_text(window, cx, |text| {
10022 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10023 if has_upper_case_characters {
10024 text.to_lowercase()
10025 } else {
10026 text.to_uppercase()
10027 }
10028 })
10029 }
10030
10031 pub fn convert_to_upper_case(
10032 &mut self,
10033 _: &ConvertToUpperCase,
10034 window: &mut Window,
10035 cx: &mut Context<Self>,
10036 ) {
10037 self.manipulate_text(window, cx, |text| text.to_uppercase())
10038 }
10039
10040 pub fn convert_to_lower_case(
10041 &mut self,
10042 _: &ConvertToLowerCase,
10043 window: &mut Window,
10044 cx: &mut Context<Self>,
10045 ) {
10046 self.manipulate_text(window, cx, |text| text.to_lowercase())
10047 }
10048
10049 pub fn convert_to_title_case(
10050 &mut self,
10051 _: &ConvertToTitleCase,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) {
10055 self.manipulate_text(window, cx, |text| {
10056 text.split('\n')
10057 .map(|line| line.to_case(Case::Title))
10058 .join("\n")
10059 })
10060 }
10061
10062 pub fn convert_to_snake_case(
10063 &mut self,
10064 _: &ConvertToSnakeCase,
10065 window: &mut Window,
10066 cx: &mut Context<Self>,
10067 ) {
10068 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10069 }
10070
10071 pub fn convert_to_kebab_case(
10072 &mut self,
10073 _: &ConvertToKebabCase,
10074 window: &mut Window,
10075 cx: &mut Context<Self>,
10076 ) {
10077 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10078 }
10079
10080 pub fn convert_to_upper_camel_case(
10081 &mut self,
10082 _: &ConvertToUpperCamelCase,
10083 window: &mut Window,
10084 cx: &mut Context<Self>,
10085 ) {
10086 self.manipulate_text(window, cx, |text| {
10087 text.split('\n')
10088 .map(|line| line.to_case(Case::UpperCamel))
10089 .join("\n")
10090 })
10091 }
10092
10093 pub fn convert_to_lower_camel_case(
10094 &mut self,
10095 _: &ConvertToLowerCamelCase,
10096 window: &mut Window,
10097 cx: &mut Context<Self>,
10098 ) {
10099 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10100 }
10101
10102 pub fn convert_to_opposite_case(
10103 &mut self,
10104 _: &ConvertToOppositeCase,
10105 window: &mut Window,
10106 cx: &mut Context<Self>,
10107 ) {
10108 self.manipulate_text(window, cx, |text| {
10109 text.chars()
10110 .fold(String::with_capacity(text.len()), |mut t, c| {
10111 if c.is_uppercase() {
10112 t.extend(c.to_lowercase());
10113 } else {
10114 t.extend(c.to_uppercase());
10115 }
10116 t
10117 })
10118 })
10119 }
10120
10121 pub fn convert_to_rot13(
10122 &mut self,
10123 _: &ConvertToRot13,
10124 window: &mut Window,
10125 cx: &mut Context<Self>,
10126 ) {
10127 self.manipulate_text(window, cx, |text| {
10128 text.chars()
10129 .map(|c| match c {
10130 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10131 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10132 _ => c,
10133 })
10134 .collect()
10135 })
10136 }
10137
10138 pub fn convert_to_rot47(
10139 &mut self,
10140 _: &ConvertToRot47,
10141 window: &mut Window,
10142 cx: &mut Context<Self>,
10143 ) {
10144 self.manipulate_text(window, cx, |text| {
10145 text.chars()
10146 .map(|c| {
10147 let code_point = c as u32;
10148 if code_point >= 33 && code_point <= 126 {
10149 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10150 }
10151 c
10152 })
10153 .collect()
10154 })
10155 }
10156
10157 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10158 where
10159 Fn: FnMut(&str) -> String,
10160 {
10161 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10162 let buffer = self.buffer.read(cx).snapshot(cx);
10163
10164 let mut new_selections = Vec::new();
10165 let mut edits = Vec::new();
10166 let mut selection_adjustment = 0i32;
10167
10168 for selection in self.selections.all::<usize>(cx) {
10169 let selection_is_empty = selection.is_empty();
10170
10171 let (start, end) = if selection_is_empty {
10172 let word_range = movement::surrounding_word(
10173 &display_map,
10174 selection.start.to_display_point(&display_map),
10175 );
10176 let start = word_range.start.to_offset(&display_map, Bias::Left);
10177 let end = word_range.end.to_offset(&display_map, Bias::Left);
10178 (start, end)
10179 } else {
10180 (selection.start, selection.end)
10181 };
10182
10183 let text = buffer.text_for_range(start..end).collect::<String>();
10184 let old_length = text.len() as i32;
10185 let text = callback(&text);
10186
10187 new_selections.push(Selection {
10188 start: (start as i32 - selection_adjustment) as usize,
10189 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10190 goal: SelectionGoal::None,
10191 ..selection
10192 });
10193
10194 selection_adjustment += old_length - text.len() as i32;
10195
10196 edits.push((start..end, text));
10197 }
10198
10199 self.transact(window, cx, |this, window, cx| {
10200 this.buffer.update(cx, |buffer, cx| {
10201 buffer.edit(edits, None, cx);
10202 });
10203
10204 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10205 s.select(new_selections);
10206 });
10207
10208 this.request_autoscroll(Autoscroll::fit(), cx);
10209 });
10210 }
10211
10212 pub fn duplicate(
10213 &mut self,
10214 upwards: bool,
10215 whole_lines: bool,
10216 window: &mut Window,
10217 cx: &mut Context<Self>,
10218 ) {
10219 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10220
10221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10222 let buffer = &display_map.buffer_snapshot;
10223 let selections = self.selections.all::<Point>(cx);
10224
10225 let mut edits = Vec::new();
10226 let mut selections_iter = selections.iter().peekable();
10227 while let Some(selection) = selections_iter.next() {
10228 let mut rows = selection.spanned_rows(false, &display_map);
10229 // duplicate line-wise
10230 if whole_lines || selection.start == selection.end {
10231 // Avoid duplicating the same lines twice.
10232 while let Some(next_selection) = selections_iter.peek() {
10233 let next_rows = next_selection.spanned_rows(false, &display_map);
10234 if next_rows.start < rows.end {
10235 rows.end = next_rows.end;
10236 selections_iter.next().unwrap();
10237 } else {
10238 break;
10239 }
10240 }
10241
10242 // Copy the text from the selected row region and splice it either at the start
10243 // or end of the region.
10244 let start = Point::new(rows.start.0, 0);
10245 let end = Point::new(
10246 rows.end.previous_row().0,
10247 buffer.line_len(rows.end.previous_row()),
10248 );
10249 let text = buffer
10250 .text_for_range(start..end)
10251 .chain(Some("\n"))
10252 .collect::<String>();
10253 let insert_location = if upwards {
10254 Point::new(rows.end.0, 0)
10255 } else {
10256 start
10257 };
10258 edits.push((insert_location..insert_location, text));
10259 } else {
10260 // duplicate character-wise
10261 let start = selection.start;
10262 let end = selection.end;
10263 let text = buffer.text_for_range(start..end).collect::<String>();
10264 edits.push((selection.end..selection.end, text));
10265 }
10266 }
10267
10268 self.transact(window, cx, |this, _, cx| {
10269 this.buffer.update(cx, |buffer, cx| {
10270 buffer.edit(edits, None, cx);
10271 });
10272
10273 this.request_autoscroll(Autoscroll::fit(), cx);
10274 });
10275 }
10276
10277 pub fn duplicate_line_up(
10278 &mut self,
10279 _: &DuplicateLineUp,
10280 window: &mut Window,
10281 cx: &mut Context<Self>,
10282 ) {
10283 self.duplicate(true, true, window, cx);
10284 }
10285
10286 pub fn duplicate_line_down(
10287 &mut self,
10288 _: &DuplicateLineDown,
10289 window: &mut Window,
10290 cx: &mut Context<Self>,
10291 ) {
10292 self.duplicate(false, true, window, cx);
10293 }
10294
10295 pub fn duplicate_selection(
10296 &mut self,
10297 _: &DuplicateSelection,
10298 window: &mut Window,
10299 cx: &mut Context<Self>,
10300 ) {
10301 self.duplicate(false, false, window, cx);
10302 }
10303
10304 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10305 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10306
10307 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10308 let buffer = self.buffer.read(cx).snapshot(cx);
10309
10310 let mut edits = Vec::new();
10311 let mut unfold_ranges = Vec::new();
10312 let mut refold_creases = Vec::new();
10313
10314 let selections = self.selections.all::<Point>(cx);
10315 let mut selections = selections.iter().peekable();
10316 let mut contiguous_row_selections = Vec::new();
10317 let mut new_selections = Vec::new();
10318
10319 while let Some(selection) = selections.next() {
10320 // Find all the selections that span a contiguous row range
10321 let (start_row, end_row) = consume_contiguous_rows(
10322 &mut contiguous_row_selections,
10323 selection,
10324 &display_map,
10325 &mut selections,
10326 );
10327
10328 // Move the text spanned by the row range to be before the line preceding the row range
10329 if start_row.0 > 0 {
10330 let range_to_move = Point::new(
10331 start_row.previous_row().0,
10332 buffer.line_len(start_row.previous_row()),
10333 )
10334 ..Point::new(
10335 end_row.previous_row().0,
10336 buffer.line_len(end_row.previous_row()),
10337 );
10338 let insertion_point = display_map
10339 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10340 .0;
10341
10342 // Don't move lines across excerpts
10343 if buffer
10344 .excerpt_containing(insertion_point..range_to_move.end)
10345 .is_some()
10346 {
10347 let text = buffer
10348 .text_for_range(range_to_move.clone())
10349 .flat_map(|s| s.chars())
10350 .skip(1)
10351 .chain(['\n'])
10352 .collect::<String>();
10353
10354 edits.push((
10355 buffer.anchor_after(range_to_move.start)
10356 ..buffer.anchor_before(range_to_move.end),
10357 String::new(),
10358 ));
10359 let insertion_anchor = buffer.anchor_after(insertion_point);
10360 edits.push((insertion_anchor..insertion_anchor, text));
10361
10362 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10363
10364 // Move selections up
10365 new_selections.extend(contiguous_row_selections.drain(..).map(
10366 |mut selection| {
10367 selection.start.row -= row_delta;
10368 selection.end.row -= row_delta;
10369 selection
10370 },
10371 ));
10372
10373 // Move folds up
10374 unfold_ranges.push(range_to_move.clone());
10375 for fold in display_map.folds_in_range(
10376 buffer.anchor_before(range_to_move.start)
10377 ..buffer.anchor_after(range_to_move.end),
10378 ) {
10379 let mut start = fold.range.start.to_point(&buffer);
10380 let mut end = fold.range.end.to_point(&buffer);
10381 start.row -= row_delta;
10382 end.row -= row_delta;
10383 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10384 }
10385 }
10386 }
10387
10388 // If we didn't move line(s), preserve the existing selections
10389 new_selections.append(&mut contiguous_row_selections);
10390 }
10391
10392 self.transact(window, cx, |this, window, cx| {
10393 this.unfold_ranges(&unfold_ranges, true, true, cx);
10394 this.buffer.update(cx, |buffer, cx| {
10395 for (range, text) in edits {
10396 buffer.edit([(range, text)], None, cx);
10397 }
10398 });
10399 this.fold_creases(refold_creases, true, window, cx);
10400 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10401 s.select(new_selections);
10402 })
10403 });
10404 }
10405
10406 pub fn move_line_down(
10407 &mut self,
10408 _: &MoveLineDown,
10409 window: &mut Window,
10410 cx: &mut Context<Self>,
10411 ) {
10412 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10413
10414 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10415 let buffer = self.buffer.read(cx).snapshot(cx);
10416
10417 let mut edits = Vec::new();
10418 let mut unfold_ranges = Vec::new();
10419 let mut refold_creases = Vec::new();
10420
10421 let selections = self.selections.all::<Point>(cx);
10422 let mut selections = selections.iter().peekable();
10423 let mut contiguous_row_selections = Vec::new();
10424 let mut new_selections = Vec::new();
10425
10426 while let Some(selection) = selections.next() {
10427 // Find all the selections that span a contiguous row range
10428 let (start_row, end_row) = consume_contiguous_rows(
10429 &mut contiguous_row_selections,
10430 selection,
10431 &display_map,
10432 &mut selections,
10433 );
10434
10435 // Move the text spanned by the row range to be after the last line of the row range
10436 if end_row.0 <= buffer.max_point().row {
10437 let range_to_move =
10438 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10439 let insertion_point = display_map
10440 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10441 .0;
10442
10443 // Don't move lines across excerpt boundaries
10444 if buffer
10445 .excerpt_containing(range_to_move.start..insertion_point)
10446 .is_some()
10447 {
10448 let mut text = String::from("\n");
10449 text.extend(buffer.text_for_range(range_to_move.clone()));
10450 text.pop(); // Drop trailing newline
10451 edits.push((
10452 buffer.anchor_after(range_to_move.start)
10453 ..buffer.anchor_before(range_to_move.end),
10454 String::new(),
10455 ));
10456 let insertion_anchor = buffer.anchor_after(insertion_point);
10457 edits.push((insertion_anchor..insertion_anchor, text));
10458
10459 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10460
10461 // Move selections down
10462 new_selections.extend(contiguous_row_selections.drain(..).map(
10463 |mut selection| {
10464 selection.start.row += row_delta;
10465 selection.end.row += row_delta;
10466 selection
10467 },
10468 ));
10469
10470 // Move folds down
10471 unfold_ranges.push(range_to_move.clone());
10472 for fold in display_map.folds_in_range(
10473 buffer.anchor_before(range_to_move.start)
10474 ..buffer.anchor_after(range_to_move.end),
10475 ) {
10476 let mut start = fold.range.start.to_point(&buffer);
10477 let mut end = fold.range.end.to_point(&buffer);
10478 start.row += row_delta;
10479 end.row += row_delta;
10480 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10481 }
10482 }
10483 }
10484
10485 // If we didn't move line(s), preserve the existing selections
10486 new_selections.append(&mut contiguous_row_selections);
10487 }
10488
10489 self.transact(window, cx, |this, window, cx| {
10490 this.unfold_ranges(&unfold_ranges, true, true, cx);
10491 this.buffer.update(cx, |buffer, cx| {
10492 for (range, text) in edits {
10493 buffer.edit([(range, text)], None, cx);
10494 }
10495 });
10496 this.fold_creases(refold_creases, true, window, cx);
10497 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10498 s.select(new_selections)
10499 });
10500 });
10501 }
10502
10503 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10504 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10505 let text_layout_details = &self.text_layout_details(window);
10506 self.transact(window, cx, |this, window, cx| {
10507 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10508 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10509 s.move_with(|display_map, selection| {
10510 if !selection.is_empty() {
10511 return;
10512 }
10513
10514 let mut head = selection.head();
10515 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10516 if head.column() == display_map.line_len(head.row()) {
10517 transpose_offset = display_map
10518 .buffer_snapshot
10519 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10520 }
10521
10522 if transpose_offset == 0 {
10523 return;
10524 }
10525
10526 *head.column_mut() += 1;
10527 head = display_map.clip_point(head, Bias::Right);
10528 let goal = SelectionGoal::HorizontalPosition(
10529 display_map
10530 .x_for_display_point(head, text_layout_details)
10531 .into(),
10532 );
10533 selection.collapse_to(head, goal);
10534
10535 let transpose_start = display_map
10536 .buffer_snapshot
10537 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10538 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10539 let transpose_end = display_map
10540 .buffer_snapshot
10541 .clip_offset(transpose_offset + 1, Bias::Right);
10542 if let Some(ch) =
10543 display_map.buffer_snapshot.chars_at(transpose_start).next()
10544 {
10545 edits.push((transpose_start..transpose_offset, String::new()));
10546 edits.push((transpose_end..transpose_end, ch.to_string()));
10547 }
10548 }
10549 });
10550 edits
10551 });
10552 this.buffer
10553 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10554 let selections = this.selections.all::<usize>(cx);
10555 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10556 s.select(selections);
10557 });
10558 });
10559 }
10560
10561 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10562 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10563 self.rewrap_impl(RewrapOptions::default(), cx)
10564 }
10565
10566 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10567 let buffer = self.buffer.read(cx).snapshot(cx);
10568 let selections = self.selections.all::<Point>(cx);
10569 let mut selections = selections.iter().peekable();
10570
10571 let mut edits = Vec::new();
10572 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10573
10574 while let Some(selection) = selections.next() {
10575 let mut start_row = selection.start.row;
10576 let mut end_row = selection.end.row;
10577
10578 // Skip selections that overlap with a range that has already been rewrapped.
10579 let selection_range = start_row..end_row;
10580 if rewrapped_row_ranges
10581 .iter()
10582 .any(|range| range.overlaps(&selection_range))
10583 {
10584 continue;
10585 }
10586
10587 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10588
10589 // Since not all lines in the selection may be at the same indent
10590 // level, choose the indent size that is the most common between all
10591 // of the lines.
10592 //
10593 // If there is a tie, we use the deepest indent.
10594 let (indent_size, indent_end) = {
10595 let mut indent_size_occurrences = HashMap::default();
10596 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10597
10598 for row in start_row..=end_row {
10599 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10600 rows_by_indent_size.entry(indent).or_default().push(row);
10601 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10602 }
10603
10604 let indent_size = indent_size_occurrences
10605 .into_iter()
10606 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10607 .map(|(indent, _)| indent)
10608 .unwrap_or_default();
10609 let row = rows_by_indent_size[&indent_size][0];
10610 let indent_end = Point::new(row, indent_size.len);
10611
10612 (indent_size, indent_end)
10613 };
10614
10615 let mut line_prefix = indent_size.chars().collect::<String>();
10616
10617 let mut inside_comment = false;
10618 if let Some(comment_prefix) =
10619 buffer
10620 .language_scope_at(selection.head())
10621 .and_then(|language| {
10622 language
10623 .line_comment_prefixes()
10624 .iter()
10625 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10626 .cloned()
10627 })
10628 {
10629 line_prefix.push_str(&comment_prefix);
10630 inside_comment = true;
10631 }
10632
10633 let language_settings = buffer.language_settings_at(selection.head(), cx);
10634 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10635 RewrapBehavior::InComments => inside_comment,
10636 RewrapBehavior::InSelections => !selection.is_empty(),
10637 RewrapBehavior::Anywhere => true,
10638 };
10639
10640 let should_rewrap = options.override_language_settings
10641 || allow_rewrap_based_on_language
10642 || self.hard_wrap.is_some();
10643 if !should_rewrap {
10644 continue;
10645 }
10646
10647 if selection.is_empty() {
10648 'expand_upwards: while start_row > 0 {
10649 let prev_row = start_row - 1;
10650 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10651 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10652 {
10653 start_row = prev_row;
10654 } else {
10655 break 'expand_upwards;
10656 }
10657 }
10658
10659 'expand_downwards: while end_row < buffer.max_point().row {
10660 let next_row = end_row + 1;
10661 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10662 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10663 {
10664 end_row = next_row;
10665 } else {
10666 break 'expand_downwards;
10667 }
10668 }
10669 }
10670
10671 let start = Point::new(start_row, 0);
10672 let start_offset = start.to_offset(&buffer);
10673 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10674 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10675 let Some(lines_without_prefixes) = selection_text
10676 .lines()
10677 .map(|line| {
10678 line.strip_prefix(&line_prefix)
10679 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10680 .with_context(|| {
10681 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10682 })
10683 })
10684 .collect::<Result<Vec<_>, _>>()
10685 .log_err()
10686 else {
10687 continue;
10688 };
10689
10690 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10691 buffer
10692 .language_settings_at(Point::new(start_row, 0), cx)
10693 .preferred_line_length as usize
10694 });
10695 let wrapped_text = wrap_with_prefix(
10696 line_prefix,
10697 lines_without_prefixes.join("\n"),
10698 wrap_column,
10699 tab_size,
10700 options.preserve_existing_whitespace,
10701 );
10702
10703 // TODO: should always use char-based diff while still supporting cursor behavior that
10704 // matches vim.
10705 let mut diff_options = DiffOptions::default();
10706 if options.override_language_settings {
10707 diff_options.max_word_diff_len = 0;
10708 diff_options.max_word_diff_line_count = 0;
10709 } else {
10710 diff_options.max_word_diff_len = usize::MAX;
10711 diff_options.max_word_diff_line_count = usize::MAX;
10712 }
10713
10714 for (old_range, new_text) in
10715 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10716 {
10717 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10718 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10719 edits.push((edit_start..edit_end, new_text));
10720 }
10721
10722 rewrapped_row_ranges.push(start_row..=end_row);
10723 }
10724
10725 self.buffer
10726 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10727 }
10728
10729 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10730 let mut text = String::new();
10731 let buffer = self.buffer.read(cx).snapshot(cx);
10732 let mut selections = self.selections.all::<Point>(cx);
10733 let mut clipboard_selections = Vec::with_capacity(selections.len());
10734 {
10735 let max_point = buffer.max_point();
10736 let mut is_first = true;
10737 for selection in &mut selections {
10738 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10739 if is_entire_line {
10740 selection.start = Point::new(selection.start.row, 0);
10741 if !selection.is_empty() && selection.end.column == 0 {
10742 selection.end = cmp::min(max_point, selection.end);
10743 } else {
10744 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10745 }
10746 selection.goal = SelectionGoal::None;
10747 }
10748 if is_first {
10749 is_first = false;
10750 } else {
10751 text += "\n";
10752 }
10753 let mut len = 0;
10754 for chunk in buffer.text_for_range(selection.start..selection.end) {
10755 text.push_str(chunk);
10756 len += chunk.len();
10757 }
10758 clipboard_selections.push(ClipboardSelection {
10759 len,
10760 is_entire_line,
10761 first_line_indent: buffer
10762 .indent_size_for_line(MultiBufferRow(selection.start.row))
10763 .len,
10764 });
10765 }
10766 }
10767
10768 self.transact(window, cx, |this, window, cx| {
10769 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10770 s.select(selections);
10771 });
10772 this.insert("", window, cx);
10773 });
10774 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10775 }
10776
10777 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10778 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10779 let item = self.cut_common(window, cx);
10780 cx.write_to_clipboard(item);
10781 }
10782
10783 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10784 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10785 self.change_selections(None, window, cx, |s| {
10786 s.move_with(|snapshot, sel| {
10787 if sel.is_empty() {
10788 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10789 }
10790 });
10791 });
10792 let item = self.cut_common(window, cx);
10793 cx.set_global(KillRing(item))
10794 }
10795
10796 pub fn kill_ring_yank(
10797 &mut self,
10798 _: &KillRingYank,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) {
10802 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10803 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10804 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10805 (kill_ring.text().to_string(), kill_ring.metadata_json())
10806 } else {
10807 return;
10808 }
10809 } else {
10810 return;
10811 };
10812 self.do_paste(&text, metadata, false, window, cx);
10813 }
10814
10815 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10816 self.do_copy(true, cx);
10817 }
10818
10819 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10820 self.do_copy(false, cx);
10821 }
10822
10823 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10824 let selections = self.selections.all::<Point>(cx);
10825 let buffer = self.buffer.read(cx).read(cx);
10826 let mut text = String::new();
10827
10828 let mut clipboard_selections = Vec::with_capacity(selections.len());
10829 {
10830 let max_point = buffer.max_point();
10831 let mut is_first = true;
10832 for selection in &selections {
10833 let mut start = selection.start;
10834 let mut end = selection.end;
10835 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10836 if is_entire_line {
10837 start = Point::new(start.row, 0);
10838 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10839 }
10840
10841 let mut trimmed_selections = Vec::new();
10842 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10843 let row = MultiBufferRow(start.row);
10844 let first_indent = buffer.indent_size_for_line(row);
10845 if first_indent.len == 0 || start.column > first_indent.len {
10846 trimmed_selections.push(start..end);
10847 } else {
10848 trimmed_selections.push(
10849 Point::new(row.0, first_indent.len)
10850 ..Point::new(row.0, buffer.line_len(row)),
10851 );
10852 for row in start.row + 1..=end.row {
10853 let mut line_len = buffer.line_len(MultiBufferRow(row));
10854 if row == end.row {
10855 line_len = end.column;
10856 }
10857 if line_len == 0 {
10858 trimmed_selections
10859 .push(Point::new(row, 0)..Point::new(row, line_len));
10860 continue;
10861 }
10862 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10863 if row_indent_size.len >= first_indent.len {
10864 trimmed_selections.push(
10865 Point::new(row, first_indent.len)..Point::new(row, line_len),
10866 );
10867 } else {
10868 trimmed_selections.clear();
10869 trimmed_selections.push(start..end);
10870 break;
10871 }
10872 }
10873 }
10874 } else {
10875 trimmed_selections.push(start..end);
10876 }
10877
10878 for trimmed_range in trimmed_selections {
10879 if is_first {
10880 is_first = false;
10881 } else {
10882 text += "\n";
10883 }
10884 let mut len = 0;
10885 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10886 text.push_str(chunk);
10887 len += chunk.len();
10888 }
10889 clipboard_selections.push(ClipboardSelection {
10890 len,
10891 is_entire_line,
10892 first_line_indent: buffer
10893 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10894 .len,
10895 });
10896 }
10897 }
10898 }
10899
10900 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10901 text,
10902 clipboard_selections,
10903 ));
10904 }
10905
10906 pub fn do_paste(
10907 &mut self,
10908 text: &String,
10909 clipboard_selections: Option<Vec<ClipboardSelection>>,
10910 handle_entire_lines: bool,
10911 window: &mut Window,
10912 cx: &mut Context<Self>,
10913 ) {
10914 if self.read_only(cx) {
10915 return;
10916 }
10917
10918 let clipboard_text = Cow::Borrowed(text);
10919
10920 self.transact(window, cx, |this, window, cx| {
10921 if let Some(mut clipboard_selections) = clipboard_selections {
10922 let old_selections = this.selections.all::<usize>(cx);
10923 let all_selections_were_entire_line =
10924 clipboard_selections.iter().all(|s| s.is_entire_line);
10925 let first_selection_indent_column =
10926 clipboard_selections.first().map(|s| s.first_line_indent);
10927 if clipboard_selections.len() != old_selections.len() {
10928 clipboard_selections.drain(..);
10929 }
10930 let cursor_offset = this.selections.last::<usize>(cx).head();
10931 let mut auto_indent_on_paste = true;
10932
10933 this.buffer.update(cx, |buffer, cx| {
10934 let snapshot = buffer.read(cx);
10935 auto_indent_on_paste = snapshot
10936 .language_settings_at(cursor_offset, cx)
10937 .auto_indent_on_paste;
10938
10939 let mut start_offset = 0;
10940 let mut edits = Vec::new();
10941 let mut original_indent_columns = Vec::new();
10942 for (ix, selection) in old_selections.iter().enumerate() {
10943 let to_insert;
10944 let entire_line;
10945 let original_indent_column;
10946 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10947 let end_offset = start_offset + clipboard_selection.len;
10948 to_insert = &clipboard_text[start_offset..end_offset];
10949 entire_line = clipboard_selection.is_entire_line;
10950 start_offset = end_offset + 1;
10951 original_indent_column = Some(clipboard_selection.first_line_indent);
10952 } else {
10953 to_insert = clipboard_text.as_str();
10954 entire_line = all_selections_were_entire_line;
10955 original_indent_column = first_selection_indent_column
10956 }
10957
10958 // If the corresponding selection was empty when this slice of the
10959 // clipboard text was written, then the entire line containing the
10960 // selection was copied. If this selection is also currently empty,
10961 // then paste the line before the current line of the buffer.
10962 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10963 let column = selection.start.to_point(&snapshot).column as usize;
10964 let line_start = selection.start - column;
10965 line_start..line_start
10966 } else {
10967 selection.range()
10968 };
10969
10970 edits.push((range, to_insert));
10971 original_indent_columns.push(original_indent_column);
10972 }
10973 drop(snapshot);
10974
10975 buffer.edit(
10976 edits,
10977 if auto_indent_on_paste {
10978 Some(AutoindentMode::Block {
10979 original_indent_columns,
10980 })
10981 } else {
10982 None
10983 },
10984 cx,
10985 );
10986 });
10987
10988 let selections = this.selections.all::<usize>(cx);
10989 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10990 s.select(selections)
10991 });
10992 } else {
10993 this.insert(&clipboard_text, window, cx);
10994 }
10995 });
10996 }
10997
10998 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10999 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11000 if let Some(item) = cx.read_from_clipboard() {
11001 let entries = item.entries();
11002
11003 match entries.first() {
11004 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11005 // of all the pasted entries.
11006 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11007 .do_paste(
11008 clipboard_string.text(),
11009 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11010 true,
11011 window,
11012 cx,
11013 ),
11014 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11015 }
11016 }
11017 }
11018
11019 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11020 if self.read_only(cx) {
11021 return;
11022 }
11023
11024 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11025
11026 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11027 if let Some((selections, _)) =
11028 self.selection_history.transaction(transaction_id).cloned()
11029 {
11030 self.change_selections(None, window, cx, |s| {
11031 s.select_anchors(selections.to_vec());
11032 });
11033 } else {
11034 log::error!(
11035 "No entry in selection_history found for undo. \
11036 This may correspond to a bug where undo does not update the selection. \
11037 If this is occurring, please add details to \
11038 https://github.com/zed-industries/zed/issues/22692"
11039 );
11040 }
11041 self.request_autoscroll(Autoscroll::fit(), cx);
11042 self.unmark_text(window, cx);
11043 self.refresh_inline_completion(true, false, window, cx);
11044 cx.emit(EditorEvent::Edited { transaction_id });
11045 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11046 }
11047 }
11048
11049 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11050 if self.read_only(cx) {
11051 return;
11052 }
11053
11054 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11055
11056 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11057 if let Some((_, Some(selections))) =
11058 self.selection_history.transaction(transaction_id).cloned()
11059 {
11060 self.change_selections(None, window, cx, |s| {
11061 s.select_anchors(selections.to_vec());
11062 });
11063 } else {
11064 log::error!(
11065 "No entry in selection_history found for redo. \
11066 This may correspond to a bug where undo does not update the selection. \
11067 If this is occurring, please add details to \
11068 https://github.com/zed-industries/zed/issues/22692"
11069 );
11070 }
11071 self.request_autoscroll(Autoscroll::fit(), cx);
11072 self.unmark_text(window, cx);
11073 self.refresh_inline_completion(true, false, window, cx);
11074 cx.emit(EditorEvent::Edited { transaction_id });
11075 }
11076 }
11077
11078 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11079 self.buffer
11080 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11081 }
11082
11083 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11084 self.buffer
11085 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11086 }
11087
11088 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11089 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11090 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11091 s.move_with(|map, selection| {
11092 let cursor = if selection.is_empty() {
11093 movement::left(map, selection.start)
11094 } else {
11095 selection.start
11096 };
11097 selection.collapse_to(cursor, SelectionGoal::None);
11098 });
11099 })
11100 }
11101
11102 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11103 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11104 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11105 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11106 })
11107 }
11108
11109 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11110 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11112 s.move_with(|map, selection| {
11113 let cursor = if selection.is_empty() {
11114 movement::right(map, selection.end)
11115 } else {
11116 selection.end
11117 };
11118 selection.collapse_to(cursor, SelectionGoal::None)
11119 });
11120 })
11121 }
11122
11123 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11126 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11127 })
11128 }
11129
11130 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11131 if self.take_rename(true, window, cx).is_some() {
11132 return;
11133 }
11134
11135 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11136 cx.propagate();
11137 return;
11138 }
11139
11140 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11141
11142 let text_layout_details = &self.text_layout_details(window);
11143 let selection_count = self.selections.count();
11144 let first_selection = self.selections.first_anchor();
11145
11146 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11147 s.move_with(|map, selection| {
11148 if !selection.is_empty() {
11149 selection.goal = SelectionGoal::None;
11150 }
11151 let (cursor, goal) = movement::up(
11152 map,
11153 selection.start,
11154 selection.goal,
11155 false,
11156 text_layout_details,
11157 );
11158 selection.collapse_to(cursor, goal);
11159 });
11160 });
11161
11162 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11163 {
11164 cx.propagate();
11165 }
11166 }
11167
11168 pub fn move_up_by_lines(
11169 &mut self,
11170 action: &MoveUpByLines,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 if self.take_rename(true, window, cx).is_some() {
11175 return;
11176 }
11177
11178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11179 cx.propagate();
11180 return;
11181 }
11182
11183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11184
11185 let text_layout_details = &self.text_layout_details(window);
11186
11187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11188 s.move_with(|map, selection| {
11189 if !selection.is_empty() {
11190 selection.goal = SelectionGoal::None;
11191 }
11192 let (cursor, goal) = movement::up_by_rows(
11193 map,
11194 selection.start,
11195 action.lines,
11196 selection.goal,
11197 false,
11198 text_layout_details,
11199 );
11200 selection.collapse_to(cursor, goal);
11201 });
11202 })
11203 }
11204
11205 pub fn move_down_by_lines(
11206 &mut self,
11207 action: &MoveDownByLines,
11208 window: &mut Window,
11209 cx: &mut Context<Self>,
11210 ) {
11211 if self.take_rename(true, window, cx).is_some() {
11212 return;
11213 }
11214
11215 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11216 cx.propagate();
11217 return;
11218 }
11219
11220 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11221
11222 let text_layout_details = &self.text_layout_details(window);
11223
11224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11225 s.move_with(|map, selection| {
11226 if !selection.is_empty() {
11227 selection.goal = SelectionGoal::None;
11228 }
11229 let (cursor, goal) = movement::down_by_rows(
11230 map,
11231 selection.start,
11232 action.lines,
11233 selection.goal,
11234 false,
11235 text_layout_details,
11236 );
11237 selection.collapse_to(cursor, goal);
11238 });
11239 })
11240 }
11241
11242 pub fn select_down_by_lines(
11243 &mut self,
11244 action: &SelectDownByLines,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11249 let text_layout_details = &self.text_layout_details(window);
11250 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11251 s.move_heads_with(|map, head, goal| {
11252 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11253 })
11254 })
11255 }
11256
11257 pub fn select_up_by_lines(
11258 &mut self,
11259 action: &SelectUpByLines,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11264 let text_layout_details = &self.text_layout_details(window);
11265 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11266 s.move_heads_with(|map, head, goal| {
11267 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11268 })
11269 })
11270 }
11271
11272 pub fn select_page_up(
11273 &mut self,
11274 _: &SelectPageUp,
11275 window: &mut Window,
11276 cx: &mut Context<Self>,
11277 ) {
11278 let Some(row_count) = self.visible_row_count() else {
11279 return;
11280 };
11281
11282 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11283
11284 let text_layout_details = &self.text_layout_details(window);
11285
11286 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11287 s.move_heads_with(|map, head, goal| {
11288 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11289 })
11290 })
11291 }
11292
11293 pub fn move_page_up(
11294 &mut self,
11295 action: &MovePageUp,
11296 window: &mut Window,
11297 cx: &mut Context<Self>,
11298 ) {
11299 if self.take_rename(true, window, cx).is_some() {
11300 return;
11301 }
11302
11303 if self
11304 .context_menu
11305 .borrow_mut()
11306 .as_mut()
11307 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11308 .unwrap_or(false)
11309 {
11310 return;
11311 }
11312
11313 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11314 cx.propagate();
11315 return;
11316 }
11317
11318 let Some(row_count) = self.visible_row_count() else {
11319 return;
11320 };
11321
11322 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11323
11324 let autoscroll = if action.center_cursor {
11325 Autoscroll::center()
11326 } else {
11327 Autoscroll::fit()
11328 };
11329
11330 let text_layout_details = &self.text_layout_details(window);
11331
11332 self.change_selections(Some(autoscroll), window, cx, |s| {
11333 s.move_with(|map, selection| {
11334 if !selection.is_empty() {
11335 selection.goal = SelectionGoal::None;
11336 }
11337 let (cursor, goal) = movement::up_by_rows(
11338 map,
11339 selection.end,
11340 row_count,
11341 selection.goal,
11342 false,
11343 text_layout_details,
11344 );
11345 selection.collapse_to(cursor, goal);
11346 });
11347 });
11348 }
11349
11350 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11351 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11352 let text_layout_details = &self.text_layout_details(window);
11353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11354 s.move_heads_with(|map, head, goal| {
11355 movement::up(map, head, goal, false, text_layout_details)
11356 })
11357 })
11358 }
11359
11360 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11361 self.take_rename(true, window, cx);
11362
11363 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11364 cx.propagate();
11365 return;
11366 }
11367
11368 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11369
11370 let text_layout_details = &self.text_layout_details(window);
11371 let selection_count = self.selections.count();
11372 let first_selection = self.selections.first_anchor();
11373
11374 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11375 s.move_with(|map, selection| {
11376 if !selection.is_empty() {
11377 selection.goal = SelectionGoal::None;
11378 }
11379 let (cursor, goal) = movement::down(
11380 map,
11381 selection.end,
11382 selection.goal,
11383 false,
11384 text_layout_details,
11385 );
11386 selection.collapse_to(cursor, goal);
11387 });
11388 });
11389
11390 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11391 {
11392 cx.propagate();
11393 }
11394 }
11395
11396 pub fn select_page_down(
11397 &mut self,
11398 _: &SelectPageDown,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 let Some(row_count) = self.visible_row_count() else {
11403 return;
11404 };
11405
11406 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11407
11408 let text_layout_details = &self.text_layout_details(window);
11409
11410 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11411 s.move_heads_with(|map, head, goal| {
11412 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11413 })
11414 })
11415 }
11416
11417 pub fn move_page_down(
11418 &mut self,
11419 action: &MovePageDown,
11420 window: &mut Window,
11421 cx: &mut Context<Self>,
11422 ) {
11423 if self.take_rename(true, window, cx).is_some() {
11424 return;
11425 }
11426
11427 if self
11428 .context_menu
11429 .borrow_mut()
11430 .as_mut()
11431 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11432 .unwrap_or(false)
11433 {
11434 return;
11435 }
11436
11437 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11438 cx.propagate();
11439 return;
11440 }
11441
11442 let Some(row_count) = self.visible_row_count() else {
11443 return;
11444 };
11445
11446 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11447
11448 let autoscroll = if action.center_cursor {
11449 Autoscroll::center()
11450 } else {
11451 Autoscroll::fit()
11452 };
11453
11454 let text_layout_details = &self.text_layout_details(window);
11455 self.change_selections(Some(autoscroll), window, cx, |s| {
11456 s.move_with(|map, selection| {
11457 if !selection.is_empty() {
11458 selection.goal = SelectionGoal::None;
11459 }
11460 let (cursor, goal) = movement::down_by_rows(
11461 map,
11462 selection.end,
11463 row_count,
11464 selection.goal,
11465 false,
11466 text_layout_details,
11467 );
11468 selection.collapse_to(cursor, goal);
11469 });
11470 });
11471 }
11472
11473 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11475 let text_layout_details = &self.text_layout_details(window);
11476 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11477 s.move_heads_with(|map, head, goal| {
11478 movement::down(map, head, goal, false, text_layout_details)
11479 })
11480 });
11481 }
11482
11483 pub fn context_menu_first(
11484 &mut self,
11485 _: &ContextMenuFirst,
11486 _window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11490 context_menu.select_first(self.completion_provider.as_deref(), cx);
11491 }
11492 }
11493
11494 pub fn context_menu_prev(
11495 &mut self,
11496 _: &ContextMenuPrevious,
11497 _window: &mut Window,
11498 cx: &mut Context<Self>,
11499 ) {
11500 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11501 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11502 }
11503 }
11504
11505 pub fn context_menu_next(
11506 &mut self,
11507 _: &ContextMenuNext,
11508 _window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11512 context_menu.select_next(self.completion_provider.as_deref(), cx);
11513 }
11514 }
11515
11516 pub fn context_menu_last(
11517 &mut self,
11518 _: &ContextMenuLast,
11519 _window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11523 context_menu.select_last(self.completion_provider.as_deref(), cx);
11524 }
11525 }
11526
11527 pub fn move_to_previous_word_start(
11528 &mut self,
11529 _: &MoveToPreviousWordStart,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11534 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11535 s.move_cursors_with(|map, head, _| {
11536 (
11537 movement::previous_word_start(map, head),
11538 SelectionGoal::None,
11539 )
11540 });
11541 })
11542 }
11543
11544 pub fn move_to_previous_subword_start(
11545 &mut self,
11546 _: &MoveToPreviousSubwordStart,
11547 window: &mut Window,
11548 cx: &mut Context<Self>,
11549 ) {
11550 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11551 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11552 s.move_cursors_with(|map, head, _| {
11553 (
11554 movement::previous_subword_start(map, head),
11555 SelectionGoal::None,
11556 )
11557 });
11558 })
11559 }
11560
11561 pub fn select_to_previous_word_start(
11562 &mut self,
11563 _: &SelectToPreviousWordStart,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 ) {
11567 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11569 s.move_heads_with(|map, head, _| {
11570 (
11571 movement::previous_word_start(map, head),
11572 SelectionGoal::None,
11573 )
11574 });
11575 })
11576 }
11577
11578 pub fn select_to_previous_subword_start(
11579 &mut self,
11580 _: &SelectToPreviousSubwordStart,
11581 window: &mut Window,
11582 cx: &mut Context<Self>,
11583 ) {
11584 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11585 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11586 s.move_heads_with(|map, head, _| {
11587 (
11588 movement::previous_subword_start(map, head),
11589 SelectionGoal::None,
11590 )
11591 });
11592 })
11593 }
11594
11595 pub fn delete_to_previous_word_start(
11596 &mut self,
11597 action: &DeleteToPreviousWordStart,
11598 window: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11602 self.transact(window, cx, |this, window, cx| {
11603 this.select_autoclose_pair(window, cx);
11604 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11605 s.move_with(|map, selection| {
11606 if selection.is_empty() {
11607 let cursor = if action.ignore_newlines {
11608 movement::previous_word_start(map, selection.head())
11609 } else {
11610 movement::previous_word_start_or_newline(map, selection.head())
11611 };
11612 selection.set_head(cursor, SelectionGoal::None);
11613 }
11614 });
11615 });
11616 this.insert("", window, cx);
11617 });
11618 }
11619
11620 pub fn delete_to_previous_subword_start(
11621 &mut self,
11622 _: &DeleteToPreviousSubwordStart,
11623 window: &mut Window,
11624 cx: &mut Context<Self>,
11625 ) {
11626 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11627 self.transact(window, cx, |this, window, cx| {
11628 this.select_autoclose_pair(window, cx);
11629 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11630 s.move_with(|map, selection| {
11631 if selection.is_empty() {
11632 let cursor = movement::previous_subword_start(map, selection.head());
11633 selection.set_head(cursor, SelectionGoal::None);
11634 }
11635 });
11636 });
11637 this.insert("", window, cx);
11638 });
11639 }
11640
11641 pub fn move_to_next_word_end(
11642 &mut self,
11643 _: &MoveToNextWordEnd,
11644 window: &mut Window,
11645 cx: &mut Context<Self>,
11646 ) {
11647 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11648 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11649 s.move_cursors_with(|map, head, _| {
11650 (movement::next_word_end(map, head), SelectionGoal::None)
11651 });
11652 })
11653 }
11654
11655 pub fn move_to_next_subword_end(
11656 &mut self,
11657 _: &MoveToNextSubwordEnd,
11658 window: &mut Window,
11659 cx: &mut Context<Self>,
11660 ) {
11661 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11662 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11663 s.move_cursors_with(|map, head, _| {
11664 (movement::next_subword_end(map, head), SelectionGoal::None)
11665 });
11666 })
11667 }
11668
11669 pub fn select_to_next_word_end(
11670 &mut self,
11671 _: &SelectToNextWordEnd,
11672 window: &mut Window,
11673 cx: &mut Context<Self>,
11674 ) {
11675 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11676 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11677 s.move_heads_with(|map, head, _| {
11678 (movement::next_word_end(map, head), SelectionGoal::None)
11679 });
11680 })
11681 }
11682
11683 pub fn select_to_next_subword_end(
11684 &mut self,
11685 _: &SelectToNextSubwordEnd,
11686 window: &mut Window,
11687 cx: &mut Context<Self>,
11688 ) {
11689 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11690 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11691 s.move_heads_with(|map, head, _| {
11692 (movement::next_subword_end(map, head), SelectionGoal::None)
11693 });
11694 })
11695 }
11696
11697 pub fn delete_to_next_word_end(
11698 &mut self,
11699 action: &DeleteToNextWordEnd,
11700 window: &mut Window,
11701 cx: &mut Context<Self>,
11702 ) {
11703 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11704 self.transact(window, cx, |this, window, cx| {
11705 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11706 s.move_with(|map, selection| {
11707 if selection.is_empty() {
11708 let cursor = if action.ignore_newlines {
11709 movement::next_word_end(map, selection.head())
11710 } else {
11711 movement::next_word_end_or_newline(map, selection.head())
11712 };
11713 selection.set_head(cursor, SelectionGoal::None);
11714 }
11715 });
11716 });
11717 this.insert("", window, cx);
11718 });
11719 }
11720
11721 pub fn delete_to_next_subword_end(
11722 &mut self,
11723 _: &DeleteToNextSubwordEnd,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11728 self.transact(window, cx, |this, window, cx| {
11729 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11730 s.move_with(|map, selection| {
11731 if selection.is_empty() {
11732 let cursor = movement::next_subword_end(map, selection.head());
11733 selection.set_head(cursor, SelectionGoal::None);
11734 }
11735 });
11736 });
11737 this.insert("", window, cx);
11738 });
11739 }
11740
11741 pub fn move_to_beginning_of_line(
11742 &mut self,
11743 action: &MoveToBeginningOfLine,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11748 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11749 s.move_cursors_with(|map, head, _| {
11750 (
11751 movement::indented_line_beginning(
11752 map,
11753 head,
11754 action.stop_at_soft_wraps,
11755 action.stop_at_indent,
11756 ),
11757 SelectionGoal::None,
11758 )
11759 });
11760 })
11761 }
11762
11763 pub fn select_to_beginning_of_line(
11764 &mut self,
11765 action: &SelectToBeginningOfLine,
11766 window: &mut Window,
11767 cx: &mut Context<Self>,
11768 ) {
11769 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11770 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11771 s.move_heads_with(|map, head, _| {
11772 (
11773 movement::indented_line_beginning(
11774 map,
11775 head,
11776 action.stop_at_soft_wraps,
11777 action.stop_at_indent,
11778 ),
11779 SelectionGoal::None,
11780 )
11781 });
11782 });
11783 }
11784
11785 pub fn delete_to_beginning_of_line(
11786 &mut self,
11787 action: &DeleteToBeginningOfLine,
11788 window: &mut Window,
11789 cx: &mut Context<Self>,
11790 ) {
11791 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11792 self.transact(window, cx, |this, window, cx| {
11793 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.move_with(|_, selection| {
11795 selection.reversed = true;
11796 });
11797 });
11798
11799 this.select_to_beginning_of_line(
11800 &SelectToBeginningOfLine {
11801 stop_at_soft_wraps: false,
11802 stop_at_indent: action.stop_at_indent,
11803 },
11804 window,
11805 cx,
11806 );
11807 this.backspace(&Backspace, window, cx);
11808 });
11809 }
11810
11811 pub fn move_to_end_of_line(
11812 &mut self,
11813 action: &MoveToEndOfLine,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11819 s.move_cursors_with(|map, head, _| {
11820 (
11821 movement::line_end(map, head, action.stop_at_soft_wraps),
11822 SelectionGoal::None,
11823 )
11824 });
11825 })
11826 }
11827
11828 pub fn select_to_end_of_line(
11829 &mut self,
11830 action: &SelectToEndOfLine,
11831 window: &mut Window,
11832 cx: &mut Context<Self>,
11833 ) {
11834 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11835 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11836 s.move_heads_with(|map, head, _| {
11837 (
11838 movement::line_end(map, head, action.stop_at_soft_wraps),
11839 SelectionGoal::None,
11840 )
11841 });
11842 })
11843 }
11844
11845 pub fn delete_to_end_of_line(
11846 &mut self,
11847 _: &DeleteToEndOfLine,
11848 window: &mut Window,
11849 cx: &mut Context<Self>,
11850 ) {
11851 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11852 self.transact(window, cx, |this, window, cx| {
11853 this.select_to_end_of_line(
11854 &SelectToEndOfLine {
11855 stop_at_soft_wraps: false,
11856 },
11857 window,
11858 cx,
11859 );
11860 this.delete(&Delete, window, cx);
11861 });
11862 }
11863
11864 pub fn cut_to_end_of_line(
11865 &mut self,
11866 _: &CutToEndOfLine,
11867 window: &mut Window,
11868 cx: &mut Context<Self>,
11869 ) {
11870 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11871 self.transact(window, cx, |this, window, cx| {
11872 this.select_to_end_of_line(
11873 &SelectToEndOfLine {
11874 stop_at_soft_wraps: false,
11875 },
11876 window,
11877 cx,
11878 );
11879 this.cut(&Cut, window, cx);
11880 });
11881 }
11882
11883 pub fn move_to_start_of_paragraph(
11884 &mut self,
11885 _: &MoveToStartOfParagraph,
11886 window: &mut Window,
11887 cx: &mut Context<Self>,
11888 ) {
11889 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11890 cx.propagate();
11891 return;
11892 }
11893 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11894 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11895 s.move_with(|map, selection| {
11896 selection.collapse_to(
11897 movement::start_of_paragraph(map, selection.head(), 1),
11898 SelectionGoal::None,
11899 )
11900 });
11901 })
11902 }
11903
11904 pub fn move_to_end_of_paragraph(
11905 &mut self,
11906 _: &MoveToEndOfParagraph,
11907 window: &mut Window,
11908 cx: &mut Context<Self>,
11909 ) {
11910 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11911 cx.propagate();
11912 return;
11913 }
11914 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11915 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11916 s.move_with(|map, selection| {
11917 selection.collapse_to(
11918 movement::end_of_paragraph(map, selection.head(), 1),
11919 SelectionGoal::None,
11920 )
11921 });
11922 })
11923 }
11924
11925 pub fn select_to_start_of_paragraph(
11926 &mut self,
11927 _: &SelectToStartOfParagraph,
11928 window: &mut Window,
11929 cx: &mut Context<Self>,
11930 ) {
11931 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11932 cx.propagate();
11933 return;
11934 }
11935 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11936 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11937 s.move_heads_with(|map, head, _| {
11938 (
11939 movement::start_of_paragraph(map, head, 1),
11940 SelectionGoal::None,
11941 )
11942 });
11943 })
11944 }
11945
11946 pub fn select_to_end_of_paragraph(
11947 &mut self,
11948 _: &SelectToEndOfParagraph,
11949 window: &mut Window,
11950 cx: &mut Context<Self>,
11951 ) {
11952 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11953 cx.propagate();
11954 return;
11955 }
11956 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11957 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11958 s.move_heads_with(|map, head, _| {
11959 (
11960 movement::end_of_paragraph(map, head, 1),
11961 SelectionGoal::None,
11962 )
11963 });
11964 })
11965 }
11966
11967 pub fn move_to_start_of_excerpt(
11968 &mut self,
11969 _: &MoveToStartOfExcerpt,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11974 cx.propagate();
11975 return;
11976 }
11977 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11978 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11979 s.move_with(|map, selection| {
11980 selection.collapse_to(
11981 movement::start_of_excerpt(
11982 map,
11983 selection.head(),
11984 workspace::searchable::Direction::Prev,
11985 ),
11986 SelectionGoal::None,
11987 )
11988 });
11989 })
11990 }
11991
11992 pub fn move_to_start_of_next_excerpt(
11993 &mut self,
11994 _: &MoveToStartOfNextExcerpt,
11995 window: &mut Window,
11996 cx: &mut Context<Self>,
11997 ) {
11998 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11999 cx.propagate();
12000 return;
12001 }
12002
12003 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12004 s.move_with(|map, selection| {
12005 selection.collapse_to(
12006 movement::start_of_excerpt(
12007 map,
12008 selection.head(),
12009 workspace::searchable::Direction::Next,
12010 ),
12011 SelectionGoal::None,
12012 )
12013 });
12014 })
12015 }
12016
12017 pub fn move_to_end_of_excerpt(
12018 &mut self,
12019 _: &MoveToEndOfExcerpt,
12020 window: &mut Window,
12021 cx: &mut Context<Self>,
12022 ) {
12023 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12024 cx.propagate();
12025 return;
12026 }
12027 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12028 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12029 s.move_with(|map, selection| {
12030 selection.collapse_to(
12031 movement::end_of_excerpt(
12032 map,
12033 selection.head(),
12034 workspace::searchable::Direction::Next,
12035 ),
12036 SelectionGoal::None,
12037 )
12038 });
12039 })
12040 }
12041
12042 pub fn move_to_end_of_previous_excerpt(
12043 &mut self,
12044 _: &MoveToEndOfPreviousExcerpt,
12045 window: &mut Window,
12046 cx: &mut Context<Self>,
12047 ) {
12048 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12049 cx.propagate();
12050 return;
12051 }
12052 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12053 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12054 s.move_with(|map, selection| {
12055 selection.collapse_to(
12056 movement::end_of_excerpt(
12057 map,
12058 selection.head(),
12059 workspace::searchable::Direction::Prev,
12060 ),
12061 SelectionGoal::None,
12062 )
12063 });
12064 })
12065 }
12066
12067 pub fn select_to_start_of_excerpt(
12068 &mut self,
12069 _: &SelectToStartOfExcerpt,
12070 window: &mut Window,
12071 cx: &mut Context<Self>,
12072 ) {
12073 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12074 cx.propagate();
12075 return;
12076 }
12077 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12078 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12079 s.move_heads_with(|map, head, _| {
12080 (
12081 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12082 SelectionGoal::None,
12083 )
12084 });
12085 })
12086 }
12087
12088 pub fn select_to_start_of_next_excerpt(
12089 &mut self,
12090 _: &SelectToStartOfNextExcerpt,
12091 window: &mut Window,
12092 cx: &mut Context<Self>,
12093 ) {
12094 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12095 cx.propagate();
12096 return;
12097 }
12098 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12099 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12100 s.move_heads_with(|map, head, _| {
12101 (
12102 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12103 SelectionGoal::None,
12104 )
12105 });
12106 })
12107 }
12108
12109 pub fn select_to_end_of_excerpt(
12110 &mut self,
12111 _: &SelectToEndOfExcerpt,
12112 window: &mut Window,
12113 cx: &mut Context<Self>,
12114 ) {
12115 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12116 cx.propagate();
12117 return;
12118 }
12119 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12120 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12121 s.move_heads_with(|map, head, _| {
12122 (
12123 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12124 SelectionGoal::None,
12125 )
12126 });
12127 })
12128 }
12129
12130 pub fn select_to_end_of_previous_excerpt(
12131 &mut self,
12132 _: &SelectToEndOfPreviousExcerpt,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12137 cx.propagate();
12138 return;
12139 }
12140 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12141 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12142 s.move_heads_with(|map, head, _| {
12143 (
12144 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12145 SelectionGoal::None,
12146 )
12147 });
12148 })
12149 }
12150
12151 pub fn move_to_beginning(
12152 &mut self,
12153 _: &MoveToBeginning,
12154 window: &mut Window,
12155 cx: &mut Context<Self>,
12156 ) {
12157 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12158 cx.propagate();
12159 return;
12160 }
12161 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12162 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12163 s.select_ranges(vec![0..0]);
12164 });
12165 }
12166
12167 pub fn select_to_beginning(
12168 &mut self,
12169 _: &SelectToBeginning,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 let mut selection = self.selections.last::<Point>(cx);
12174 selection.set_head(Point::zero(), SelectionGoal::None);
12175 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12176 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12177 s.select(vec![selection]);
12178 });
12179 }
12180
12181 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12182 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12183 cx.propagate();
12184 return;
12185 }
12186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12187 let cursor = self.buffer.read(cx).read(cx).len();
12188 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12189 s.select_ranges(vec![cursor..cursor])
12190 });
12191 }
12192
12193 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12194 self.nav_history = nav_history;
12195 }
12196
12197 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12198 self.nav_history.as_ref()
12199 }
12200
12201 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12202 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12203 }
12204
12205 fn push_to_nav_history(
12206 &mut self,
12207 cursor_anchor: Anchor,
12208 new_position: Option<Point>,
12209 is_deactivate: bool,
12210 cx: &mut Context<Self>,
12211 ) {
12212 if let Some(nav_history) = self.nav_history.as_mut() {
12213 let buffer = self.buffer.read(cx).read(cx);
12214 let cursor_position = cursor_anchor.to_point(&buffer);
12215 let scroll_state = self.scroll_manager.anchor();
12216 let scroll_top_row = scroll_state.top_row(&buffer);
12217 drop(buffer);
12218
12219 if let Some(new_position) = new_position {
12220 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12221 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12222 return;
12223 }
12224 }
12225
12226 nav_history.push(
12227 Some(NavigationData {
12228 cursor_anchor,
12229 cursor_position,
12230 scroll_anchor: scroll_state,
12231 scroll_top_row,
12232 }),
12233 cx,
12234 );
12235 cx.emit(EditorEvent::PushedToNavHistory {
12236 anchor: cursor_anchor,
12237 is_deactivate,
12238 })
12239 }
12240 }
12241
12242 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12243 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12244 let buffer = self.buffer.read(cx).snapshot(cx);
12245 let mut selection = self.selections.first::<usize>(cx);
12246 selection.set_head(buffer.len(), SelectionGoal::None);
12247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12248 s.select(vec![selection]);
12249 });
12250 }
12251
12252 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12253 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12254 let end = self.buffer.read(cx).read(cx).len();
12255 self.change_selections(None, window, cx, |s| {
12256 s.select_ranges(vec![0..end]);
12257 });
12258 }
12259
12260 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12261 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12262 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12263 let mut selections = self.selections.all::<Point>(cx);
12264 let max_point = display_map.buffer_snapshot.max_point();
12265 for selection in &mut selections {
12266 let rows = selection.spanned_rows(true, &display_map);
12267 selection.start = Point::new(rows.start.0, 0);
12268 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12269 selection.reversed = false;
12270 }
12271 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12272 s.select(selections);
12273 });
12274 }
12275
12276 pub fn split_selection_into_lines(
12277 &mut self,
12278 _: &SplitSelectionIntoLines,
12279 window: &mut Window,
12280 cx: &mut Context<Self>,
12281 ) {
12282 let selections = self
12283 .selections
12284 .all::<Point>(cx)
12285 .into_iter()
12286 .map(|selection| selection.start..selection.end)
12287 .collect::<Vec<_>>();
12288 self.unfold_ranges(&selections, true, true, cx);
12289
12290 let mut new_selection_ranges = Vec::new();
12291 {
12292 let buffer = self.buffer.read(cx).read(cx);
12293 for selection in selections {
12294 for row in selection.start.row..selection.end.row {
12295 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12296 new_selection_ranges.push(cursor..cursor);
12297 }
12298
12299 let is_multiline_selection = selection.start.row != selection.end.row;
12300 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12301 // so this action feels more ergonomic when paired with other selection operations
12302 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12303 if !should_skip_last {
12304 new_selection_ranges.push(selection.end..selection.end);
12305 }
12306 }
12307 }
12308 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12309 s.select_ranges(new_selection_ranges);
12310 });
12311 }
12312
12313 pub fn add_selection_above(
12314 &mut self,
12315 _: &AddSelectionAbove,
12316 window: &mut Window,
12317 cx: &mut Context<Self>,
12318 ) {
12319 self.add_selection(true, window, cx);
12320 }
12321
12322 pub fn add_selection_below(
12323 &mut self,
12324 _: &AddSelectionBelow,
12325 window: &mut Window,
12326 cx: &mut Context<Self>,
12327 ) {
12328 self.add_selection(false, window, cx);
12329 }
12330
12331 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12332 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12333
12334 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12335 let mut selections = self.selections.all::<Point>(cx);
12336 let text_layout_details = self.text_layout_details(window);
12337 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12338 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12339 let range = oldest_selection.display_range(&display_map).sorted();
12340
12341 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12342 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12343 let positions = start_x.min(end_x)..start_x.max(end_x);
12344
12345 selections.clear();
12346 let mut stack = Vec::new();
12347 for row in range.start.row().0..=range.end.row().0 {
12348 if let Some(selection) = self.selections.build_columnar_selection(
12349 &display_map,
12350 DisplayRow(row),
12351 &positions,
12352 oldest_selection.reversed,
12353 &text_layout_details,
12354 ) {
12355 stack.push(selection.id);
12356 selections.push(selection);
12357 }
12358 }
12359
12360 if above {
12361 stack.reverse();
12362 }
12363
12364 AddSelectionsState { above, stack }
12365 });
12366
12367 let last_added_selection = *state.stack.last().unwrap();
12368 let mut new_selections = Vec::new();
12369 if above == state.above {
12370 let end_row = if above {
12371 DisplayRow(0)
12372 } else {
12373 display_map.max_point().row()
12374 };
12375
12376 'outer: for selection in selections {
12377 if selection.id == last_added_selection {
12378 let range = selection.display_range(&display_map).sorted();
12379 debug_assert_eq!(range.start.row(), range.end.row());
12380 let mut row = range.start.row();
12381 let positions =
12382 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12383 px(start)..px(end)
12384 } else {
12385 let start_x =
12386 display_map.x_for_display_point(range.start, &text_layout_details);
12387 let end_x =
12388 display_map.x_for_display_point(range.end, &text_layout_details);
12389 start_x.min(end_x)..start_x.max(end_x)
12390 };
12391
12392 while row != end_row {
12393 if above {
12394 row.0 -= 1;
12395 } else {
12396 row.0 += 1;
12397 }
12398
12399 if let Some(new_selection) = self.selections.build_columnar_selection(
12400 &display_map,
12401 row,
12402 &positions,
12403 selection.reversed,
12404 &text_layout_details,
12405 ) {
12406 state.stack.push(new_selection.id);
12407 if above {
12408 new_selections.push(new_selection);
12409 new_selections.push(selection);
12410 } else {
12411 new_selections.push(selection);
12412 new_selections.push(new_selection);
12413 }
12414
12415 continue 'outer;
12416 }
12417 }
12418 }
12419
12420 new_selections.push(selection);
12421 }
12422 } else {
12423 new_selections = selections;
12424 new_selections.retain(|s| s.id != last_added_selection);
12425 state.stack.pop();
12426 }
12427
12428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12429 s.select(new_selections);
12430 });
12431 if state.stack.len() > 1 {
12432 self.add_selections_state = Some(state);
12433 }
12434 }
12435
12436 fn select_match_ranges(
12437 &mut self,
12438 range: Range<usize>,
12439 reversed: bool,
12440 replace_newest: bool,
12441 auto_scroll: Option<Autoscroll>,
12442 window: &mut Window,
12443 cx: &mut Context<Editor>,
12444 ) {
12445 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12446 self.change_selections(auto_scroll, window, cx, |s| {
12447 if replace_newest {
12448 s.delete(s.newest_anchor().id);
12449 }
12450 if reversed {
12451 s.insert_range(range.end..range.start);
12452 } else {
12453 s.insert_range(range);
12454 }
12455 });
12456 }
12457
12458 pub fn select_next_match_internal(
12459 &mut self,
12460 display_map: &DisplaySnapshot,
12461 replace_newest: bool,
12462 autoscroll: Option<Autoscroll>,
12463 window: &mut Window,
12464 cx: &mut Context<Self>,
12465 ) -> Result<()> {
12466 let buffer = &display_map.buffer_snapshot;
12467 let mut selections = self.selections.all::<usize>(cx);
12468 if let Some(mut select_next_state) = self.select_next_state.take() {
12469 let query = &select_next_state.query;
12470 if !select_next_state.done {
12471 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12472 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12473 let mut next_selected_range = None;
12474
12475 let bytes_after_last_selection =
12476 buffer.bytes_in_range(last_selection.end..buffer.len());
12477 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12478 let query_matches = query
12479 .stream_find_iter(bytes_after_last_selection)
12480 .map(|result| (last_selection.end, result))
12481 .chain(
12482 query
12483 .stream_find_iter(bytes_before_first_selection)
12484 .map(|result| (0, result)),
12485 );
12486
12487 for (start_offset, query_match) in query_matches {
12488 let query_match = query_match.unwrap(); // can only fail due to I/O
12489 let offset_range =
12490 start_offset + query_match.start()..start_offset + query_match.end();
12491 let display_range = offset_range.start.to_display_point(display_map)
12492 ..offset_range.end.to_display_point(display_map);
12493
12494 if !select_next_state.wordwise
12495 || (!movement::is_inside_word(display_map, display_range.start)
12496 && !movement::is_inside_word(display_map, display_range.end))
12497 {
12498 // TODO: This is n^2, because we might check all the selections
12499 if !selections
12500 .iter()
12501 .any(|selection| selection.range().overlaps(&offset_range))
12502 {
12503 next_selected_range = Some(offset_range);
12504 break;
12505 }
12506 }
12507 }
12508
12509 if let Some(next_selected_range) = next_selected_range {
12510 self.select_match_ranges(
12511 next_selected_range,
12512 last_selection.reversed,
12513 replace_newest,
12514 autoscroll,
12515 window,
12516 cx,
12517 );
12518 } else {
12519 select_next_state.done = true;
12520 }
12521 }
12522
12523 self.select_next_state = Some(select_next_state);
12524 } else {
12525 let mut only_carets = true;
12526 let mut same_text_selected = true;
12527 let mut selected_text = None;
12528
12529 let mut selections_iter = selections.iter().peekable();
12530 while let Some(selection) = selections_iter.next() {
12531 if selection.start != selection.end {
12532 only_carets = false;
12533 }
12534
12535 if same_text_selected {
12536 if selected_text.is_none() {
12537 selected_text =
12538 Some(buffer.text_for_range(selection.range()).collect::<String>());
12539 }
12540
12541 if let Some(next_selection) = selections_iter.peek() {
12542 if next_selection.range().len() == selection.range().len() {
12543 let next_selected_text = buffer
12544 .text_for_range(next_selection.range())
12545 .collect::<String>();
12546 if Some(next_selected_text) != selected_text {
12547 same_text_selected = false;
12548 selected_text = None;
12549 }
12550 } else {
12551 same_text_selected = false;
12552 selected_text = None;
12553 }
12554 }
12555 }
12556 }
12557
12558 if only_carets {
12559 for selection in &mut selections {
12560 let word_range = movement::surrounding_word(
12561 display_map,
12562 selection.start.to_display_point(display_map),
12563 );
12564 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12565 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12566 selection.goal = SelectionGoal::None;
12567 selection.reversed = false;
12568 self.select_match_ranges(
12569 selection.start..selection.end,
12570 selection.reversed,
12571 replace_newest,
12572 autoscroll,
12573 window,
12574 cx,
12575 );
12576 }
12577
12578 if selections.len() == 1 {
12579 let selection = selections
12580 .last()
12581 .expect("ensured that there's only one selection");
12582 let query = buffer
12583 .text_for_range(selection.start..selection.end)
12584 .collect::<String>();
12585 let is_empty = query.is_empty();
12586 let select_state = SelectNextState {
12587 query: AhoCorasick::new(&[query])?,
12588 wordwise: true,
12589 done: is_empty,
12590 };
12591 self.select_next_state = Some(select_state);
12592 } else {
12593 self.select_next_state = None;
12594 }
12595 } else if let Some(selected_text) = selected_text {
12596 self.select_next_state = Some(SelectNextState {
12597 query: AhoCorasick::new(&[selected_text])?,
12598 wordwise: false,
12599 done: false,
12600 });
12601 self.select_next_match_internal(
12602 display_map,
12603 replace_newest,
12604 autoscroll,
12605 window,
12606 cx,
12607 )?;
12608 }
12609 }
12610 Ok(())
12611 }
12612
12613 pub fn select_all_matches(
12614 &mut self,
12615 _action: &SelectAllMatches,
12616 window: &mut Window,
12617 cx: &mut Context<Self>,
12618 ) -> Result<()> {
12619 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12620
12621 self.push_to_selection_history();
12622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12623
12624 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12625 let Some(select_next_state) = self.select_next_state.as_mut() else {
12626 return Ok(());
12627 };
12628 if select_next_state.done {
12629 return Ok(());
12630 }
12631
12632 let mut new_selections = Vec::new();
12633
12634 let reversed = self.selections.oldest::<usize>(cx).reversed;
12635 let buffer = &display_map.buffer_snapshot;
12636 let query_matches = select_next_state
12637 .query
12638 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12639
12640 for query_match in query_matches.into_iter() {
12641 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12642 let offset_range = if reversed {
12643 query_match.end()..query_match.start()
12644 } else {
12645 query_match.start()..query_match.end()
12646 };
12647 let display_range = offset_range.start.to_display_point(&display_map)
12648 ..offset_range.end.to_display_point(&display_map);
12649
12650 if !select_next_state.wordwise
12651 || (!movement::is_inside_word(&display_map, display_range.start)
12652 && !movement::is_inside_word(&display_map, display_range.end))
12653 {
12654 new_selections.push(offset_range.start..offset_range.end);
12655 }
12656 }
12657
12658 select_next_state.done = true;
12659 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12660 self.change_selections(None, window, cx, |selections| {
12661 selections.select_ranges(new_selections)
12662 });
12663
12664 Ok(())
12665 }
12666
12667 pub fn select_next(
12668 &mut self,
12669 action: &SelectNext,
12670 window: &mut Window,
12671 cx: &mut Context<Self>,
12672 ) -> Result<()> {
12673 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12674 self.push_to_selection_history();
12675 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12676 self.select_next_match_internal(
12677 &display_map,
12678 action.replace_newest,
12679 Some(Autoscroll::newest()),
12680 window,
12681 cx,
12682 )?;
12683 Ok(())
12684 }
12685
12686 pub fn select_previous(
12687 &mut self,
12688 action: &SelectPrevious,
12689 window: &mut Window,
12690 cx: &mut Context<Self>,
12691 ) -> Result<()> {
12692 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12693 self.push_to_selection_history();
12694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12695 let buffer = &display_map.buffer_snapshot;
12696 let mut selections = self.selections.all::<usize>(cx);
12697 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12698 let query = &select_prev_state.query;
12699 if !select_prev_state.done {
12700 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12701 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12702 let mut next_selected_range = None;
12703 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12704 let bytes_before_last_selection =
12705 buffer.reversed_bytes_in_range(0..last_selection.start);
12706 let bytes_after_first_selection =
12707 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12708 let query_matches = query
12709 .stream_find_iter(bytes_before_last_selection)
12710 .map(|result| (last_selection.start, result))
12711 .chain(
12712 query
12713 .stream_find_iter(bytes_after_first_selection)
12714 .map(|result| (buffer.len(), result)),
12715 );
12716 for (end_offset, query_match) in query_matches {
12717 let query_match = query_match.unwrap(); // can only fail due to I/O
12718 let offset_range =
12719 end_offset - query_match.end()..end_offset - query_match.start();
12720 let display_range = offset_range.start.to_display_point(&display_map)
12721 ..offset_range.end.to_display_point(&display_map);
12722
12723 if !select_prev_state.wordwise
12724 || (!movement::is_inside_word(&display_map, display_range.start)
12725 && !movement::is_inside_word(&display_map, display_range.end))
12726 {
12727 next_selected_range = Some(offset_range);
12728 break;
12729 }
12730 }
12731
12732 if let Some(next_selected_range) = next_selected_range {
12733 self.select_match_ranges(
12734 next_selected_range,
12735 last_selection.reversed,
12736 action.replace_newest,
12737 Some(Autoscroll::newest()),
12738 window,
12739 cx,
12740 );
12741 } else {
12742 select_prev_state.done = true;
12743 }
12744 }
12745
12746 self.select_prev_state = Some(select_prev_state);
12747 } else {
12748 let mut only_carets = true;
12749 let mut same_text_selected = true;
12750 let mut selected_text = None;
12751
12752 let mut selections_iter = selections.iter().peekable();
12753 while let Some(selection) = selections_iter.next() {
12754 if selection.start != selection.end {
12755 only_carets = false;
12756 }
12757
12758 if same_text_selected {
12759 if selected_text.is_none() {
12760 selected_text =
12761 Some(buffer.text_for_range(selection.range()).collect::<String>());
12762 }
12763
12764 if let Some(next_selection) = selections_iter.peek() {
12765 if next_selection.range().len() == selection.range().len() {
12766 let next_selected_text = buffer
12767 .text_for_range(next_selection.range())
12768 .collect::<String>();
12769 if Some(next_selected_text) != selected_text {
12770 same_text_selected = false;
12771 selected_text = None;
12772 }
12773 } else {
12774 same_text_selected = false;
12775 selected_text = None;
12776 }
12777 }
12778 }
12779 }
12780
12781 if only_carets {
12782 for selection in &mut selections {
12783 let word_range = movement::surrounding_word(
12784 &display_map,
12785 selection.start.to_display_point(&display_map),
12786 );
12787 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12788 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12789 selection.goal = SelectionGoal::None;
12790 selection.reversed = false;
12791 self.select_match_ranges(
12792 selection.start..selection.end,
12793 selection.reversed,
12794 action.replace_newest,
12795 Some(Autoscroll::newest()),
12796 window,
12797 cx,
12798 );
12799 }
12800 if selections.len() == 1 {
12801 let selection = selections
12802 .last()
12803 .expect("ensured that there's only one selection");
12804 let query = buffer
12805 .text_for_range(selection.start..selection.end)
12806 .collect::<String>();
12807 let is_empty = query.is_empty();
12808 let select_state = SelectNextState {
12809 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12810 wordwise: true,
12811 done: is_empty,
12812 };
12813 self.select_prev_state = Some(select_state);
12814 } else {
12815 self.select_prev_state = None;
12816 }
12817 } else if let Some(selected_text) = selected_text {
12818 self.select_prev_state = Some(SelectNextState {
12819 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12820 wordwise: false,
12821 done: false,
12822 });
12823 self.select_previous(action, window, cx)?;
12824 }
12825 }
12826 Ok(())
12827 }
12828
12829 pub fn find_next_match(
12830 &mut self,
12831 _: &FindNextMatch,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) -> Result<()> {
12835 let selections = self.selections.disjoint_anchors();
12836 match selections.first() {
12837 Some(first) if selections.len() >= 2 => {
12838 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12839 s.select_ranges([first.range()]);
12840 });
12841 }
12842 _ => self.select_next(
12843 &SelectNext {
12844 replace_newest: true,
12845 },
12846 window,
12847 cx,
12848 )?,
12849 }
12850 Ok(())
12851 }
12852
12853 pub fn find_previous_match(
12854 &mut self,
12855 _: &FindPreviousMatch,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) -> Result<()> {
12859 let selections = self.selections.disjoint_anchors();
12860 match selections.last() {
12861 Some(last) if selections.len() >= 2 => {
12862 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12863 s.select_ranges([last.range()]);
12864 });
12865 }
12866 _ => self.select_previous(
12867 &SelectPrevious {
12868 replace_newest: true,
12869 },
12870 window,
12871 cx,
12872 )?,
12873 }
12874 Ok(())
12875 }
12876
12877 pub fn toggle_comments(
12878 &mut self,
12879 action: &ToggleComments,
12880 window: &mut Window,
12881 cx: &mut Context<Self>,
12882 ) {
12883 if self.read_only(cx) {
12884 return;
12885 }
12886 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12887 let text_layout_details = &self.text_layout_details(window);
12888 self.transact(window, cx, |this, window, cx| {
12889 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12890 let mut edits = Vec::new();
12891 let mut selection_edit_ranges = Vec::new();
12892 let mut last_toggled_row = None;
12893 let snapshot = this.buffer.read(cx).read(cx);
12894 let empty_str: Arc<str> = Arc::default();
12895 let mut suffixes_inserted = Vec::new();
12896 let ignore_indent = action.ignore_indent;
12897
12898 fn comment_prefix_range(
12899 snapshot: &MultiBufferSnapshot,
12900 row: MultiBufferRow,
12901 comment_prefix: &str,
12902 comment_prefix_whitespace: &str,
12903 ignore_indent: bool,
12904 ) -> Range<Point> {
12905 let indent_size = if ignore_indent {
12906 0
12907 } else {
12908 snapshot.indent_size_for_line(row).len
12909 };
12910
12911 let start = Point::new(row.0, indent_size);
12912
12913 let mut line_bytes = snapshot
12914 .bytes_in_range(start..snapshot.max_point())
12915 .flatten()
12916 .copied();
12917
12918 // If this line currently begins with the line comment prefix, then record
12919 // the range containing the prefix.
12920 if line_bytes
12921 .by_ref()
12922 .take(comment_prefix.len())
12923 .eq(comment_prefix.bytes())
12924 {
12925 // Include any whitespace that matches the comment prefix.
12926 let matching_whitespace_len = line_bytes
12927 .zip(comment_prefix_whitespace.bytes())
12928 .take_while(|(a, b)| a == b)
12929 .count() as u32;
12930 let end = Point::new(
12931 start.row,
12932 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12933 );
12934 start..end
12935 } else {
12936 start..start
12937 }
12938 }
12939
12940 fn comment_suffix_range(
12941 snapshot: &MultiBufferSnapshot,
12942 row: MultiBufferRow,
12943 comment_suffix: &str,
12944 comment_suffix_has_leading_space: bool,
12945 ) -> Range<Point> {
12946 let end = Point::new(row.0, snapshot.line_len(row));
12947 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12948
12949 let mut line_end_bytes = snapshot
12950 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12951 .flatten()
12952 .copied();
12953
12954 let leading_space_len = if suffix_start_column > 0
12955 && line_end_bytes.next() == Some(b' ')
12956 && comment_suffix_has_leading_space
12957 {
12958 1
12959 } else {
12960 0
12961 };
12962
12963 // If this line currently begins with the line comment prefix, then record
12964 // the range containing the prefix.
12965 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12966 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12967 start..end
12968 } else {
12969 end..end
12970 }
12971 }
12972
12973 // TODO: Handle selections that cross excerpts
12974 for selection in &mut selections {
12975 let start_column = snapshot
12976 .indent_size_for_line(MultiBufferRow(selection.start.row))
12977 .len;
12978 let language = if let Some(language) =
12979 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12980 {
12981 language
12982 } else {
12983 continue;
12984 };
12985
12986 selection_edit_ranges.clear();
12987
12988 // If multiple selections contain a given row, avoid processing that
12989 // row more than once.
12990 let mut start_row = MultiBufferRow(selection.start.row);
12991 if last_toggled_row == Some(start_row) {
12992 start_row = start_row.next_row();
12993 }
12994 let end_row =
12995 if selection.end.row > selection.start.row && selection.end.column == 0 {
12996 MultiBufferRow(selection.end.row - 1)
12997 } else {
12998 MultiBufferRow(selection.end.row)
12999 };
13000 last_toggled_row = Some(end_row);
13001
13002 if start_row > end_row {
13003 continue;
13004 }
13005
13006 // If the language has line comments, toggle those.
13007 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13008
13009 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13010 if ignore_indent {
13011 full_comment_prefixes = full_comment_prefixes
13012 .into_iter()
13013 .map(|s| Arc::from(s.trim_end()))
13014 .collect();
13015 }
13016
13017 if !full_comment_prefixes.is_empty() {
13018 let first_prefix = full_comment_prefixes
13019 .first()
13020 .expect("prefixes is non-empty");
13021 let prefix_trimmed_lengths = full_comment_prefixes
13022 .iter()
13023 .map(|p| p.trim_end_matches(' ').len())
13024 .collect::<SmallVec<[usize; 4]>>();
13025
13026 let mut all_selection_lines_are_comments = true;
13027
13028 for row in start_row.0..=end_row.0 {
13029 let row = MultiBufferRow(row);
13030 if start_row < end_row && snapshot.is_line_blank(row) {
13031 continue;
13032 }
13033
13034 let prefix_range = full_comment_prefixes
13035 .iter()
13036 .zip(prefix_trimmed_lengths.iter().copied())
13037 .map(|(prefix, trimmed_prefix_len)| {
13038 comment_prefix_range(
13039 snapshot.deref(),
13040 row,
13041 &prefix[..trimmed_prefix_len],
13042 &prefix[trimmed_prefix_len..],
13043 ignore_indent,
13044 )
13045 })
13046 .max_by_key(|range| range.end.column - range.start.column)
13047 .expect("prefixes is non-empty");
13048
13049 if prefix_range.is_empty() {
13050 all_selection_lines_are_comments = false;
13051 }
13052
13053 selection_edit_ranges.push(prefix_range);
13054 }
13055
13056 if all_selection_lines_are_comments {
13057 edits.extend(
13058 selection_edit_ranges
13059 .iter()
13060 .cloned()
13061 .map(|range| (range, empty_str.clone())),
13062 );
13063 } else {
13064 let min_column = selection_edit_ranges
13065 .iter()
13066 .map(|range| range.start.column)
13067 .min()
13068 .unwrap_or(0);
13069 edits.extend(selection_edit_ranges.iter().map(|range| {
13070 let position = Point::new(range.start.row, min_column);
13071 (position..position, first_prefix.clone())
13072 }));
13073 }
13074 } else if let Some((full_comment_prefix, comment_suffix)) =
13075 language.block_comment_delimiters()
13076 {
13077 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13078 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13079 let prefix_range = comment_prefix_range(
13080 snapshot.deref(),
13081 start_row,
13082 comment_prefix,
13083 comment_prefix_whitespace,
13084 ignore_indent,
13085 );
13086 let suffix_range = comment_suffix_range(
13087 snapshot.deref(),
13088 end_row,
13089 comment_suffix.trim_start_matches(' '),
13090 comment_suffix.starts_with(' '),
13091 );
13092
13093 if prefix_range.is_empty() || suffix_range.is_empty() {
13094 edits.push((
13095 prefix_range.start..prefix_range.start,
13096 full_comment_prefix.clone(),
13097 ));
13098 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13099 suffixes_inserted.push((end_row, comment_suffix.len()));
13100 } else {
13101 edits.push((prefix_range, empty_str.clone()));
13102 edits.push((suffix_range, empty_str.clone()));
13103 }
13104 } else {
13105 continue;
13106 }
13107 }
13108
13109 drop(snapshot);
13110 this.buffer.update(cx, |buffer, cx| {
13111 buffer.edit(edits, None, cx);
13112 });
13113
13114 // Adjust selections so that they end before any comment suffixes that
13115 // were inserted.
13116 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13117 let mut selections = this.selections.all::<Point>(cx);
13118 let snapshot = this.buffer.read(cx).read(cx);
13119 for selection in &mut selections {
13120 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13121 match row.cmp(&MultiBufferRow(selection.end.row)) {
13122 Ordering::Less => {
13123 suffixes_inserted.next();
13124 continue;
13125 }
13126 Ordering::Greater => break,
13127 Ordering::Equal => {
13128 if selection.end.column == snapshot.line_len(row) {
13129 if selection.is_empty() {
13130 selection.start.column -= suffix_len as u32;
13131 }
13132 selection.end.column -= suffix_len as u32;
13133 }
13134 break;
13135 }
13136 }
13137 }
13138 }
13139
13140 drop(snapshot);
13141 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13142 s.select(selections)
13143 });
13144
13145 let selections = this.selections.all::<Point>(cx);
13146 let selections_on_single_row = selections.windows(2).all(|selections| {
13147 selections[0].start.row == selections[1].start.row
13148 && selections[0].end.row == selections[1].end.row
13149 && selections[0].start.row == selections[0].end.row
13150 });
13151 let selections_selecting = selections
13152 .iter()
13153 .any(|selection| selection.start != selection.end);
13154 let advance_downwards = action.advance_downwards
13155 && selections_on_single_row
13156 && !selections_selecting
13157 && !matches!(this.mode, EditorMode::SingleLine { .. });
13158
13159 if advance_downwards {
13160 let snapshot = this.buffer.read(cx).snapshot(cx);
13161
13162 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13163 s.move_cursors_with(|display_snapshot, display_point, _| {
13164 let mut point = display_point.to_point(display_snapshot);
13165 point.row += 1;
13166 point = snapshot.clip_point(point, Bias::Left);
13167 let display_point = point.to_display_point(display_snapshot);
13168 let goal = SelectionGoal::HorizontalPosition(
13169 display_snapshot
13170 .x_for_display_point(display_point, text_layout_details)
13171 .into(),
13172 );
13173 (display_point, goal)
13174 })
13175 });
13176 }
13177 });
13178 }
13179
13180 pub fn select_enclosing_symbol(
13181 &mut self,
13182 _: &SelectEnclosingSymbol,
13183 window: &mut Window,
13184 cx: &mut Context<Self>,
13185 ) {
13186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13187
13188 let buffer = self.buffer.read(cx).snapshot(cx);
13189 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13190
13191 fn update_selection(
13192 selection: &Selection<usize>,
13193 buffer_snap: &MultiBufferSnapshot,
13194 ) -> Option<Selection<usize>> {
13195 let cursor = selection.head();
13196 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13197 for symbol in symbols.iter().rev() {
13198 let start = symbol.range.start.to_offset(buffer_snap);
13199 let end = symbol.range.end.to_offset(buffer_snap);
13200 let new_range = start..end;
13201 if start < selection.start || end > selection.end {
13202 return Some(Selection {
13203 id: selection.id,
13204 start: new_range.start,
13205 end: new_range.end,
13206 goal: SelectionGoal::None,
13207 reversed: selection.reversed,
13208 });
13209 }
13210 }
13211 None
13212 }
13213
13214 let mut selected_larger_symbol = false;
13215 let new_selections = old_selections
13216 .iter()
13217 .map(|selection| match update_selection(selection, &buffer) {
13218 Some(new_selection) => {
13219 if new_selection.range() != selection.range() {
13220 selected_larger_symbol = true;
13221 }
13222 new_selection
13223 }
13224 None => selection.clone(),
13225 })
13226 .collect::<Vec<_>>();
13227
13228 if selected_larger_symbol {
13229 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13230 s.select(new_selections);
13231 });
13232 }
13233 }
13234
13235 pub fn select_larger_syntax_node(
13236 &mut self,
13237 _: &SelectLargerSyntaxNode,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 let Some(visible_row_count) = self.visible_row_count() else {
13242 return;
13243 };
13244 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13245 if old_selections.is_empty() {
13246 return;
13247 }
13248
13249 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13250
13251 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13252 let buffer = self.buffer.read(cx).snapshot(cx);
13253
13254 let mut selected_larger_node = false;
13255 let mut new_selections = old_selections
13256 .iter()
13257 .map(|selection| {
13258 let old_range = selection.start..selection.end;
13259
13260 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13261 // manually select word at selection
13262 if ["string_content", "inline"].contains(&node.kind()) {
13263 let word_range = {
13264 let display_point = buffer
13265 .offset_to_point(old_range.start)
13266 .to_display_point(&display_map);
13267 let Range { start, end } =
13268 movement::surrounding_word(&display_map, display_point);
13269 start.to_point(&display_map).to_offset(&buffer)
13270 ..end.to_point(&display_map).to_offset(&buffer)
13271 };
13272 // ignore if word is already selected
13273 if !word_range.is_empty() && old_range != word_range {
13274 let last_word_range = {
13275 let display_point = buffer
13276 .offset_to_point(old_range.end)
13277 .to_display_point(&display_map);
13278 let Range { start, end } =
13279 movement::surrounding_word(&display_map, display_point);
13280 start.to_point(&display_map).to_offset(&buffer)
13281 ..end.to_point(&display_map).to_offset(&buffer)
13282 };
13283 // only select word if start and end point belongs to same word
13284 if word_range == last_word_range {
13285 selected_larger_node = true;
13286 return Selection {
13287 id: selection.id,
13288 start: word_range.start,
13289 end: word_range.end,
13290 goal: SelectionGoal::None,
13291 reversed: selection.reversed,
13292 };
13293 }
13294 }
13295 }
13296 }
13297
13298 let mut new_range = old_range.clone();
13299 while let Some((_node, containing_range)) =
13300 buffer.syntax_ancestor(new_range.clone())
13301 {
13302 new_range = match containing_range {
13303 MultiOrSingleBufferOffsetRange::Single(_) => break,
13304 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13305 };
13306 if !display_map.intersects_fold(new_range.start)
13307 && !display_map.intersects_fold(new_range.end)
13308 {
13309 break;
13310 }
13311 }
13312
13313 selected_larger_node |= new_range != old_range;
13314 Selection {
13315 id: selection.id,
13316 start: new_range.start,
13317 end: new_range.end,
13318 goal: SelectionGoal::None,
13319 reversed: selection.reversed,
13320 }
13321 })
13322 .collect::<Vec<_>>();
13323
13324 if !selected_larger_node {
13325 return; // don't put this call in the history
13326 }
13327
13328 // scroll based on transformation done to the last selection created by the user
13329 let (last_old, last_new) = old_selections
13330 .last()
13331 .zip(new_selections.last().cloned())
13332 .expect("old_selections isn't empty");
13333
13334 // revert selection
13335 let is_selection_reversed = {
13336 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13337 new_selections.last_mut().expect("checked above").reversed =
13338 should_newest_selection_be_reversed;
13339 should_newest_selection_be_reversed
13340 };
13341
13342 if selected_larger_node {
13343 self.select_syntax_node_history.disable_clearing = true;
13344 self.change_selections(None, window, cx, |s| {
13345 s.select(new_selections.clone());
13346 });
13347 self.select_syntax_node_history.disable_clearing = false;
13348 }
13349
13350 let start_row = last_new.start.to_display_point(&display_map).row().0;
13351 let end_row = last_new.end.to_display_point(&display_map).row().0;
13352 let selection_height = end_row - start_row + 1;
13353 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13354
13355 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13356 let scroll_behavior = if fits_on_the_screen {
13357 self.request_autoscroll(Autoscroll::fit(), cx);
13358 SelectSyntaxNodeScrollBehavior::FitSelection
13359 } else if is_selection_reversed {
13360 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13361 SelectSyntaxNodeScrollBehavior::CursorTop
13362 } else {
13363 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13364 SelectSyntaxNodeScrollBehavior::CursorBottom
13365 };
13366
13367 self.select_syntax_node_history.push((
13368 old_selections,
13369 scroll_behavior,
13370 is_selection_reversed,
13371 ));
13372 }
13373
13374 pub fn select_smaller_syntax_node(
13375 &mut self,
13376 _: &SelectSmallerSyntaxNode,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13381
13382 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13383 self.select_syntax_node_history.pop()
13384 {
13385 if let Some(selection) = selections.last_mut() {
13386 selection.reversed = is_selection_reversed;
13387 }
13388
13389 self.select_syntax_node_history.disable_clearing = true;
13390 self.change_selections(None, window, cx, |s| {
13391 s.select(selections.to_vec());
13392 });
13393 self.select_syntax_node_history.disable_clearing = false;
13394
13395 match scroll_behavior {
13396 SelectSyntaxNodeScrollBehavior::CursorTop => {
13397 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13398 }
13399 SelectSyntaxNodeScrollBehavior::FitSelection => {
13400 self.request_autoscroll(Autoscroll::fit(), cx);
13401 }
13402 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13403 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13404 }
13405 }
13406 }
13407 }
13408
13409 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13410 if !EditorSettings::get_global(cx).gutter.runnables {
13411 self.clear_tasks();
13412 return Task::ready(());
13413 }
13414 let project = self.project.as_ref().map(Entity::downgrade);
13415 let task_sources = self.lsp_task_sources(cx);
13416 cx.spawn_in(window, async move |editor, cx| {
13417 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13418 let Some(project) = project.and_then(|p| p.upgrade()) else {
13419 return;
13420 };
13421 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13422 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13423 }) else {
13424 return;
13425 };
13426
13427 let hide_runnables = project
13428 .update(cx, |project, cx| {
13429 // Do not display any test indicators in non-dev server remote projects.
13430 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13431 })
13432 .unwrap_or(true);
13433 if hide_runnables {
13434 return;
13435 }
13436 let new_rows =
13437 cx.background_spawn({
13438 let snapshot = display_snapshot.clone();
13439 async move {
13440 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13441 }
13442 })
13443 .await;
13444 let Ok(lsp_tasks) =
13445 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13446 else {
13447 return;
13448 };
13449 let lsp_tasks = lsp_tasks.await;
13450
13451 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13452 lsp_tasks
13453 .into_iter()
13454 .flat_map(|(kind, tasks)| {
13455 tasks.into_iter().filter_map(move |(location, task)| {
13456 Some((kind.clone(), location?, task))
13457 })
13458 })
13459 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13460 let buffer = location.target.buffer;
13461 let buffer_snapshot = buffer.read(cx).snapshot();
13462 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13463 |(excerpt_id, snapshot, _)| {
13464 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13465 display_snapshot
13466 .buffer_snapshot
13467 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13468 } else {
13469 None
13470 }
13471 },
13472 );
13473 if let Some(offset) = offset {
13474 let task_buffer_range =
13475 location.target.range.to_point(&buffer_snapshot);
13476 let context_buffer_range =
13477 task_buffer_range.to_offset(&buffer_snapshot);
13478 let context_range = BufferOffset(context_buffer_range.start)
13479 ..BufferOffset(context_buffer_range.end);
13480
13481 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13482 .or_insert_with(|| RunnableTasks {
13483 templates: Vec::new(),
13484 offset,
13485 column: task_buffer_range.start.column,
13486 extra_variables: HashMap::default(),
13487 context_range,
13488 })
13489 .templates
13490 .push((kind, task.original_task().clone()));
13491 }
13492
13493 acc
13494 })
13495 }) else {
13496 return;
13497 };
13498
13499 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13500 editor
13501 .update(cx, |editor, _| {
13502 editor.clear_tasks();
13503 for (key, mut value) in rows {
13504 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13505 value.templates.extend(lsp_tasks.templates);
13506 }
13507
13508 editor.insert_tasks(key, value);
13509 }
13510 for (key, value) in lsp_tasks_by_rows {
13511 editor.insert_tasks(key, value);
13512 }
13513 })
13514 .ok();
13515 })
13516 }
13517 fn fetch_runnable_ranges(
13518 snapshot: &DisplaySnapshot,
13519 range: Range<Anchor>,
13520 ) -> Vec<language::RunnableRange> {
13521 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13522 }
13523
13524 fn runnable_rows(
13525 project: Entity<Project>,
13526 snapshot: DisplaySnapshot,
13527 runnable_ranges: Vec<RunnableRange>,
13528 mut cx: AsyncWindowContext,
13529 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13530 runnable_ranges
13531 .into_iter()
13532 .filter_map(|mut runnable| {
13533 let tasks = cx
13534 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13535 .ok()?;
13536 if tasks.is_empty() {
13537 return None;
13538 }
13539
13540 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13541
13542 let row = snapshot
13543 .buffer_snapshot
13544 .buffer_line_for_row(MultiBufferRow(point.row))?
13545 .1
13546 .start
13547 .row;
13548
13549 let context_range =
13550 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13551 Some((
13552 (runnable.buffer_id, row),
13553 RunnableTasks {
13554 templates: tasks,
13555 offset: snapshot
13556 .buffer_snapshot
13557 .anchor_before(runnable.run_range.start),
13558 context_range,
13559 column: point.column,
13560 extra_variables: runnable.extra_captures,
13561 },
13562 ))
13563 })
13564 .collect()
13565 }
13566
13567 fn templates_with_tags(
13568 project: &Entity<Project>,
13569 runnable: &mut Runnable,
13570 cx: &mut App,
13571 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13572 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13573 let (worktree_id, file) = project
13574 .buffer_for_id(runnable.buffer, cx)
13575 .and_then(|buffer| buffer.read(cx).file())
13576 .map(|file| (file.worktree_id(cx), file.clone()))
13577 .unzip();
13578
13579 (
13580 project.task_store().read(cx).task_inventory().cloned(),
13581 worktree_id,
13582 file,
13583 )
13584 });
13585
13586 let mut templates_with_tags = mem::take(&mut runnable.tags)
13587 .into_iter()
13588 .flat_map(|RunnableTag(tag)| {
13589 inventory
13590 .as_ref()
13591 .into_iter()
13592 .flat_map(|inventory| {
13593 inventory.read(cx).list_tasks(
13594 file.clone(),
13595 Some(runnable.language.clone()),
13596 worktree_id,
13597 cx,
13598 )
13599 })
13600 .filter(move |(_, template)| {
13601 template.tags.iter().any(|source_tag| source_tag == &tag)
13602 })
13603 })
13604 .sorted_by_key(|(kind, _)| kind.to_owned())
13605 .collect::<Vec<_>>();
13606 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13607 // Strongest source wins; if we have worktree tag binding, prefer that to
13608 // global and language bindings;
13609 // if we have a global binding, prefer that to language binding.
13610 let first_mismatch = templates_with_tags
13611 .iter()
13612 .position(|(tag_source, _)| tag_source != leading_tag_source);
13613 if let Some(index) = first_mismatch {
13614 templates_with_tags.truncate(index);
13615 }
13616 }
13617
13618 templates_with_tags
13619 }
13620
13621 pub fn move_to_enclosing_bracket(
13622 &mut self,
13623 _: &MoveToEnclosingBracket,
13624 window: &mut Window,
13625 cx: &mut Context<Self>,
13626 ) {
13627 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13628 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13629 s.move_offsets_with(|snapshot, selection| {
13630 let Some(enclosing_bracket_ranges) =
13631 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13632 else {
13633 return;
13634 };
13635
13636 let mut best_length = usize::MAX;
13637 let mut best_inside = false;
13638 let mut best_in_bracket_range = false;
13639 let mut best_destination = None;
13640 for (open, close) in enclosing_bracket_ranges {
13641 let close = close.to_inclusive();
13642 let length = close.end() - open.start;
13643 let inside = selection.start >= open.end && selection.end <= *close.start();
13644 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13645 || close.contains(&selection.head());
13646
13647 // If best is next to a bracket and current isn't, skip
13648 if !in_bracket_range && best_in_bracket_range {
13649 continue;
13650 }
13651
13652 // Prefer smaller lengths unless best is inside and current isn't
13653 if length > best_length && (best_inside || !inside) {
13654 continue;
13655 }
13656
13657 best_length = length;
13658 best_inside = inside;
13659 best_in_bracket_range = in_bracket_range;
13660 best_destination = Some(
13661 if close.contains(&selection.start) && close.contains(&selection.end) {
13662 if inside { open.end } else { open.start }
13663 } else if inside {
13664 *close.start()
13665 } else {
13666 *close.end()
13667 },
13668 );
13669 }
13670
13671 if let Some(destination) = best_destination {
13672 selection.collapse_to(destination, SelectionGoal::None);
13673 }
13674 })
13675 });
13676 }
13677
13678 pub fn undo_selection(
13679 &mut self,
13680 _: &UndoSelection,
13681 window: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13685 self.end_selection(window, cx);
13686 self.selection_history.mode = SelectionHistoryMode::Undoing;
13687 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13688 self.change_selections(None, window, cx, |s| {
13689 s.select_anchors(entry.selections.to_vec())
13690 });
13691 self.select_next_state = entry.select_next_state;
13692 self.select_prev_state = entry.select_prev_state;
13693 self.add_selections_state = entry.add_selections_state;
13694 self.request_autoscroll(Autoscroll::newest(), cx);
13695 }
13696 self.selection_history.mode = SelectionHistoryMode::Normal;
13697 }
13698
13699 pub fn redo_selection(
13700 &mut self,
13701 _: &RedoSelection,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13706 self.end_selection(window, cx);
13707 self.selection_history.mode = SelectionHistoryMode::Redoing;
13708 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13709 self.change_selections(None, window, cx, |s| {
13710 s.select_anchors(entry.selections.to_vec())
13711 });
13712 self.select_next_state = entry.select_next_state;
13713 self.select_prev_state = entry.select_prev_state;
13714 self.add_selections_state = entry.add_selections_state;
13715 self.request_autoscroll(Autoscroll::newest(), cx);
13716 }
13717 self.selection_history.mode = SelectionHistoryMode::Normal;
13718 }
13719
13720 pub fn expand_excerpts(
13721 &mut self,
13722 action: &ExpandExcerpts,
13723 _: &mut Window,
13724 cx: &mut Context<Self>,
13725 ) {
13726 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13727 }
13728
13729 pub fn expand_excerpts_down(
13730 &mut self,
13731 action: &ExpandExcerptsDown,
13732 _: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13736 }
13737
13738 pub fn expand_excerpts_up(
13739 &mut self,
13740 action: &ExpandExcerptsUp,
13741 _: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13745 }
13746
13747 pub fn expand_excerpts_for_direction(
13748 &mut self,
13749 lines: u32,
13750 direction: ExpandExcerptDirection,
13751
13752 cx: &mut Context<Self>,
13753 ) {
13754 let selections = self.selections.disjoint_anchors();
13755
13756 let lines = if lines == 0 {
13757 EditorSettings::get_global(cx).expand_excerpt_lines
13758 } else {
13759 lines
13760 };
13761
13762 self.buffer.update(cx, |buffer, cx| {
13763 let snapshot = buffer.snapshot(cx);
13764 let mut excerpt_ids = selections
13765 .iter()
13766 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13767 .collect::<Vec<_>>();
13768 excerpt_ids.sort();
13769 excerpt_ids.dedup();
13770 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13771 })
13772 }
13773
13774 pub fn expand_excerpt(
13775 &mut self,
13776 excerpt: ExcerptId,
13777 direction: ExpandExcerptDirection,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 let current_scroll_position = self.scroll_position(cx);
13782 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13783 let mut should_scroll_up = false;
13784
13785 if direction == ExpandExcerptDirection::Down {
13786 let multi_buffer = self.buffer.read(cx);
13787 let snapshot = multi_buffer.snapshot(cx);
13788 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13789 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13790 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13791 let buffer_snapshot = buffer.read(cx).snapshot();
13792 let excerpt_end_row =
13793 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13794 let last_row = buffer_snapshot.max_point().row;
13795 let lines_below = last_row.saturating_sub(excerpt_end_row);
13796 should_scroll_up = lines_below >= lines_to_expand;
13797 }
13798 }
13799 }
13800 }
13801
13802 self.buffer.update(cx, |buffer, cx| {
13803 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13804 });
13805
13806 if should_scroll_up {
13807 let new_scroll_position =
13808 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13809 self.set_scroll_position(new_scroll_position, window, cx);
13810 }
13811 }
13812
13813 pub fn go_to_singleton_buffer_point(
13814 &mut self,
13815 point: Point,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 self.go_to_singleton_buffer_range(point..point, window, cx);
13820 }
13821
13822 pub fn go_to_singleton_buffer_range(
13823 &mut self,
13824 range: Range<Point>,
13825 window: &mut Window,
13826 cx: &mut Context<Self>,
13827 ) {
13828 let multibuffer = self.buffer().read(cx);
13829 let Some(buffer) = multibuffer.as_singleton() else {
13830 return;
13831 };
13832 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13833 return;
13834 };
13835 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13836 return;
13837 };
13838 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13839 s.select_anchor_ranges([start..end])
13840 });
13841 }
13842
13843 pub fn go_to_diagnostic(
13844 &mut self,
13845 _: &GoToDiagnostic,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) {
13849 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13850 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13851 }
13852
13853 pub fn go_to_prev_diagnostic(
13854 &mut self,
13855 _: &GoToPreviousDiagnostic,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13860 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13861 }
13862
13863 pub fn go_to_diagnostic_impl(
13864 &mut self,
13865 direction: Direction,
13866 window: &mut Window,
13867 cx: &mut Context<Self>,
13868 ) {
13869 let buffer = self.buffer.read(cx).snapshot(cx);
13870 let selection = self.selections.newest::<usize>(cx);
13871
13872 let mut active_group_id = None;
13873 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13874 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13875 active_group_id = Some(active_group.group_id);
13876 }
13877 }
13878
13879 fn filtered(
13880 snapshot: EditorSnapshot,
13881 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13882 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13883 diagnostics
13884 .filter(|entry| entry.range.start != entry.range.end)
13885 .filter(|entry| !entry.diagnostic.is_unnecessary)
13886 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13887 }
13888
13889 let snapshot = self.snapshot(window, cx);
13890 let before = filtered(
13891 snapshot.clone(),
13892 buffer
13893 .diagnostics_in_range(0..selection.start)
13894 .filter(|entry| entry.range.start <= selection.start),
13895 );
13896 let after = filtered(
13897 snapshot,
13898 buffer
13899 .diagnostics_in_range(selection.start..buffer.len())
13900 .filter(|entry| entry.range.start >= selection.start),
13901 );
13902
13903 let mut found: Option<DiagnosticEntry<usize>> = None;
13904 if direction == Direction::Prev {
13905 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13906 {
13907 for diagnostic in prev_diagnostics.into_iter().rev() {
13908 if diagnostic.range.start != selection.start
13909 || active_group_id
13910 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13911 {
13912 found = Some(diagnostic);
13913 break 'outer;
13914 }
13915 }
13916 }
13917 } else {
13918 for diagnostic in after.chain(before) {
13919 if diagnostic.range.start != selection.start
13920 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13921 {
13922 found = Some(diagnostic);
13923 break;
13924 }
13925 }
13926 }
13927 let Some(next_diagnostic) = found else {
13928 return;
13929 };
13930
13931 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13932 return;
13933 };
13934 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13935 s.select_ranges(vec![
13936 next_diagnostic.range.start..next_diagnostic.range.start,
13937 ])
13938 });
13939 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13940 self.refresh_inline_completion(false, true, window, cx);
13941 }
13942
13943 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13945 let snapshot = self.snapshot(window, cx);
13946 let selection = self.selections.newest::<Point>(cx);
13947 self.go_to_hunk_before_or_after_position(
13948 &snapshot,
13949 selection.head(),
13950 Direction::Next,
13951 window,
13952 cx,
13953 );
13954 }
13955
13956 pub fn go_to_hunk_before_or_after_position(
13957 &mut self,
13958 snapshot: &EditorSnapshot,
13959 position: Point,
13960 direction: Direction,
13961 window: &mut Window,
13962 cx: &mut Context<Editor>,
13963 ) {
13964 let row = if direction == Direction::Next {
13965 self.hunk_after_position(snapshot, position)
13966 .map(|hunk| hunk.row_range.start)
13967 } else {
13968 self.hunk_before_position(snapshot, position)
13969 };
13970
13971 if let Some(row) = row {
13972 let destination = Point::new(row.0, 0);
13973 let autoscroll = Autoscroll::center();
13974
13975 self.unfold_ranges(&[destination..destination], false, false, cx);
13976 self.change_selections(Some(autoscroll), window, cx, |s| {
13977 s.select_ranges([destination..destination]);
13978 });
13979 }
13980 }
13981
13982 fn hunk_after_position(
13983 &mut self,
13984 snapshot: &EditorSnapshot,
13985 position: Point,
13986 ) -> Option<MultiBufferDiffHunk> {
13987 snapshot
13988 .buffer_snapshot
13989 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13990 .find(|hunk| hunk.row_range.start.0 > position.row)
13991 .or_else(|| {
13992 snapshot
13993 .buffer_snapshot
13994 .diff_hunks_in_range(Point::zero()..position)
13995 .find(|hunk| hunk.row_range.end.0 < position.row)
13996 })
13997 }
13998
13999 fn go_to_prev_hunk(
14000 &mut self,
14001 _: &GoToPreviousHunk,
14002 window: &mut Window,
14003 cx: &mut Context<Self>,
14004 ) {
14005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14006 let snapshot = self.snapshot(window, cx);
14007 let selection = self.selections.newest::<Point>(cx);
14008 self.go_to_hunk_before_or_after_position(
14009 &snapshot,
14010 selection.head(),
14011 Direction::Prev,
14012 window,
14013 cx,
14014 );
14015 }
14016
14017 fn hunk_before_position(
14018 &mut self,
14019 snapshot: &EditorSnapshot,
14020 position: Point,
14021 ) -> Option<MultiBufferRow> {
14022 snapshot
14023 .buffer_snapshot
14024 .diff_hunk_before(position)
14025 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14026 }
14027
14028 fn go_to_next_change(
14029 &mut self,
14030 _: &GoToNextChange,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 if let Some(selections) = self
14035 .change_list
14036 .next_change(1, Direction::Next)
14037 .map(|s| s.to_vec())
14038 {
14039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14040 let map = s.display_map();
14041 s.select_display_ranges(selections.iter().map(|a| {
14042 let point = a.to_display_point(&map);
14043 point..point
14044 }))
14045 })
14046 }
14047 }
14048
14049 fn go_to_previous_change(
14050 &mut self,
14051 _: &GoToPreviousChange,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 if let Some(selections) = self
14056 .change_list
14057 .next_change(1, Direction::Prev)
14058 .map(|s| s.to_vec())
14059 {
14060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14061 let map = s.display_map();
14062 s.select_display_ranges(selections.iter().map(|a| {
14063 let point = a.to_display_point(&map);
14064 point..point
14065 }))
14066 })
14067 }
14068 }
14069
14070 fn go_to_line<T: 'static>(
14071 &mut self,
14072 position: Anchor,
14073 highlight_color: Option<Hsla>,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) {
14077 let snapshot = self.snapshot(window, cx).display_snapshot;
14078 let position = position.to_point(&snapshot.buffer_snapshot);
14079 let start = snapshot
14080 .buffer_snapshot
14081 .clip_point(Point::new(position.row, 0), Bias::Left);
14082 let end = start + Point::new(1, 0);
14083 let start = snapshot.buffer_snapshot.anchor_before(start);
14084 let end = snapshot.buffer_snapshot.anchor_before(end);
14085
14086 self.highlight_rows::<T>(
14087 start..end,
14088 highlight_color
14089 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14090 Default::default(),
14091 cx,
14092 );
14093
14094 if self.buffer.read(cx).is_singleton() {
14095 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14096 }
14097 }
14098
14099 pub fn go_to_definition(
14100 &mut self,
14101 _: &GoToDefinition,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) -> Task<Result<Navigated>> {
14105 let definition =
14106 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14107 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14108 cx.spawn_in(window, async move |editor, cx| {
14109 if definition.await? == Navigated::Yes {
14110 return Ok(Navigated::Yes);
14111 }
14112 match fallback_strategy {
14113 GoToDefinitionFallback::None => Ok(Navigated::No),
14114 GoToDefinitionFallback::FindAllReferences => {
14115 match editor.update_in(cx, |editor, window, cx| {
14116 editor.find_all_references(&FindAllReferences, window, cx)
14117 })? {
14118 Some(references) => references.await,
14119 None => Ok(Navigated::No),
14120 }
14121 }
14122 }
14123 })
14124 }
14125
14126 pub fn go_to_declaration(
14127 &mut self,
14128 _: &GoToDeclaration,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) -> Task<Result<Navigated>> {
14132 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14133 }
14134
14135 pub fn go_to_declaration_split(
14136 &mut self,
14137 _: &GoToDeclaration,
14138 window: &mut Window,
14139 cx: &mut Context<Self>,
14140 ) -> Task<Result<Navigated>> {
14141 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14142 }
14143
14144 pub fn go_to_implementation(
14145 &mut self,
14146 _: &GoToImplementation,
14147 window: &mut Window,
14148 cx: &mut Context<Self>,
14149 ) -> Task<Result<Navigated>> {
14150 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14151 }
14152
14153 pub fn go_to_implementation_split(
14154 &mut self,
14155 _: &GoToImplementationSplit,
14156 window: &mut Window,
14157 cx: &mut Context<Self>,
14158 ) -> Task<Result<Navigated>> {
14159 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14160 }
14161
14162 pub fn go_to_type_definition(
14163 &mut self,
14164 _: &GoToTypeDefinition,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) -> Task<Result<Navigated>> {
14168 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14169 }
14170
14171 pub fn go_to_definition_split(
14172 &mut self,
14173 _: &GoToDefinitionSplit,
14174 window: &mut Window,
14175 cx: &mut Context<Self>,
14176 ) -> Task<Result<Navigated>> {
14177 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14178 }
14179
14180 pub fn go_to_type_definition_split(
14181 &mut self,
14182 _: &GoToTypeDefinitionSplit,
14183 window: &mut Window,
14184 cx: &mut Context<Self>,
14185 ) -> Task<Result<Navigated>> {
14186 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14187 }
14188
14189 fn go_to_definition_of_kind(
14190 &mut self,
14191 kind: GotoDefinitionKind,
14192 split: bool,
14193 window: &mut Window,
14194 cx: &mut Context<Self>,
14195 ) -> Task<Result<Navigated>> {
14196 let Some(provider) = self.semantics_provider.clone() else {
14197 return Task::ready(Ok(Navigated::No));
14198 };
14199 let head = self.selections.newest::<usize>(cx).head();
14200 let buffer = self.buffer.read(cx);
14201 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14202 text_anchor
14203 } else {
14204 return Task::ready(Ok(Navigated::No));
14205 };
14206
14207 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14208 return Task::ready(Ok(Navigated::No));
14209 };
14210
14211 cx.spawn_in(window, async move |editor, cx| {
14212 let definitions = definitions.await?;
14213 let navigated = editor
14214 .update_in(cx, |editor, window, cx| {
14215 editor.navigate_to_hover_links(
14216 Some(kind),
14217 definitions
14218 .into_iter()
14219 .filter(|location| {
14220 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14221 })
14222 .map(HoverLink::Text)
14223 .collect::<Vec<_>>(),
14224 split,
14225 window,
14226 cx,
14227 )
14228 })?
14229 .await?;
14230 anyhow::Ok(navigated)
14231 })
14232 }
14233
14234 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14235 let selection = self.selections.newest_anchor();
14236 let head = selection.head();
14237 let tail = selection.tail();
14238
14239 let Some((buffer, start_position)) =
14240 self.buffer.read(cx).text_anchor_for_position(head, cx)
14241 else {
14242 return;
14243 };
14244
14245 let end_position = if head != tail {
14246 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14247 return;
14248 };
14249 Some(pos)
14250 } else {
14251 None
14252 };
14253
14254 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14255 let url = if let Some(end_pos) = end_position {
14256 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14257 } else {
14258 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14259 };
14260
14261 if let Some(url) = url {
14262 editor.update(cx, |_, cx| {
14263 cx.open_url(&url);
14264 })
14265 } else {
14266 Ok(())
14267 }
14268 });
14269
14270 url_finder.detach();
14271 }
14272
14273 pub fn open_selected_filename(
14274 &mut self,
14275 _: &OpenSelectedFilename,
14276 window: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 let Some(workspace) = self.workspace() else {
14280 return;
14281 };
14282
14283 let position = self.selections.newest_anchor().head();
14284
14285 let Some((buffer, buffer_position)) =
14286 self.buffer.read(cx).text_anchor_for_position(position, cx)
14287 else {
14288 return;
14289 };
14290
14291 let project = self.project.clone();
14292
14293 cx.spawn_in(window, async move |_, cx| {
14294 let result = find_file(&buffer, project, buffer_position, cx).await;
14295
14296 if let Some((_, path)) = result {
14297 workspace
14298 .update_in(cx, |workspace, window, cx| {
14299 workspace.open_resolved_path(path, window, cx)
14300 })?
14301 .await?;
14302 }
14303 anyhow::Ok(())
14304 })
14305 .detach();
14306 }
14307
14308 pub(crate) fn navigate_to_hover_links(
14309 &mut self,
14310 kind: Option<GotoDefinitionKind>,
14311 mut definitions: Vec<HoverLink>,
14312 split: bool,
14313 window: &mut Window,
14314 cx: &mut Context<Editor>,
14315 ) -> Task<Result<Navigated>> {
14316 // If there is one definition, just open it directly
14317 if definitions.len() == 1 {
14318 let definition = definitions.pop().unwrap();
14319
14320 enum TargetTaskResult {
14321 Location(Option<Location>),
14322 AlreadyNavigated,
14323 }
14324
14325 let target_task = match definition {
14326 HoverLink::Text(link) => {
14327 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14328 }
14329 HoverLink::InlayHint(lsp_location, server_id) => {
14330 let computation =
14331 self.compute_target_location(lsp_location, server_id, window, cx);
14332 cx.background_spawn(async move {
14333 let location = computation.await?;
14334 Ok(TargetTaskResult::Location(location))
14335 })
14336 }
14337 HoverLink::Url(url) => {
14338 cx.open_url(&url);
14339 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14340 }
14341 HoverLink::File(path) => {
14342 if let Some(workspace) = self.workspace() {
14343 cx.spawn_in(window, async move |_, cx| {
14344 workspace
14345 .update_in(cx, |workspace, window, cx| {
14346 workspace.open_resolved_path(path, window, cx)
14347 })?
14348 .await
14349 .map(|_| TargetTaskResult::AlreadyNavigated)
14350 })
14351 } else {
14352 Task::ready(Ok(TargetTaskResult::Location(None)))
14353 }
14354 }
14355 };
14356 cx.spawn_in(window, async move |editor, cx| {
14357 let target = match target_task.await.context("target resolution task")? {
14358 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14359 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14360 TargetTaskResult::Location(Some(target)) => target,
14361 };
14362
14363 editor.update_in(cx, |editor, window, cx| {
14364 let Some(workspace) = editor.workspace() else {
14365 return Navigated::No;
14366 };
14367 let pane = workspace.read(cx).active_pane().clone();
14368
14369 let range = target.range.to_point(target.buffer.read(cx));
14370 let range = editor.range_for_match(&range);
14371 let range = collapse_multiline_range(range);
14372
14373 if !split
14374 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14375 {
14376 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14377 } else {
14378 window.defer(cx, move |window, cx| {
14379 let target_editor: Entity<Self> =
14380 workspace.update(cx, |workspace, cx| {
14381 let pane = if split {
14382 workspace.adjacent_pane(window, cx)
14383 } else {
14384 workspace.active_pane().clone()
14385 };
14386
14387 workspace.open_project_item(
14388 pane,
14389 target.buffer.clone(),
14390 true,
14391 true,
14392 window,
14393 cx,
14394 )
14395 });
14396 target_editor.update(cx, |target_editor, cx| {
14397 // When selecting a definition in a different buffer, disable the nav history
14398 // to avoid creating a history entry at the previous cursor location.
14399 pane.update(cx, |pane, _| pane.disable_history());
14400 target_editor.go_to_singleton_buffer_range(range, window, cx);
14401 pane.update(cx, |pane, _| pane.enable_history());
14402 });
14403 });
14404 }
14405 Navigated::Yes
14406 })
14407 })
14408 } else if !definitions.is_empty() {
14409 cx.spawn_in(window, async move |editor, cx| {
14410 let (title, location_tasks, workspace) = editor
14411 .update_in(cx, |editor, window, cx| {
14412 let tab_kind = match kind {
14413 Some(GotoDefinitionKind::Implementation) => "Implementations",
14414 _ => "Definitions",
14415 };
14416 let title = definitions
14417 .iter()
14418 .find_map(|definition| match definition {
14419 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14420 let buffer = origin.buffer.read(cx);
14421 format!(
14422 "{} for {}",
14423 tab_kind,
14424 buffer
14425 .text_for_range(origin.range.clone())
14426 .collect::<String>()
14427 )
14428 }),
14429 HoverLink::InlayHint(_, _) => None,
14430 HoverLink::Url(_) => None,
14431 HoverLink::File(_) => None,
14432 })
14433 .unwrap_or(tab_kind.to_string());
14434 let location_tasks = definitions
14435 .into_iter()
14436 .map(|definition| match definition {
14437 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14438 HoverLink::InlayHint(lsp_location, server_id) => editor
14439 .compute_target_location(lsp_location, server_id, window, cx),
14440 HoverLink::Url(_) => Task::ready(Ok(None)),
14441 HoverLink::File(_) => Task::ready(Ok(None)),
14442 })
14443 .collect::<Vec<_>>();
14444 (title, location_tasks, editor.workspace().clone())
14445 })
14446 .context("location tasks preparation")?;
14447
14448 let locations = future::join_all(location_tasks)
14449 .await
14450 .into_iter()
14451 .filter_map(|location| location.transpose())
14452 .collect::<Result<_>>()
14453 .context("location tasks")?;
14454
14455 let Some(workspace) = workspace else {
14456 return Ok(Navigated::No);
14457 };
14458 let opened = workspace
14459 .update_in(cx, |workspace, window, cx| {
14460 Self::open_locations_in_multibuffer(
14461 workspace,
14462 locations,
14463 title,
14464 split,
14465 MultibufferSelectionMode::First,
14466 window,
14467 cx,
14468 )
14469 })
14470 .ok();
14471
14472 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14473 })
14474 } else {
14475 Task::ready(Ok(Navigated::No))
14476 }
14477 }
14478
14479 fn compute_target_location(
14480 &self,
14481 lsp_location: lsp::Location,
14482 server_id: LanguageServerId,
14483 window: &mut Window,
14484 cx: &mut Context<Self>,
14485 ) -> Task<anyhow::Result<Option<Location>>> {
14486 let Some(project) = self.project.clone() else {
14487 return Task::ready(Ok(None));
14488 };
14489
14490 cx.spawn_in(window, async move |editor, cx| {
14491 let location_task = editor.update(cx, |_, cx| {
14492 project.update(cx, |project, cx| {
14493 let language_server_name = project
14494 .language_server_statuses(cx)
14495 .find(|(id, _)| server_id == *id)
14496 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14497 language_server_name.map(|language_server_name| {
14498 project.open_local_buffer_via_lsp(
14499 lsp_location.uri.clone(),
14500 server_id,
14501 language_server_name,
14502 cx,
14503 )
14504 })
14505 })
14506 })?;
14507 let location = match location_task {
14508 Some(task) => Some({
14509 let target_buffer_handle = task.await.context("open local buffer")?;
14510 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14511 let target_start = target_buffer
14512 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14513 let target_end = target_buffer
14514 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14515 target_buffer.anchor_after(target_start)
14516 ..target_buffer.anchor_before(target_end)
14517 })?;
14518 Location {
14519 buffer: target_buffer_handle,
14520 range,
14521 }
14522 }),
14523 None => None,
14524 };
14525 Ok(location)
14526 })
14527 }
14528
14529 pub fn find_all_references(
14530 &mut self,
14531 _: &FindAllReferences,
14532 window: &mut Window,
14533 cx: &mut Context<Self>,
14534 ) -> Option<Task<Result<Navigated>>> {
14535 let selection = self.selections.newest::<usize>(cx);
14536 let multi_buffer = self.buffer.read(cx);
14537 let head = selection.head();
14538
14539 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14540 let head_anchor = multi_buffer_snapshot.anchor_at(
14541 head,
14542 if head < selection.tail() {
14543 Bias::Right
14544 } else {
14545 Bias::Left
14546 },
14547 );
14548
14549 match self
14550 .find_all_references_task_sources
14551 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14552 {
14553 Ok(_) => {
14554 log::info!(
14555 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14556 );
14557 return None;
14558 }
14559 Err(i) => {
14560 self.find_all_references_task_sources.insert(i, head_anchor);
14561 }
14562 }
14563
14564 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14565 let workspace = self.workspace()?;
14566 let project = workspace.read(cx).project().clone();
14567 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14568 Some(cx.spawn_in(window, async move |editor, cx| {
14569 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14570 if let Ok(i) = editor
14571 .find_all_references_task_sources
14572 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14573 {
14574 editor.find_all_references_task_sources.remove(i);
14575 }
14576 });
14577
14578 let locations = references.await?;
14579 if locations.is_empty() {
14580 return anyhow::Ok(Navigated::No);
14581 }
14582
14583 workspace.update_in(cx, |workspace, window, cx| {
14584 let title = locations
14585 .first()
14586 .as_ref()
14587 .map(|location| {
14588 let buffer = location.buffer.read(cx);
14589 format!(
14590 "References to `{}`",
14591 buffer
14592 .text_for_range(location.range.clone())
14593 .collect::<String>()
14594 )
14595 })
14596 .unwrap();
14597 Self::open_locations_in_multibuffer(
14598 workspace,
14599 locations,
14600 title,
14601 false,
14602 MultibufferSelectionMode::First,
14603 window,
14604 cx,
14605 );
14606 Navigated::Yes
14607 })
14608 }))
14609 }
14610
14611 /// Opens a multibuffer with the given project locations in it
14612 pub fn open_locations_in_multibuffer(
14613 workspace: &mut Workspace,
14614 mut locations: Vec<Location>,
14615 title: String,
14616 split: bool,
14617 multibuffer_selection_mode: MultibufferSelectionMode,
14618 window: &mut Window,
14619 cx: &mut Context<Workspace>,
14620 ) {
14621 // If there are multiple definitions, open them in a multibuffer
14622 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14623 let mut locations = locations.into_iter().peekable();
14624 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14625 let capability = workspace.project().read(cx).capability();
14626
14627 let excerpt_buffer = cx.new(|cx| {
14628 let mut multibuffer = MultiBuffer::new(capability);
14629 while let Some(location) = locations.next() {
14630 let buffer = location.buffer.read(cx);
14631 let mut ranges_for_buffer = Vec::new();
14632 let range = location.range.to_point(buffer);
14633 ranges_for_buffer.push(range.clone());
14634
14635 while let Some(next_location) = locations.peek() {
14636 if next_location.buffer == location.buffer {
14637 ranges_for_buffer.push(next_location.range.to_point(buffer));
14638 locations.next();
14639 } else {
14640 break;
14641 }
14642 }
14643
14644 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14645 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14646 PathKey::for_buffer(&location.buffer, cx),
14647 location.buffer.clone(),
14648 ranges_for_buffer,
14649 DEFAULT_MULTIBUFFER_CONTEXT,
14650 cx,
14651 );
14652 ranges.extend(new_ranges)
14653 }
14654
14655 multibuffer.with_title(title)
14656 });
14657
14658 let editor = cx.new(|cx| {
14659 Editor::for_multibuffer(
14660 excerpt_buffer,
14661 Some(workspace.project().clone()),
14662 window,
14663 cx,
14664 )
14665 });
14666 editor.update(cx, |editor, cx| {
14667 match multibuffer_selection_mode {
14668 MultibufferSelectionMode::First => {
14669 if let Some(first_range) = ranges.first() {
14670 editor.change_selections(None, window, cx, |selections| {
14671 selections.clear_disjoint();
14672 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14673 });
14674 }
14675 editor.highlight_background::<Self>(
14676 &ranges,
14677 |theme| theme.editor_highlighted_line_background,
14678 cx,
14679 );
14680 }
14681 MultibufferSelectionMode::All => {
14682 editor.change_selections(None, window, cx, |selections| {
14683 selections.clear_disjoint();
14684 selections.select_anchor_ranges(ranges);
14685 });
14686 }
14687 }
14688 editor.register_buffers_with_language_servers(cx);
14689 });
14690
14691 let item = Box::new(editor);
14692 let item_id = item.item_id();
14693
14694 if split {
14695 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14696 } else {
14697 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14698 let (preview_item_id, preview_item_idx) =
14699 workspace.active_pane().update(cx, |pane, _| {
14700 (pane.preview_item_id(), pane.preview_item_idx())
14701 });
14702
14703 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14704
14705 if let Some(preview_item_id) = preview_item_id {
14706 workspace.active_pane().update(cx, |pane, cx| {
14707 pane.remove_item(preview_item_id, false, false, window, cx);
14708 });
14709 }
14710 } else {
14711 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14712 }
14713 }
14714 workspace.active_pane().update(cx, |pane, cx| {
14715 pane.set_preview_item_id(Some(item_id), cx);
14716 });
14717 }
14718
14719 pub fn rename(
14720 &mut self,
14721 _: &Rename,
14722 window: &mut Window,
14723 cx: &mut Context<Self>,
14724 ) -> Option<Task<Result<()>>> {
14725 use language::ToOffset as _;
14726
14727 let provider = self.semantics_provider.clone()?;
14728 let selection = self.selections.newest_anchor().clone();
14729 let (cursor_buffer, cursor_buffer_position) = self
14730 .buffer
14731 .read(cx)
14732 .text_anchor_for_position(selection.head(), cx)?;
14733 let (tail_buffer, cursor_buffer_position_end) = self
14734 .buffer
14735 .read(cx)
14736 .text_anchor_for_position(selection.tail(), cx)?;
14737 if tail_buffer != cursor_buffer {
14738 return None;
14739 }
14740
14741 let snapshot = cursor_buffer.read(cx).snapshot();
14742 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14743 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14744 let prepare_rename = provider
14745 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14746 .unwrap_or_else(|| Task::ready(Ok(None)));
14747 drop(snapshot);
14748
14749 Some(cx.spawn_in(window, async move |this, cx| {
14750 let rename_range = if let Some(range) = prepare_rename.await? {
14751 Some(range)
14752 } else {
14753 this.update(cx, |this, cx| {
14754 let buffer = this.buffer.read(cx).snapshot(cx);
14755 let mut buffer_highlights = this
14756 .document_highlights_for_position(selection.head(), &buffer)
14757 .filter(|highlight| {
14758 highlight.start.excerpt_id == selection.head().excerpt_id
14759 && highlight.end.excerpt_id == selection.head().excerpt_id
14760 });
14761 buffer_highlights
14762 .next()
14763 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14764 })?
14765 };
14766 if let Some(rename_range) = rename_range {
14767 this.update_in(cx, |this, window, cx| {
14768 let snapshot = cursor_buffer.read(cx).snapshot();
14769 let rename_buffer_range = rename_range.to_offset(&snapshot);
14770 let cursor_offset_in_rename_range =
14771 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14772 let cursor_offset_in_rename_range_end =
14773 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14774
14775 this.take_rename(false, window, cx);
14776 let buffer = this.buffer.read(cx).read(cx);
14777 let cursor_offset = selection.head().to_offset(&buffer);
14778 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14779 let rename_end = rename_start + rename_buffer_range.len();
14780 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14781 let mut old_highlight_id = None;
14782 let old_name: Arc<str> = buffer
14783 .chunks(rename_start..rename_end, true)
14784 .map(|chunk| {
14785 if old_highlight_id.is_none() {
14786 old_highlight_id = chunk.syntax_highlight_id;
14787 }
14788 chunk.text
14789 })
14790 .collect::<String>()
14791 .into();
14792
14793 drop(buffer);
14794
14795 // Position the selection in the rename editor so that it matches the current selection.
14796 this.show_local_selections = false;
14797 let rename_editor = cx.new(|cx| {
14798 let mut editor = Editor::single_line(window, cx);
14799 editor.buffer.update(cx, |buffer, cx| {
14800 buffer.edit([(0..0, old_name.clone())], None, cx)
14801 });
14802 let rename_selection_range = match cursor_offset_in_rename_range
14803 .cmp(&cursor_offset_in_rename_range_end)
14804 {
14805 Ordering::Equal => {
14806 editor.select_all(&SelectAll, window, cx);
14807 return editor;
14808 }
14809 Ordering::Less => {
14810 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14811 }
14812 Ordering::Greater => {
14813 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14814 }
14815 };
14816 if rename_selection_range.end > old_name.len() {
14817 editor.select_all(&SelectAll, window, cx);
14818 } else {
14819 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14820 s.select_ranges([rename_selection_range]);
14821 });
14822 }
14823 editor
14824 });
14825 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14826 if e == &EditorEvent::Focused {
14827 cx.emit(EditorEvent::FocusedIn)
14828 }
14829 })
14830 .detach();
14831
14832 let write_highlights =
14833 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14834 let read_highlights =
14835 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14836 let ranges = write_highlights
14837 .iter()
14838 .flat_map(|(_, ranges)| ranges.iter())
14839 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14840 .cloned()
14841 .collect();
14842
14843 this.highlight_text::<Rename>(
14844 ranges,
14845 HighlightStyle {
14846 fade_out: Some(0.6),
14847 ..Default::default()
14848 },
14849 cx,
14850 );
14851 let rename_focus_handle = rename_editor.focus_handle(cx);
14852 window.focus(&rename_focus_handle);
14853 let block_id = this.insert_blocks(
14854 [BlockProperties {
14855 style: BlockStyle::Flex,
14856 placement: BlockPlacement::Below(range.start),
14857 height: Some(1),
14858 render: Arc::new({
14859 let rename_editor = rename_editor.clone();
14860 move |cx: &mut BlockContext| {
14861 let mut text_style = cx.editor_style.text.clone();
14862 if let Some(highlight_style) = old_highlight_id
14863 .and_then(|h| h.style(&cx.editor_style.syntax))
14864 {
14865 text_style = text_style.highlight(highlight_style);
14866 }
14867 div()
14868 .block_mouse_down()
14869 .pl(cx.anchor_x)
14870 .child(EditorElement::new(
14871 &rename_editor,
14872 EditorStyle {
14873 background: cx.theme().system().transparent,
14874 local_player: cx.editor_style.local_player,
14875 text: text_style,
14876 scrollbar_width: cx.editor_style.scrollbar_width,
14877 syntax: cx.editor_style.syntax.clone(),
14878 status: cx.editor_style.status.clone(),
14879 inlay_hints_style: HighlightStyle {
14880 font_weight: Some(FontWeight::BOLD),
14881 ..make_inlay_hints_style(cx.app)
14882 },
14883 inline_completion_styles: make_suggestion_styles(
14884 cx.app,
14885 ),
14886 ..EditorStyle::default()
14887 },
14888 ))
14889 .into_any_element()
14890 }
14891 }),
14892 priority: 0,
14893 render_in_minimap: true,
14894 }],
14895 Some(Autoscroll::fit()),
14896 cx,
14897 )[0];
14898 this.pending_rename = Some(RenameState {
14899 range,
14900 old_name,
14901 editor: rename_editor,
14902 block_id,
14903 });
14904 })?;
14905 }
14906
14907 Ok(())
14908 }))
14909 }
14910
14911 pub fn confirm_rename(
14912 &mut self,
14913 _: &ConfirmRename,
14914 window: &mut Window,
14915 cx: &mut Context<Self>,
14916 ) -> Option<Task<Result<()>>> {
14917 let rename = self.take_rename(false, window, cx)?;
14918 let workspace = self.workspace()?.downgrade();
14919 let (buffer, start) = self
14920 .buffer
14921 .read(cx)
14922 .text_anchor_for_position(rename.range.start, cx)?;
14923 let (end_buffer, _) = self
14924 .buffer
14925 .read(cx)
14926 .text_anchor_for_position(rename.range.end, cx)?;
14927 if buffer != end_buffer {
14928 return None;
14929 }
14930
14931 let old_name = rename.old_name;
14932 let new_name = rename.editor.read(cx).text(cx);
14933
14934 let rename = self.semantics_provider.as_ref()?.perform_rename(
14935 &buffer,
14936 start,
14937 new_name.clone(),
14938 cx,
14939 )?;
14940
14941 Some(cx.spawn_in(window, async move |editor, cx| {
14942 let project_transaction = rename.await?;
14943 Self::open_project_transaction(
14944 &editor,
14945 workspace,
14946 project_transaction,
14947 format!("Rename: {} → {}", old_name, new_name),
14948 cx,
14949 )
14950 .await?;
14951
14952 editor.update(cx, |editor, cx| {
14953 editor.refresh_document_highlights(cx);
14954 })?;
14955 Ok(())
14956 }))
14957 }
14958
14959 fn take_rename(
14960 &mut self,
14961 moving_cursor: bool,
14962 window: &mut Window,
14963 cx: &mut Context<Self>,
14964 ) -> Option<RenameState> {
14965 let rename = self.pending_rename.take()?;
14966 if rename.editor.focus_handle(cx).is_focused(window) {
14967 window.focus(&self.focus_handle);
14968 }
14969
14970 self.remove_blocks(
14971 [rename.block_id].into_iter().collect(),
14972 Some(Autoscroll::fit()),
14973 cx,
14974 );
14975 self.clear_highlights::<Rename>(cx);
14976 self.show_local_selections = true;
14977
14978 if moving_cursor {
14979 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14980 editor.selections.newest::<usize>(cx).head()
14981 });
14982
14983 // Update the selection to match the position of the selection inside
14984 // the rename editor.
14985 let snapshot = self.buffer.read(cx).read(cx);
14986 let rename_range = rename.range.to_offset(&snapshot);
14987 let cursor_in_editor = snapshot
14988 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14989 .min(rename_range.end);
14990 drop(snapshot);
14991
14992 self.change_selections(None, window, cx, |s| {
14993 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14994 });
14995 } else {
14996 self.refresh_document_highlights(cx);
14997 }
14998
14999 Some(rename)
15000 }
15001
15002 pub fn pending_rename(&self) -> Option<&RenameState> {
15003 self.pending_rename.as_ref()
15004 }
15005
15006 fn format(
15007 &mut self,
15008 _: &Format,
15009 window: &mut Window,
15010 cx: &mut Context<Self>,
15011 ) -> Option<Task<Result<()>>> {
15012 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15013
15014 let project = match &self.project {
15015 Some(project) => project.clone(),
15016 None => return None,
15017 };
15018
15019 Some(self.perform_format(
15020 project,
15021 FormatTrigger::Manual,
15022 FormatTarget::Buffers,
15023 window,
15024 cx,
15025 ))
15026 }
15027
15028 fn format_selections(
15029 &mut self,
15030 _: &FormatSelections,
15031 window: &mut Window,
15032 cx: &mut Context<Self>,
15033 ) -> Option<Task<Result<()>>> {
15034 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15035
15036 let project = match &self.project {
15037 Some(project) => project.clone(),
15038 None => return None,
15039 };
15040
15041 let ranges = self
15042 .selections
15043 .all_adjusted(cx)
15044 .into_iter()
15045 .map(|selection| selection.range())
15046 .collect_vec();
15047
15048 Some(self.perform_format(
15049 project,
15050 FormatTrigger::Manual,
15051 FormatTarget::Ranges(ranges),
15052 window,
15053 cx,
15054 ))
15055 }
15056
15057 fn perform_format(
15058 &mut self,
15059 project: Entity<Project>,
15060 trigger: FormatTrigger,
15061 target: FormatTarget,
15062 window: &mut Window,
15063 cx: &mut Context<Self>,
15064 ) -> Task<Result<()>> {
15065 let buffer = self.buffer.clone();
15066 let (buffers, target) = match target {
15067 FormatTarget::Buffers => {
15068 let mut buffers = buffer.read(cx).all_buffers();
15069 if trigger == FormatTrigger::Save {
15070 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15071 }
15072 (buffers, LspFormatTarget::Buffers)
15073 }
15074 FormatTarget::Ranges(selection_ranges) => {
15075 let multi_buffer = buffer.read(cx);
15076 let snapshot = multi_buffer.read(cx);
15077 let mut buffers = HashSet::default();
15078 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15079 BTreeMap::new();
15080 for selection_range in selection_ranges {
15081 for (buffer, buffer_range, _) in
15082 snapshot.range_to_buffer_ranges(selection_range)
15083 {
15084 let buffer_id = buffer.remote_id();
15085 let start = buffer.anchor_before(buffer_range.start);
15086 let end = buffer.anchor_after(buffer_range.end);
15087 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15088 buffer_id_to_ranges
15089 .entry(buffer_id)
15090 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15091 .or_insert_with(|| vec![start..end]);
15092 }
15093 }
15094 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15095 }
15096 };
15097
15098 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15099 let selections_prev = transaction_id_prev
15100 .and_then(|transaction_id_prev| {
15101 // default to selections as they were after the last edit, if we have them,
15102 // instead of how they are now.
15103 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15104 // will take you back to where you made the last edit, instead of staying where you scrolled
15105 self.selection_history
15106 .transaction(transaction_id_prev)
15107 .map(|t| t.0.clone())
15108 })
15109 .unwrap_or_else(|| {
15110 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15111 self.selections.disjoint_anchors()
15112 });
15113
15114 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15115 let format = project.update(cx, |project, cx| {
15116 project.format(buffers, target, true, trigger, cx)
15117 });
15118
15119 cx.spawn_in(window, async move |editor, cx| {
15120 let transaction = futures::select_biased! {
15121 transaction = format.log_err().fuse() => transaction,
15122 () = timeout => {
15123 log::warn!("timed out waiting for formatting");
15124 None
15125 }
15126 };
15127
15128 buffer
15129 .update(cx, |buffer, cx| {
15130 if let Some(transaction) = transaction {
15131 if !buffer.is_singleton() {
15132 buffer.push_transaction(&transaction.0, cx);
15133 }
15134 }
15135 cx.notify();
15136 })
15137 .ok();
15138
15139 if let Some(transaction_id_now) =
15140 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15141 {
15142 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15143 if has_new_transaction {
15144 _ = editor.update(cx, |editor, _| {
15145 editor
15146 .selection_history
15147 .insert_transaction(transaction_id_now, selections_prev);
15148 });
15149 }
15150 }
15151
15152 Ok(())
15153 })
15154 }
15155
15156 fn organize_imports(
15157 &mut self,
15158 _: &OrganizeImports,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) -> Option<Task<Result<()>>> {
15162 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15163 let project = match &self.project {
15164 Some(project) => project.clone(),
15165 None => return None,
15166 };
15167 Some(self.perform_code_action_kind(
15168 project,
15169 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15170 window,
15171 cx,
15172 ))
15173 }
15174
15175 fn perform_code_action_kind(
15176 &mut self,
15177 project: Entity<Project>,
15178 kind: CodeActionKind,
15179 window: &mut Window,
15180 cx: &mut Context<Self>,
15181 ) -> Task<Result<()>> {
15182 let buffer = self.buffer.clone();
15183 let buffers = buffer.read(cx).all_buffers();
15184 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15185 let apply_action = project.update(cx, |project, cx| {
15186 project.apply_code_action_kind(buffers, kind, true, cx)
15187 });
15188 cx.spawn_in(window, async move |_, cx| {
15189 let transaction = futures::select_biased! {
15190 () = timeout => {
15191 log::warn!("timed out waiting for executing code action");
15192 None
15193 }
15194 transaction = apply_action.log_err().fuse() => transaction,
15195 };
15196 buffer
15197 .update(cx, |buffer, cx| {
15198 // check if we need this
15199 if let Some(transaction) = transaction {
15200 if !buffer.is_singleton() {
15201 buffer.push_transaction(&transaction.0, cx);
15202 }
15203 }
15204 cx.notify();
15205 })
15206 .ok();
15207 Ok(())
15208 })
15209 }
15210
15211 fn restart_language_server(
15212 &mut self,
15213 _: &RestartLanguageServer,
15214 _: &mut Window,
15215 cx: &mut Context<Self>,
15216 ) {
15217 if let Some(project) = self.project.clone() {
15218 self.buffer.update(cx, |multi_buffer, cx| {
15219 project.update(cx, |project, cx| {
15220 project.restart_language_servers_for_buffers(
15221 multi_buffer.all_buffers().into_iter().collect(),
15222 cx,
15223 );
15224 });
15225 })
15226 }
15227 }
15228
15229 fn stop_language_server(
15230 &mut self,
15231 _: &StopLanguageServer,
15232 _: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 if let Some(project) = self.project.clone() {
15236 self.buffer.update(cx, |multi_buffer, cx| {
15237 project.update(cx, |project, cx| {
15238 project.stop_language_servers_for_buffers(
15239 multi_buffer.all_buffers().into_iter().collect(),
15240 cx,
15241 );
15242 cx.emit(project::Event::RefreshInlayHints);
15243 });
15244 });
15245 }
15246 }
15247
15248 fn cancel_language_server_work(
15249 workspace: &mut Workspace,
15250 _: &actions::CancelLanguageServerWork,
15251 _: &mut Window,
15252 cx: &mut Context<Workspace>,
15253 ) {
15254 let project = workspace.project();
15255 let buffers = workspace
15256 .active_item(cx)
15257 .and_then(|item| item.act_as::<Editor>(cx))
15258 .map_or(HashSet::default(), |editor| {
15259 editor.read(cx).buffer.read(cx).all_buffers()
15260 });
15261 project.update(cx, |project, cx| {
15262 project.cancel_language_server_work_for_buffers(buffers, cx);
15263 });
15264 }
15265
15266 fn show_character_palette(
15267 &mut self,
15268 _: &ShowCharacterPalette,
15269 window: &mut Window,
15270 _: &mut Context<Self>,
15271 ) {
15272 window.show_character_palette();
15273 }
15274
15275 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15276 if self.mode.is_minimap() {
15277 return;
15278 }
15279
15280 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15281 let buffer = self.buffer.read(cx).snapshot(cx);
15282 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15283 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15284 let is_valid = buffer
15285 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15286 .any(|entry| {
15287 entry.diagnostic.is_primary
15288 && !entry.range.is_empty()
15289 && entry.range.start == primary_range_start
15290 && entry.diagnostic.message == active_diagnostics.active_message
15291 });
15292
15293 if !is_valid {
15294 self.dismiss_diagnostics(cx);
15295 }
15296 }
15297 }
15298
15299 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15300 match &self.active_diagnostics {
15301 ActiveDiagnostic::Group(group) => Some(group),
15302 _ => None,
15303 }
15304 }
15305
15306 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15307 self.dismiss_diagnostics(cx);
15308 self.active_diagnostics = ActiveDiagnostic::All;
15309 }
15310
15311 fn activate_diagnostics(
15312 &mut self,
15313 buffer_id: BufferId,
15314 diagnostic: DiagnosticEntry<usize>,
15315 window: &mut Window,
15316 cx: &mut Context<Self>,
15317 ) {
15318 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15319 return;
15320 }
15321 self.dismiss_diagnostics(cx);
15322 let snapshot = self.snapshot(window, cx);
15323 let buffer = self.buffer.read(cx).snapshot(cx);
15324 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15325 return;
15326 };
15327
15328 let diagnostic_group = buffer
15329 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15330 .collect::<Vec<_>>();
15331
15332 let blocks =
15333 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15334
15335 let blocks = self.display_map.update(cx, |display_map, cx| {
15336 display_map.insert_blocks(blocks, cx).into_iter().collect()
15337 });
15338 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15339 active_range: buffer.anchor_before(diagnostic.range.start)
15340 ..buffer.anchor_after(diagnostic.range.end),
15341 active_message: diagnostic.diagnostic.message.clone(),
15342 group_id: diagnostic.diagnostic.group_id,
15343 blocks,
15344 });
15345 cx.notify();
15346 }
15347
15348 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15349 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15350 return;
15351 };
15352
15353 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15354 if let ActiveDiagnostic::Group(group) = prev {
15355 self.display_map.update(cx, |display_map, cx| {
15356 display_map.remove_blocks(group.blocks, cx);
15357 });
15358 cx.notify();
15359 }
15360 }
15361
15362 /// Disable inline diagnostics rendering for this editor.
15363 pub fn disable_inline_diagnostics(&mut self) {
15364 self.inline_diagnostics_enabled = false;
15365 self.inline_diagnostics_update = Task::ready(());
15366 self.inline_diagnostics.clear();
15367 }
15368
15369 pub fn diagnostics_enabled(&self) -> bool {
15370 self.mode.is_full()
15371 }
15372
15373 pub fn inline_diagnostics_enabled(&self) -> bool {
15374 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15375 }
15376
15377 pub fn show_inline_diagnostics(&self) -> bool {
15378 self.show_inline_diagnostics
15379 }
15380
15381 pub fn toggle_inline_diagnostics(
15382 &mut self,
15383 _: &ToggleInlineDiagnostics,
15384 window: &mut Window,
15385 cx: &mut Context<Editor>,
15386 ) {
15387 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15388 self.refresh_inline_diagnostics(false, window, cx);
15389 }
15390
15391 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15392 self.diagnostics_max_severity = severity;
15393 self.display_map.update(cx, |display_map, _| {
15394 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15395 });
15396 }
15397
15398 pub fn toggle_diagnostics(
15399 &mut self,
15400 _: &ToggleDiagnostics,
15401 window: &mut Window,
15402 cx: &mut Context<Editor>,
15403 ) {
15404 if !self.diagnostics_enabled() {
15405 return;
15406 }
15407
15408 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15409 EditorSettings::get_global(cx)
15410 .diagnostics_max_severity
15411 .filter(|severity| severity != &DiagnosticSeverity::Off)
15412 .unwrap_or(DiagnosticSeverity::Hint)
15413 } else {
15414 DiagnosticSeverity::Off
15415 };
15416 self.set_max_diagnostics_severity(new_severity, cx);
15417 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15418 self.active_diagnostics = ActiveDiagnostic::None;
15419 self.inline_diagnostics_update = Task::ready(());
15420 self.inline_diagnostics.clear();
15421 } else {
15422 self.refresh_inline_diagnostics(false, window, cx);
15423 }
15424
15425 cx.notify();
15426 }
15427
15428 pub fn toggle_minimap(
15429 &mut self,
15430 _: &ToggleMinimap,
15431 window: &mut Window,
15432 cx: &mut Context<Editor>,
15433 ) {
15434 if self.supports_minimap(cx) {
15435 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15436 }
15437 }
15438
15439 fn refresh_inline_diagnostics(
15440 &mut self,
15441 debounce: bool,
15442 window: &mut Window,
15443 cx: &mut Context<Self>,
15444 ) {
15445 let max_severity = ProjectSettings::get_global(cx)
15446 .diagnostics
15447 .inline
15448 .max_severity
15449 .unwrap_or(self.diagnostics_max_severity);
15450
15451 if !self.inline_diagnostics_enabled()
15452 || !self.show_inline_diagnostics
15453 || max_severity == DiagnosticSeverity::Off
15454 {
15455 self.inline_diagnostics_update = Task::ready(());
15456 self.inline_diagnostics.clear();
15457 return;
15458 }
15459
15460 let debounce_ms = ProjectSettings::get_global(cx)
15461 .diagnostics
15462 .inline
15463 .update_debounce_ms;
15464 let debounce = if debounce && debounce_ms > 0 {
15465 Some(Duration::from_millis(debounce_ms))
15466 } else {
15467 None
15468 };
15469 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15470 let editor = editor.upgrade().unwrap();
15471
15472 if let Some(debounce) = debounce {
15473 cx.background_executor().timer(debounce).await;
15474 }
15475 let Some(snapshot) = editor
15476 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15477 .ok()
15478 else {
15479 return;
15480 };
15481
15482 let new_inline_diagnostics = cx
15483 .background_spawn(async move {
15484 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15485 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15486 let message = diagnostic_entry
15487 .diagnostic
15488 .message
15489 .split_once('\n')
15490 .map(|(line, _)| line)
15491 .map(SharedString::new)
15492 .unwrap_or_else(|| {
15493 SharedString::from(diagnostic_entry.diagnostic.message)
15494 });
15495 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15496 let (Ok(i) | Err(i)) = inline_diagnostics
15497 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15498 inline_diagnostics.insert(
15499 i,
15500 (
15501 start_anchor,
15502 InlineDiagnostic {
15503 message,
15504 group_id: diagnostic_entry.diagnostic.group_id,
15505 start: diagnostic_entry.range.start.to_point(&snapshot),
15506 is_primary: diagnostic_entry.diagnostic.is_primary,
15507 severity: diagnostic_entry.diagnostic.severity,
15508 },
15509 ),
15510 );
15511 }
15512 inline_diagnostics
15513 })
15514 .await;
15515
15516 editor
15517 .update(cx, |editor, cx| {
15518 editor.inline_diagnostics = new_inline_diagnostics;
15519 cx.notify();
15520 })
15521 .ok();
15522 });
15523 }
15524
15525 pub fn set_selections_from_remote(
15526 &mut self,
15527 selections: Vec<Selection<Anchor>>,
15528 pending_selection: Option<Selection<Anchor>>,
15529 window: &mut Window,
15530 cx: &mut Context<Self>,
15531 ) {
15532 let old_cursor_position = self.selections.newest_anchor().head();
15533 self.selections.change_with(cx, |s| {
15534 s.select_anchors(selections);
15535 if let Some(pending_selection) = pending_selection {
15536 s.set_pending(pending_selection, SelectMode::Character);
15537 } else {
15538 s.clear_pending();
15539 }
15540 });
15541 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15542 }
15543
15544 fn push_to_selection_history(&mut self) {
15545 self.selection_history.push(SelectionHistoryEntry {
15546 selections: self.selections.disjoint_anchors(),
15547 select_next_state: self.select_next_state.clone(),
15548 select_prev_state: self.select_prev_state.clone(),
15549 add_selections_state: self.add_selections_state.clone(),
15550 });
15551 }
15552
15553 pub fn transact(
15554 &mut self,
15555 window: &mut Window,
15556 cx: &mut Context<Self>,
15557 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15558 ) -> Option<TransactionId> {
15559 self.start_transaction_at(Instant::now(), window, cx);
15560 update(self, window, cx);
15561 self.end_transaction_at(Instant::now(), cx)
15562 }
15563
15564 pub fn start_transaction_at(
15565 &mut self,
15566 now: Instant,
15567 window: &mut Window,
15568 cx: &mut Context<Self>,
15569 ) {
15570 self.end_selection(window, cx);
15571 if let Some(tx_id) = self
15572 .buffer
15573 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15574 {
15575 self.selection_history
15576 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15577 cx.emit(EditorEvent::TransactionBegun {
15578 transaction_id: tx_id,
15579 })
15580 }
15581 }
15582
15583 pub fn end_transaction_at(
15584 &mut self,
15585 now: Instant,
15586 cx: &mut Context<Self>,
15587 ) -> Option<TransactionId> {
15588 if let Some(transaction_id) = self
15589 .buffer
15590 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15591 {
15592 if let Some((_, end_selections)) =
15593 self.selection_history.transaction_mut(transaction_id)
15594 {
15595 *end_selections = Some(self.selections.disjoint_anchors());
15596 } else {
15597 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15598 }
15599
15600 cx.emit(EditorEvent::Edited { transaction_id });
15601 Some(transaction_id)
15602 } else {
15603 None
15604 }
15605 }
15606
15607 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15608 if self.selection_mark_mode {
15609 self.change_selections(None, window, cx, |s| {
15610 s.move_with(|_, sel| {
15611 sel.collapse_to(sel.head(), SelectionGoal::None);
15612 });
15613 })
15614 }
15615 self.selection_mark_mode = true;
15616 cx.notify();
15617 }
15618
15619 pub fn swap_selection_ends(
15620 &mut self,
15621 _: &actions::SwapSelectionEnds,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) {
15625 self.change_selections(None, window, cx, |s| {
15626 s.move_with(|_, sel| {
15627 if sel.start != sel.end {
15628 sel.reversed = !sel.reversed
15629 }
15630 });
15631 });
15632 self.request_autoscroll(Autoscroll::newest(), cx);
15633 cx.notify();
15634 }
15635
15636 pub fn toggle_fold(
15637 &mut self,
15638 _: &actions::ToggleFold,
15639 window: &mut Window,
15640 cx: &mut Context<Self>,
15641 ) {
15642 if self.is_singleton(cx) {
15643 let selection = self.selections.newest::<Point>(cx);
15644
15645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15646 let range = if selection.is_empty() {
15647 let point = selection.head().to_display_point(&display_map);
15648 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15649 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15650 .to_point(&display_map);
15651 start..end
15652 } else {
15653 selection.range()
15654 };
15655 if display_map.folds_in_range(range).next().is_some() {
15656 self.unfold_lines(&Default::default(), window, cx)
15657 } else {
15658 self.fold(&Default::default(), window, cx)
15659 }
15660 } else {
15661 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15662 let buffer_ids: HashSet<_> = self
15663 .selections
15664 .disjoint_anchor_ranges()
15665 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15666 .collect();
15667
15668 let should_unfold = buffer_ids
15669 .iter()
15670 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15671
15672 for buffer_id in buffer_ids {
15673 if should_unfold {
15674 self.unfold_buffer(buffer_id, cx);
15675 } else {
15676 self.fold_buffer(buffer_id, cx);
15677 }
15678 }
15679 }
15680 }
15681
15682 pub fn toggle_fold_recursive(
15683 &mut self,
15684 _: &actions::ToggleFoldRecursive,
15685 window: &mut Window,
15686 cx: &mut Context<Self>,
15687 ) {
15688 let selection = self.selections.newest::<Point>(cx);
15689
15690 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15691 let range = if selection.is_empty() {
15692 let point = selection.head().to_display_point(&display_map);
15693 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15694 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15695 .to_point(&display_map);
15696 start..end
15697 } else {
15698 selection.range()
15699 };
15700 if display_map.folds_in_range(range).next().is_some() {
15701 self.unfold_recursive(&Default::default(), window, cx)
15702 } else {
15703 self.fold_recursive(&Default::default(), window, cx)
15704 }
15705 }
15706
15707 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15708 if self.is_singleton(cx) {
15709 let mut to_fold = Vec::new();
15710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15711 let selections = self.selections.all_adjusted(cx);
15712
15713 for selection in selections {
15714 let range = selection.range().sorted();
15715 let buffer_start_row = range.start.row;
15716
15717 if range.start.row != range.end.row {
15718 let mut found = false;
15719 let mut row = range.start.row;
15720 while row <= range.end.row {
15721 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15722 {
15723 found = true;
15724 row = crease.range().end.row + 1;
15725 to_fold.push(crease);
15726 } else {
15727 row += 1
15728 }
15729 }
15730 if found {
15731 continue;
15732 }
15733 }
15734
15735 for row in (0..=range.start.row).rev() {
15736 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15737 if crease.range().end.row >= buffer_start_row {
15738 to_fold.push(crease);
15739 if row <= range.start.row {
15740 break;
15741 }
15742 }
15743 }
15744 }
15745 }
15746
15747 self.fold_creases(to_fold, true, window, cx);
15748 } else {
15749 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15750 let buffer_ids = self
15751 .selections
15752 .disjoint_anchor_ranges()
15753 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15754 .collect::<HashSet<_>>();
15755 for buffer_id in buffer_ids {
15756 self.fold_buffer(buffer_id, cx);
15757 }
15758 }
15759 }
15760
15761 fn fold_at_level(
15762 &mut self,
15763 fold_at: &FoldAtLevel,
15764 window: &mut Window,
15765 cx: &mut Context<Self>,
15766 ) {
15767 if !self.buffer.read(cx).is_singleton() {
15768 return;
15769 }
15770
15771 let fold_at_level = fold_at.0;
15772 let snapshot = self.buffer.read(cx).snapshot(cx);
15773 let mut to_fold = Vec::new();
15774 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15775
15776 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15777 while start_row < end_row {
15778 match self
15779 .snapshot(window, cx)
15780 .crease_for_buffer_row(MultiBufferRow(start_row))
15781 {
15782 Some(crease) => {
15783 let nested_start_row = crease.range().start.row + 1;
15784 let nested_end_row = crease.range().end.row;
15785
15786 if current_level < fold_at_level {
15787 stack.push((nested_start_row, nested_end_row, current_level + 1));
15788 } else if current_level == fold_at_level {
15789 to_fold.push(crease);
15790 }
15791
15792 start_row = nested_end_row + 1;
15793 }
15794 None => start_row += 1,
15795 }
15796 }
15797 }
15798
15799 self.fold_creases(to_fold, true, window, cx);
15800 }
15801
15802 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15803 if self.buffer.read(cx).is_singleton() {
15804 let mut fold_ranges = Vec::new();
15805 let snapshot = self.buffer.read(cx).snapshot(cx);
15806
15807 for row in 0..snapshot.max_row().0 {
15808 if let Some(foldable_range) = self
15809 .snapshot(window, cx)
15810 .crease_for_buffer_row(MultiBufferRow(row))
15811 {
15812 fold_ranges.push(foldable_range);
15813 }
15814 }
15815
15816 self.fold_creases(fold_ranges, true, window, cx);
15817 } else {
15818 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15819 editor
15820 .update_in(cx, |editor, _, cx| {
15821 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15822 editor.fold_buffer(buffer_id, cx);
15823 }
15824 })
15825 .ok();
15826 });
15827 }
15828 }
15829
15830 pub fn fold_function_bodies(
15831 &mut self,
15832 _: &actions::FoldFunctionBodies,
15833 window: &mut Window,
15834 cx: &mut Context<Self>,
15835 ) {
15836 let snapshot = self.buffer.read(cx).snapshot(cx);
15837
15838 let ranges = snapshot
15839 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15840 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15841 .collect::<Vec<_>>();
15842
15843 let creases = ranges
15844 .into_iter()
15845 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15846 .collect();
15847
15848 self.fold_creases(creases, true, window, cx);
15849 }
15850
15851 pub fn fold_recursive(
15852 &mut self,
15853 _: &actions::FoldRecursive,
15854 window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 let mut to_fold = Vec::new();
15858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15859 let selections = self.selections.all_adjusted(cx);
15860
15861 for selection in selections {
15862 let range = selection.range().sorted();
15863 let buffer_start_row = range.start.row;
15864
15865 if range.start.row != range.end.row {
15866 let mut found = false;
15867 for row in range.start.row..=range.end.row {
15868 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15869 found = true;
15870 to_fold.push(crease);
15871 }
15872 }
15873 if found {
15874 continue;
15875 }
15876 }
15877
15878 for row in (0..=range.start.row).rev() {
15879 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15880 if crease.range().end.row >= buffer_start_row {
15881 to_fold.push(crease);
15882 } else {
15883 break;
15884 }
15885 }
15886 }
15887 }
15888
15889 self.fold_creases(to_fold, true, window, cx);
15890 }
15891
15892 pub fn fold_at(
15893 &mut self,
15894 buffer_row: MultiBufferRow,
15895 window: &mut Window,
15896 cx: &mut Context<Self>,
15897 ) {
15898 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15899
15900 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15901 let autoscroll = self
15902 .selections
15903 .all::<Point>(cx)
15904 .iter()
15905 .any(|selection| crease.range().overlaps(&selection.range()));
15906
15907 self.fold_creases(vec![crease], autoscroll, window, cx);
15908 }
15909 }
15910
15911 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15912 if self.is_singleton(cx) {
15913 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15914 let buffer = &display_map.buffer_snapshot;
15915 let selections = self.selections.all::<Point>(cx);
15916 let ranges = selections
15917 .iter()
15918 .map(|s| {
15919 let range = s.display_range(&display_map).sorted();
15920 let mut start = range.start.to_point(&display_map);
15921 let mut end = range.end.to_point(&display_map);
15922 start.column = 0;
15923 end.column = buffer.line_len(MultiBufferRow(end.row));
15924 start..end
15925 })
15926 .collect::<Vec<_>>();
15927
15928 self.unfold_ranges(&ranges, true, true, cx);
15929 } else {
15930 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15931 let buffer_ids = self
15932 .selections
15933 .disjoint_anchor_ranges()
15934 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15935 .collect::<HashSet<_>>();
15936 for buffer_id in buffer_ids {
15937 self.unfold_buffer(buffer_id, cx);
15938 }
15939 }
15940 }
15941
15942 pub fn unfold_recursive(
15943 &mut self,
15944 _: &UnfoldRecursive,
15945 _window: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) {
15948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15949 let selections = self.selections.all::<Point>(cx);
15950 let ranges = selections
15951 .iter()
15952 .map(|s| {
15953 let mut range = s.display_range(&display_map).sorted();
15954 *range.start.column_mut() = 0;
15955 *range.end.column_mut() = display_map.line_len(range.end.row());
15956 let start = range.start.to_point(&display_map);
15957 let end = range.end.to_point(&display_map);
15958 start..end
15959 })
15960 .collect::<Vec<_>>();
15961
15962 self.unfold_ranges(&ranges, true, true, cx);
15963 }
15964
15965 pub fn unfold_at(
15966 &mut self,
15967 buffer_row: MultiBufferRow,
15968 _window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15972
15973 let intersection_range = Point::new(buffer_row.0, 0)
15974 ..Point::new(
15975 buffer_row.0,
15976 display_map.buffer_snapshot.line_len(buffer_row),
15977 );
15978
15979 let autoscroll = self
15980 .selections
15981 .all::<Point>(cx)
15982 .iter()
15983 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15984
15985 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15986 }
15987
15988 pub fn unfold_all(
15989 &mut self,
15990 _: &actions::UnfoldAll,
15991 _window: &mut Window,
15992 cx: &mut Context<Self>,
15993 ) {
15994 if self.buffer.read(cx).is_singleton() {
15995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15996 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15997 } else {
15998 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15999 editor
16000 .update(cx, |editor, cx| {
16001 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16002 editor.unfold_buffer(buffer_id, cx);
16003 }
16004 })
16005 .ok();
16006 });
16007 }
16008 }
16009
16010 pub fn fold_selected_ranges(
16011 &mut self,
16012 _: &FoldSelectedRanges,
16013 window: &mut Window,
16014 cx: &mut Context<Self>,
16015 ) {
16016 let selections = self.selections.all_adjusted(cx);
16017 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16018 let ranges = selections
16019 .into_iter()
16020 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16021 .collect::<Vec<_>>();
16022 self.fold_creases(ranges, true, window, cx);
16023 }
16024
16025 pub fn fold_ranges<T: ToOffset + Clone>(
16026 &mut self,
16027 ranges: Vec<Range<T>>,
16028 auto_scroll: bool,
16029 window: &mut Window,
16030 cx: &mut Context<Self>,
16031 ) {
16032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16033 let ranges = ranges
16034 .into_iter()
16035 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16036 .collect::<Vec<_>>();
16037 self.fold_creases(ranges, auto_scroll, window, cx);
16038 }
16039
16040 pub fn fold_creases<T: ToOffset + Clone>(
16041 &mut self,
16042 creases: Vec<Crease<T>>,
16043 auto_scroll: bool,
16044 _window: &mut Window,
16045 cx: &mut Context<Self>,
16046 ) {
16047 if creases.is_empty() {
16048 return;
16049 }
16050
16051 let mut buffers_affected = HashSet::default();
16052 let multi_buffer = self.buffer().read(cx);
16053 for crease in &creases {
16054 if let Some((_, buffer, _)) =
16055 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16056 {
16057 buffers_affected.insert(buffer.read(cx).remote_id());
16058 };
16059 }
16060
16061 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16062
16063 if auto_scroll {
16064 self.request_autoscroll(Autoscroll::fit(), cx);
16065 }
16066
16067 cx.notify();
16068
16069 self.scrollbar_marker_state.dirty = true;
16070 self.folds_did_change(cx);
16071 }
16072
16073 /// Removes any folds whose ranges intersect any of the given ranges.
16074 pub fn unfold_ranges<T: ToOffset + Clone>(
16075 &mut self,
16076 ranges: &[Range<T>],
16077 inclusive: bool,
16078 auto_scroll: bool,
16079 cx: &mut Context<Self>,
16080 ) {
16081 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16082 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16083 });
16084 self.folds_did_change(cx);
16085 }
16086
16087 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16088 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16089 return;
16090 }
16091 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16092 self.display_map.update(cx, |display_map, cx| {
16093 display_map.fold_buffers([buffer_id], cx)
16094 });
16095 cx.emit(EditorEvent::BufferFoldToggled {
16096 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16097 folded: true,
16098 });
16099 cx.notify();
16100 }
16101
16102 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16103 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16104 return;
16105 }
16106 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16107 self.display_map.update(cx, |display_map, cx| {
16108 display_map.unfold_buffers([buffer_id], cx);
16109 });
16110 cx.emit(EditorEvent::BufferFoldToggled {
16111 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16112 folded: false,
16113 });
16114 cx.notify();
16115 }
16116
16117 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16118 self.display_map.read(cx).is_buffer_folded(buffer)
16119 }
16120
16121 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16122 self.display_map.read(cx).folded_buffers()
16123 }
16124
16125 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16126 self.display_map.update(cx, |display_map, cx| {
16127 display_map.disable_header_for_buffer(buffer_id, cx);
16128 });
16129 cx.notify();
16130 }
16131
16132 /// Removes any folds with the given ranges.
16133 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16134 &mut self,
16135 ranges: &[Range<T>],
16136 type_id: TypeId,
16137 auto_scroll: bool,
16138 cx: &mut Context<Self>,
16139 ) {
16140 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16141 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16142 });
16143 self.folds_did_change(cx);
16144 }
16145
16146 fn remove_folds_with<T: ToOffset + Clone>(
16147 &mut self,
16148 ranges: &[Range<T>],
16149 auto_scroll: bool,
16150 cx: &mut Context<Self>,
16151 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16152 ) {
16153 if ranges.is_empty() {
16154 return;
16155 }
16156
16157 let mut buffers_affected = HashSet::default();
16158 let multi_buffer = self.buffer().read(cx);
16159 for range in ranges {
16160 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16161 buffers_affected.insert(buffer.read(cx).remote_id());
16162 };
16163 }
16164
16165 self.display_map.update(cx, update);
16166
16167 if auto_scroll {
16168 self.request_autoscroll(Autoscroll::fit(), cx);
16169 }
16170
16171 cx.notify();
16172 self.scrollbar_marker_state.dirty = true;
16173 self.active_indent_guides_state.dirty = true;
16174 }
16175
16176 pub fn update_fold_widths(
16177 &mut self,
16178 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16179 cx: &mut Context<Self>,
16180 ) -> bool {
16181 self.display_map
16182 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16183 }
16184
16185 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16186 self.display_map.read(cx).fold_placeholder.clone()
16187 }
16188
16189 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16190 self.buffer.update(cx, |buffer, cx| {
16191 buffer.set_all_diff_hunks_expanded(cx);
16192 });
16193 }
16194
16195 pub fn expand_all_diff_hunks(
16196 &mut self,
16197 _: &ExpandAllDiffHunks,
16198 _window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) {
16201 self.buffer.update(cx, |buffer, cx| {
16202 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16203 });
16204 }
16205
16206 pub fn toggle_selected_diff_hunks(
16207 &mut self,
16208 _: &ToggleSelectedDiffHunks,
16209 _window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16213 self.toggle_diff_hunks_in_ranges(ranges, cx);
16214 }
16215
16216 pub fn diff_hunks_in_ranges<'a>(
16217 &'a self,
16218 ranges: &'a [Range<Anchor>],
16219 buffer: &'a MultiBufferSnapshot,
16220 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16221 ranges.iter().flat_map(move |range| {
16222 let end_excerpt_id = range.end.excerpt_id;
16223 let range = range.to_point(buffer);
16224 let mut peek_end = range.end;
16225 if range.end.row < buffer.max_row().0 {
16226 peek_end = Point::new(range.end.row + 1, 0);
16227 }
16228 buffer
16229 .diff_hunks_in_range(range.start..peek_end)
16230 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16231 })
16232 }
16233
16234 pub fn has_stageable_diff_hunks_in_ranges(
16235 &self,
16236 ranges: &[Range<Anchor>],
16237 snapshot: &MultiBufferSnapshot,
16238 ) -> bool {
16239 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16240 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16241 }
16242
16243 pub fn toggle_staged_selected_diff_hunks(
16244 &mut self,
16245 _: &::git::ToggleStaged,
16246 _: &mut Window,
16247 cx: &mut Context<Self>,
16248 ) {
16249 let snapshot = self.buffer.read(cx).snapshot(cx);
16250 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16251 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16252 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16253 }
16254
16255 pub fn set_render_diff_hunk_controls(
16256 &mut self,
16257 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16258 cx: &mut Context<Self>,
16259 ) {
16260 self.render_diff_hunk_controls = render_diff_hunk_controls;
16261 cx.notify();
16262 }
16263
16264 pub fn stage_and_next(
16265 &mut self,
16266 _: &::git::StageAndNext,
16267 window: &mut Window,
16268 cx: &mut Context<Self>,
16269 ) {
16270 self.do_stage_or_unstage_and_next(true, window, cx);
16271 }
16272
16273 pub fn unstage_and_next(
16274 &mut self,
16275 _: &::git::UnstageAndNext,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 self.do_stage_or_unstage_and_next(false, window, cx);
16280 }
16281
16282 pub fn stage_or_unstage_diff_hunks(
16283 &mut self,
16284 stage: bool,
16285 ranges: Vec<Range<Anchor>>,
16286 cx: &mut Context<Self>,
16287 ) {
16288 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16289 cx.spawn(async move |this, cx| {
16290 task.await?;
16291 this.update(cx, |this, cx| {
16292 let snapshot = this.buffer.read(cx).snapshot(cx);
16293 let chunk_by = this
16294 .diff_hunks_in_ranges(&ranges, &snapshot)
16295 .chunk_by(|hunk| hunk.buffer_id);
16296 for (buffer_id, hunks) in &chunk_by {
16297 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16298 }
16299 })
16300 })
16301 .detach_and_log_err(cx);
16302 }
16303
16304 fn save_buffers_for_ranges_if_needed(
16305 &mut self,
16306 ranges: &[Range<Anchor>],
16307 cx: &mut Context<Editor>,
16308 ) -> Task<Result<()>> {
16309 let multibuffer = self.buffer.read(cx);
16310 let snapshot = multibuffer.read(cx);
16311 let buffer_ids: HashSet<_> = ranges
16312 .iter()
16313 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16314 .collect();
16315 drop(snapshot);
16316
16317 let mut buffers = HashSet::default();
16318 for buffer_id in buffer_ids {
16319 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16320 let buffer = buffer_entity.read(cx);
16321 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16322 {
16323 buffers.insert(buffer_entity);
16324 }
16325 }
16326 }
16327
16328 if let Some(project) = &self.project {
16329 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16330 } else {
16331 Task::ready(Ok(()))
16332 }
16333 }
16334
16335 fn do_stage_or_unstage_and_next(
16336 &mut self,
16337 stage: bool,
16338 window: &mut Window,
16339 cx: &mut Context<Self>,
16340 ) {
16341 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16342
16343 if ranges.iter().any(|range| range.start != range.end) {
16344 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16345 return;
16346 }
16347
16348 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16349 let snapshot = self.snapshot(window, cx);
16350 let position = self.selections.newest::<Point>(cx).head();
16351 let mut row = snapshot
16352 .buffer_snapshot
16353 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16354 .find(|hunk| hunk.row_range.start.0 > position.row)
16355 .map(|hunk| hunk.row_range.start);
16356
16357 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16358 // Outside of the project diff editor, wrap around to the beginning.
16359 if !all_diff_hunks_expanded {
16360 row = row.or_else(|| {
16361 snapshot
16362 .buffer_snapshot
16363 .diff_hunks_in_range(Point::zero()..position)
16364 .find(|hunk| hunk.row_range.end.0 < position.row)
16365 .map(|hunk| hunk.row_range.start)
16366 });
16367 }
16368
16369 if let Some(row) = row {
16370 let destination = Point::new(row.0, 0);
16371 let autoscroll = Autoscroll::center();
16372
16373 self.unfold_ranges(&[destination..destination], false, false, cx);
16374 self.change_selections(Some(autoscroll), window, cx, |s| {
16375 s.select_ranges([destination..destination]);
16376 });
16377 }
16378 }
16379
16380 fn do_stage_or_unstage(
16381 &self,
16382 stage: bool,
16383 buffer_id: BufferId,
16384 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16385 cx: &mut App,
16386 ) -> Option<()> {
16387 let project = self.project.as_ref()?;
16388 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16389 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16390 let buffer_snapshot = buffer.read(cx).snapshot();
16391 let file_exists = buffer_snapshot
16392 .file()
16393 .is_some_and(|file| file.disk_state().exists());
16394 diff.update(cx, |diff, cx| {
16395 diff.stage_or_unstage_hunks(
16396 stage,
16397 &hunks
16398 .map(|hunk| buffer_diff::DiffHunk {
16399 buffer_range: hunk.buffer_range,
16400 diff_base_byte_range: hunk.diff_base_byte_range,
16401 secondary_status: hunk.secondary_status,
16402 range: Point::zero()..Point::zero(), // unused
16403 })
16404 .collect::<Vec<_>>(),
16405 &buffer_snapshot,
16406 file_exists,
16407 cx,
16408 )
16409 });
16410 None
16411 }
16412
16413 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16414 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16415 self.buffer
16416 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16417 }
16418
16419 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16420 self.buffer.update(cx, |buffer, cx| {
16421 let ranges = vec![Anchor::min()..Anchor::max()];
16422 if !buffer.all_diff_hunks_expanded()
16423 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16424 {
16425 buffer.collapse_diff_hunks(ranges, cx);
16426 true
16427 } else {
16428 false
16429 }
16430 })
16431 }
16432
16433 fn toggle_diff_hunks_in_ranges(
16434 &mut self,
16435 ranges: Vec<Range<Anchor>>,
16436 cx: &mut Context<Editor>,
16437 ) {
16438 self.buffer.update(cx, |buffer, cx| {
16439 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16440 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16441 })
16442 }
16443
16444 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16445 self.buffer.update(cx, |buffer, cx| {
16446 let snapshot = buffer.snapshot(cx);
16447 let excerpt_id = range.end.excerpt_id;
16448 let point_range = range.to_point(&snapshot);
16449 let expand = !buffer.single_hunk_is_expanded(range, cx);
16450 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16451 })
16452 }
16453
16454 pub(crate) fn apply_all_diff_hunks(
16455 &mut self,
16456 _: &ApplyAllDiffHunks,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) {
16460 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16461
16462 let buffers = self.buffer.read(cx).all_buffers();
16463 for branch_buffer in buffers {
16464 branch_buffer.update(cx, |branch_buffer, cx| {
16465 branch_buffer.merge_into_base(Vec::new(), cx);
16466 });
16467 }
16468
16469 if let Some(project) = self.project.clone() {
16470 self.save(true, project, window, cx).detach_and_log_err(cx);
16471 }
16472 }
16473
16474 pub(crate) fn apply_selected_diff_hunks(
16475 &mut self,
16476 _: &ApplyDiffHunk,
16477 window: &mut Window,
16478 cx: &mut Context<Self>,
16479 ) {
16480 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16481 let snapshot = self.snapshot(window, cx);
16482 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16483 let mut ranges_by_buffer = HashMap::default();
16484 self.transact(window, cx, |editor, _window, cx| {
16485 for hunk in hunks {
16486 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16487 ranges_by_buffer
16488 .entry(buffer.clone())
16489 .or_insert_with(Vec::new)
16490 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16491 }
16492 }
16493
16494 for (buffer, ranges) in ranges_by_buffer {
16495 buffer.update(cx, |buffer, cx| {
16496 buffer.merge_into_base(ranges, cx);
16497 });
16498 }
16499 });
16500
16501 if let Some(project) = self.project.clone() {
16502 self.save(true, project, window, cx).detach_and_log_err(cx);
16503 }
16504 }
16505
16506 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16507 if hovered != self.gutter_hovered {
16508 self.gutter_hovered = hovered;
16509 cx.notify();
16510 }
16511 }
16512
16513 pub fn insert_blocks(
16514 &mut self,
16515 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16516 autoscroll: Option<Autoscroll>,
16517 cx: &mut Context<Self>,
16518 ) -> Vec<CustomBlockId> {
16519 let blocks = self
16520 .display_map
16521 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16522 if let Some(autoscroll) = autoscroll {
16523 self.request_autoscroll(autoscroll, cx);
16524 }
16525 cx.notify();
16526 blocks
16527 }
16528
16529 pub fn resize_blocks(
16530 &mut self,
16531 heights: HashMap<CustomBlockId, u32>,
16532 autoscroll: Option<Autoscroll>,
16533 cx: &mut Context<Self>,
16534 ) {
16535 self.display_map
16536 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16537 if let Some(autoscroll) = autoscroll {
16538 self.request_autoscroll(autoscroll, cx);
16539 }
16540 cx.notify();
16541 }
16542
16543 pub fn replace_blocks(
16544 &mut self,
16545 renderers: HashMap<CustomBlockId, RenderBlock>,
16546 autoscroll: Option<Autoscroll>,
16547 cx: &mut Context<Self>,
16548 ) {
16549 self.display_map
16550 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16551 if let Some(autoscroll) = autoscroll {
16552 self.request_autoscroll(autoscroll, cx);
16553 }
16554 cx.notify();
16555 }
16556
16557 pub fn remove_blocks(
16558 &mut self,
16559 block_ids: HashSet<CustomBlockId>,
16560 autoscroll: Option<Autoscroll>,
16561 cx: &mut Context<Self>,
16562 ) {
16563 self.display_map.update(cx, |display_map, cx| {
16564 display_map.remove_blocks(block_ids, cx)
16565 });
16566 if let Some(autoscroll) = autoscroll {
16567 self.request_autoscroll(autoscroll, cx);
16568 }
16569 cx.notify();
16570 }
16571
16572 pub fn row_for_block(
16573 &self,
16574 block_id: CustomBlockId,
16575 cx: &mut Context<Self>,
16576 ) -> Option<DisplayRow> {
16577 self.display_map
16578 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16579 }
16580
16581 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16582 self.focused_block = Some(focused_block);
16583 }
16584
16585 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16586 self.focused_block.take()
16587 }
16588
16589 pub fn insert_creases(
16590 &mut self,
16591 creases: impl IntoIterator<Item = Crease<Anchor>>,
16592 cx: &mut Context<Self>,
16593 ) -> Vec<CreaseId> {
16594 self.display_map
16595 .update(cx, |map, cx| map.insert_creases(creases, cx))
16596 }
16597
16598 pub fn remove_creases(
16599 &mut self,
16600 ids: impl IntoIterator<Item = CreaseId>,
16601 cx: &mut Context<Self>,
16602 ) -> Vec<(CreaseId, Range<Anchor>)> {
16603 self.display_map
16604 .update(cx, |map, cx| map.remove_creases(ids, cx))
16605 }
16606
16607 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16608 self.display_map
16609 .update(cx, |map, cx| map.snapshot(cx))
16610 .longest_row()
16611 }
16612
16613 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16614 self.display_map
16615 .update(cx, |map, cx| map.snapshot(cx))
16616 .max_point()
16617 }
16618
16619 pub fn text(&self, cx: &App) -> String {
16620 self.buffer.read(cx).read(cx).text()
16621 }
16622
16623 pub fn is_empty(&self, cx: &App) -> bool {
16624 self.buffer.read(cx).read(cx).is_empty()
16625 }
16626
16627 pub fn text_option(&self, cx: &App) -> Option<String> {
16628 let text = self.text(cx);
16629 let text = text.trim();
16630
16631 if text.is_empty() {
16632 return None;
16633 }
16634
16635 Some(text.to_string())
16636 }
16637
16638 pub fn set_text(
16639 &mut self,
16640 text: impl Into<Arc<str>>,
16641 window: &mut Window,
16642 cx: &mut Context<Self>,
16643 ) {
16644 self.transact(window, cx, |this, _, cx| {
16645 this.buffer
16646 .read(cx)
16647 .as_singleton()
16648 .expect("you can only call set_text on editors for singleton buffers")
16649 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16650 });
16651 }
16652
16653 pub fn display_text(&self, cx: &mut App) -> String {
16654 self.display_map
16655 .update(cx, |map, cx| map.snapshot(cx))
16656 .text()
16657 }
16658
16659 fn create_minimap(
16660 &self,
16661 minimap_settings: MinimapSettings,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) -> Option<Entity<Self>> {
16665 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16666 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16667 }
16668
16669 fn initialize_new_minimap(
16670 &self,
16671 minimap_settings: MinimapSettings,
16672 window: &mut Window,
16673 cx: &mut Context<Self>,
16674 ) -> Entity<Self> {
16675 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16676
16677 let mut minimap = Editor::new_internal(
16678 EditorMode::Minimap {
16679 parent: cx.weak_entity(),
16680 },
16681 self.buffer.clone(),
16682 self.project.clone(),
16683 Some(self.display_map.clone()),
16684 window,
16685 cx,
16686 );
16687 minimap.scroll_manager.clone_state(&self.scroll_manager);
16688 minimap.set_text_style_refinement(TextStyleRefinement {
16689 font_size: Some(MINIMAP_FONT_SIZE),
16690 font_weight: Some(MINIMAP_FONT_WEIGHT),
16691 ..Default::default()
16692 });
16693 minimap.update_minimap_configuration(minimap_settings, cx);
16694 cx.new(|_| minimap)
16695 }
16696
16697 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16698 let current_line_highlight = minimap_settings
16699 .current_line_highlight
16700 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16701 self.set_current_line_highlight(Some(current_line_highlight));
16702 }
16703
16704 pub fn minimap(&self) -> Option<&Entity<Self>> {
16705 self.minimap
16706 .as_ref()
16707 .filter(|_| self.minimap_visibility.visible())
16708 }
16709
16710 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16711 let mut wrap_guides = smallvec![];
16712
16713 if self.show_wrap_guides == Some(false) {
16714 return wrap_guides;
16715 }
16716
16717 let settings = self.buffer.read(cx).language_settings(cx);
16718 if settings.show_wrap_guides {
16719 match self.soft_wrap_mode(cx) {
16720 SoftWrap::Column(soft_wrap) => {
16721 wrap_guides.push((soft_wrap as usize, true));
16722 }
16723 SoftWrap::Bounded(soft_wrap) => {
16724 wrap_guides.push((soft_wrap as usize, true));
16725 }
16726 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16727 }
16728 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16729 }
16730
16731 wrap_guides
16732 }
16733
16734 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16735 let settings = self.buffer.read(cx).language_settings(cx);
16736 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16737 match mode {
16738 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16739 SoftWrap::None
16740 }
16741 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16742 language_settings::SoftWrap::PreferredLineLength => {
16743 SoftWrap::Column(settings.preferred_line_length)
16744 }
16745 language_settings::SoftWrap::Bounded => {
16746 SoftWrap::Bounded(settings.preferred_line_length)
16747 }
16748 }
16749 }
16750
16751 pub fn set_soft_wrap_mode(
16752 &mut self,
16753 mode: language_settings::SoftWrap,
16754
16755 cx: &mut Context<Self>,
16756 ) {
16757 self.soft_wrap_mode_override = Some(mode);
16758 cx.notify();
16759 }
16760
16761 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16762 self.hard_wrap = hard_wrap;
16763 cx.notify();
16764 }
16765
16766 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16767 self.text_style_refinement = Some(style);
16768 }
16769
16770 /// called by the Element so we know what style we were most recently rendered with.
16771 pub(crate) fn set_style(
16772 &mut self,
16773 style: EditorStyle,
16774 window: &mut Window,
16775 cx: &mut Context<Self>,
16776 ) {
16777 // We intentionally do not inform the display map about the minimap style
16778 // so that wrapping is not recalculated and stays consistent for the editor
16779 // and its linked minimap.
16780 if !self.mode.is_minimap() {
16781 let rem_size = window.rem_size();
16782 self.display_map.update(cx, |map, cx| {
16783 map.set_font(
16784 style.text.font(),
16785 style.text.font_size.to_pixels(rem_size),
16786 cx,
16787 )
16788 });
16789 }
16790 self.style = Some(style);
16791 }
16792
16793 pub fn style(&self) -> Option<&EditorStyle> {
16794 self.style.as_ref()
16795 }
16796
16797 // Called by the element. This method is not designed to be called outside of the editor
16798 // element's layout code because it does not notify when rewrapping is computed synchronously.
16799 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16800 self.display_map
16801 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16802 }
16803
16804 pub fn set_soft_wrap(&mut self) {
16805 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16806 }
16807
16808 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16809 if self.soft_wrap_mode_override.is_some() {
16810 self.soft_wrap_mode_override.take();
16811 } else {
16812 let soft_wrap = match self.soft_wrap_mode(cx) {
16813 SoftWrap::GitDiff => return,
16814 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16815 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16816 language_settings::SoftWrap::None
16817 }
16818 };
16819 self.soft_wrap_mode_override = Some(soft_wrap);
16820 }
16821 cx.notify();
16822 }
16823
16824 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16825 let Some(workspace) = self.workspace() else {
16826 return;
16827 };
16828 let fs = workspace.read(cx).app_state().fs.clone();
16829 let current_show = TabBarSettings::get_global(cx).show;
16830 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16831 setting.show = Some(!current_show);
16832 });
16833 }
16834
16835 pub fn toggle_indent_guides(
16836 &mut self,
16837 _: &ToggleIndentGuides,
16838 _: &mut Window,
16839 cx: &mut Context<Self>,
16840 ) {
16841 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16842 self.buffer
16843 .read(cx)
16844 .language_settings(cx)
16845 .indent_guides
16846 .enabled
16847 });
16848 self.show_indent_guides = Some(!currently_enabled);
16849 cx.notify();
16850 }
16851
16852 fn should_show_indent_guides(&self) -> Option<bool> {
16853 self.show_indent_guides
16854 }
16855
16856 pub fn toggle_line_numbers(
16857 &mut self,
16858 _: &ToggleLineNumbers,
16859 _: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let mut editor_settings = EditorSettings::get_global(cx).clone();
16863 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16864 EditorSettings::override_global(editor_settings, cx);
16865 }
16866
16867 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16868 if let Some(show_line_numbers) = self.show_line_numbers {
16869 return show_line_numbers;
16870 }
16871 EditorSettings::get_global(cx).gutter.line_numbers
16872 }
16873
16874 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16875 self.use_relative_line_numbers
16876 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16877 }
16878
16879 pub fn toggle_relative_line_numbers(
16880 &mut self,
16881 _: &ToggleRelativeLineNumbers,
16882 _: &mut Window,
16883 cx: &mut Context<Self>,
16884 ) {
16885 let is_relative = self.should_use_relative_line_numbers(cx);
16886 self.set_relative_line_number(Some(!is_relative), cx)
16887 }
16888
16889 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16890 self.use_relative_line_numbers = is_relative;
16891 cx.notify();
16892 }
16893
16894 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16895 self.show_gutter = show_gutter;
16896 cx.notify();
16897 }
16898
16899 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16900 self.show_scrollbars = show_scrollbars;
16901 cx.notify();
16902 }
16903
16904 pub fn set_minimap_visibility(
16905 &mut self,
16906 minimap_visibility: MinimapVisibility,
16907 window: &mut Window,
16908 cx: &mut Context<Self>,
16909 ) {
16910 if self.minimap_visibility != minimap_visibility {
16911 if minimap_visibility.visible() && self.minimap.is_none() {
16912 let minimap_settings = EditorSettings::get_global(cx).minimap;
16913 self.minimap =
16914 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16915 }
16916 self.minimap_visibility = minimap_visibility;
16917 cx.notify();
16918 }
16919 }
16920
16921 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16922 self.set_show_scrollbars(false, cx);
16923 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16924 }
16925
16926 /// Normally the text in full mode and auto height editors is padded on the
16927 /// left side by roughly half a character width for improved hit testing.
16928 ///
16929 /// Use this method to disable this for cases where this is not wanted (e.g.
16930 /// if you want to align the editor text with some other text above or below)
16931 /// or if you want to add this padding to single-line editors.
16932 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
16933 self.offset_content = offset_content;
16934 cx.notify();
16935 }
16936
16937 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16938 self.show_line_numbers = Some(show_line_numbers);
16939 cx.notify();
16940 }
16941
16942 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16943 self.disable_expand_excerpt_buttons = true;
16944 cx.notify();
16945 }
16946
16947 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16948 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16949 cx.notify();
16950 }
16951
16952 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16953 self.show_code_actions = Some(show_code_actions);
16954 cx.notify();
16955 }
16956
16957 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16958 self.show_runnables = Some(show_runnables);
16959 cx.notify();
16960 }
16961
16962 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16963 self.show_breakpoints = Some(show_breakpoints);
16964 cx.notify();
16965 }
16966
16967 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16968 if self.display_map.read(cx).masked != masked {
16969 self.display_map.update(cx, |map, _| map.masked = masked);
16970 }
16971 cx.notify()
16972 }
16973
16974 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16975 self.show_wrap_guides = Some(show_wrap_guides);
16976 cx.notify();
16977 }
16978
16979 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16980 self.show_indent_guides = Some(show_indent_guides);
16981 cx.notify();
16982 }
16983
16984 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16985 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16986 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16987 if let Some(dir) = file.abs_path(cx).parent() {
16988 return Some(dir.to_owned());
16989 }
16990 }
16991
16992 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16993 return Some(project_path.path.to_path_buf());
16994 }
16995 }
16996
16997 None
16998 }
16999
17000 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17001 self.active_excerpt(cx)?
17002 .1
17003 .read(cx)
17004 .file()
17005 .and_then(|f| f.as_local())
17006 }
17007
17008 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17009 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17010 let buffer = buffer.read(cx);
17011 if let Some(project_path) = buffer.project_path(cx) {
17012 let project = self.project.as_ref()?.read(cx);
17013 project.absolute_path(&project_path, cx)
17014 } else {
17015 buffer
17016 .file()
17017 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17018 }
17019 })
17020 }
17021
17022 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17023 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17024 let project_path = buffer.read(cx).project_path(cx)?;
17025 let project = self.project.as_ref()?.read(cx);
17026 let entry = project.entry_for_path(&project_path, cx)?;
17027 let path = entry.path.to_path_buf();
17028 Some(path)
17029 })
17030 }
17031
17032 pub fn reveal_in_finder(
17033 &mut self,
17034 _: &RevealInFileManager,
17035 _window: &mut Window,
17036 cx: &mut Context<Self>,
17037 ) {
17038 if let Some(target) = self.target_file(cx) {
17039 cx.reveal_path(&target.abs_path(cx));
17040 }
17041 }
17042
17043 pub fn copy_path(
17044 &mut self,
17045 _: &zed_actions::workspace::CopyPath,
17046 _window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 if let Some(path) = self.target_file_abs_path(cx) {
17050 if let Some(path) = path.to_str() {
17051 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17052 }
17053 }
17054 }
17055
17056 pub fn copy_relative_path(
17057 &mut self,
17058 _: &zed_actions::workspace::CopyRelativePath,
17059 _window: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) {
17062 if let Some(path) = self.target_file_path(cx) {
17063 if let Some(path) = path.to_str() {
17064 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17065 }
17066 }
17067 }
17068
17069 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17070 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17071 buffer.read(cx).project_path(cx)
17072 } else {
17073 None
17074 }
17075 }
17076
17077 // Returns true if the editor handled a go-to-line request
17078 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17079 maybe!({
17080 let breakpoint_store = self.breakpoint_store.as_ref()?;
17081
17082 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17083 else {
17084 self.clear_row_highlights::<ActiveDebugLine>();
17085 return None;
17086 };
17087
17088 let position = active_stack_frame.position;
17089 let buffer_id = position.buffer_id?;
17090 let snapshot = self
17091 .project
17092 .as_ref()?
17093 .read(cx)
17094 .buffer_for_id(buffer_id, cx)?
17095 .read(cx)
17096 .snapshot();
17097
17098 let mut handled = false;
17099 for (id, ExcerptRange { context, .. }) in
17100 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17101 {
17102 if context.start.cmp(&position, &snapshot).is_ge()
17103 || context.end.cmp(&position, &snapshot).is_lt()
17104 {
17105 continue;
17106 }
17107 let snapshot = self.buffer.read(cx).snapshot(cx);
17108 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17109
17110 handled = true;
17111 self.clear_row_highlights::<ActiveDebugLine>();
17112
17113 self.go_to_line::<ActiveDebugLine>(
17114 multibuffer_anchor,
17115 Some(cx.theme().colors().editor_debugger_active_line_background),
17116 window,
17117 cx,
17118 );
17119
17120 cx.notify();
17121 }
17122
17123 handled.then_some(())
17124 })
17125 .is_some()
17126 }
17127
17128 pub fn copy_file_name_without_extension(
17129 &mut self,
17130 _: &CopyFileNameWithoutExtension,
17131 _: &mut Window,
17132 cx: &mut Context<Self>,
17133 ) {
17134 if let Some(file) = self.target_file(cx) {
17135 if let Some(file_stem) = file.path().file_stem() {
17136 if let Some(name) = file_stem.to_str() {
17137 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17138 }
17139 }
17140 }
17141 }
17142
17143 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17144 if let Some(file) = self.target_file(cx) {
17145 if let Some(file_name) = file.path().file_name() {
17146 if let Some(name) = file_name.to_str() {
17147 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17148 }
17149 }
17150 }
17151 }
17152
17153 pub fn toggle_git_blame(
17154 &mut self,
17155 _: &::git::Blame,
17156 window: &mut Window,
17157 cx: &mut Context<Self>,
17158 ) {
17159 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17160
17161 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17162 self.start_git_blame(true, window, cx);
17163 }
17164
17165 cx.notify();
17166 }
17167
17168 pub fn toggle_git_blame_inline(
17169 &mut self,
17170 _: &ToggleGitBlameInline,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) {
17174 self.toggle_git_blame_inline_internal(true, window, cx);
17175 cx.notify();
17176 }
17177
17178 pub fn open_git_blame_commit(
17179 &mut self,
17180 _: &OpenGitBlameCommit,
17181 window: &mut Window,
17182 cx: &mut Context<Self>,
17183 ) {
17184 self.open_git_blame_commit_internal(window, cx);
17185 }
17186
17187 fn open_git_blame_commit_internal(
17188 &mut self,
17189 window: &mut Window,
17190 cx: &mut Context<Self>,
17191 ) -> Option<()> {
17192 let blame = self.blame.as_ref()?;
17193 let snapshot = self.snapshot(window, cx);
17194 let cursor = self.selections.newest::<Point>(cx).head();
17195 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17196 let blame_entry = blame
17197 .update(cx, |blame, cx| {
17198 blame
17199 .blame_for_rows(
17200 &[RowInfo {
17201 buffer_id: Some(buffer.remote_id()),
17202 buffer_row: Some(point.row),
17203 ..Default::default()
17204 }],
17205 cx,
17206 )
17207 .next()
17208 })
17209 .flatten()?;
17210 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17211 let repo = blame.read(cx).repository(cx)?;
17212 let workspace = self.workspace()?.downgrade();
17213 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17214 None
17215 }
17216
17217 pub fn git_blame_inline_enabled(&self) -> bool {
17218 self.git_blame_inline_enabled
17219 }
17220
17221 pub fn toggle_selection_menu(
17222 &mut self,
17223 _: &ToggleSelectionMenu,
17224 _: &mut Window,
17225 cx: &mut Context<Self>,
17226 ) {
17227 self.show_selection_menu = self
17228 .show_selection_menu
17229 .map(|show_selections_menu| !show_selections_menu)
17230 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17231
17232 cx.notify();
17233 }
17234
17235 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17236 self.show_selection_menu
17237 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17238 }
17239
17240 fn start_git_blame(
17241 &mut self,
17242 user_triggered: bool,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) {
17246 if let Some(project) = self.project.as_ref() {
17247 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17248 return;
17249 };
17250
17251 if buffer.read(cx).file().is_none() {
17252 return;
17253 }
17254
17255 let focused = self.focus_handle(cx).contains_focused(window, cx);
17256
17257 let project = project.clone();
17258 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17259 self.blame_subscription =
17260 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17261 self.blame = Some(blame);
17262 }
17263 }
17264
17265 fn toggle_git_blame_inline_internal(
17266 &mut self,
17267 user_triggered: bool,
17268 window: &mut Window,
17269 cx: &mut Context<Self>,
17270 ) {
17271 if self.git_blame_inline_enabled {
17272 self.git_blame_inline_enabled = false;
17273 self.show_git_blame_inline = false;
17274 self.show_git_blame_inline_delay_task.take();
17275 } else {
17276 self.git_blame_inline_enabled = true;
17277 self.start_git_blame_inline(user_triggered, window, cx);
17278 }
17279
17280 cx.notify();
17281 }
17282
17283 fn start_git_blame_inline(
17284 &mut self,
17285 user_triggered: bool,
17286 window: &mut Window,
17287 cx: &mut Context<Self>,
17288 ) {
17289 self.start_git_blame(user_triggered, window, cx);
17290
17291 if ProjectSettings::get_global(cx)
17292 .git
17293 .inline_blame_delay()
17294 .is_some()
17295 {
17296 self.start_inline_blame_timer(window, cx);
17297 } else {
17298 self.show_git_blame_inline = true
17299 }
17300 }
17301
17302 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17303 self.blame.as_ref()
17304 }
17305
17306 pub fn show_git_blame_gutter(&self) -> bool {
17307 self.show_git_blame_gutter
17308 }
17309
17310 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17311 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17312 }
17313
17314 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17315 self.show_git_blame_inline
17316 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17317 && !self.newest_selection_head_on_empty_line(cx)
17318 && self.has_blame_entries(cx)
17319 }
17320
17321 fn has_blame_entries(&self, cx: &App) -> bool {
17322 self.blame()
17323 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17324 }
17325
17326 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17327 let cursor_anchor = self.selections.newest_anchor().head();
17328
17329 let snapshot = self.buffer.read(cx).snapshot(cx);
17330 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17331
17332 snapshot.line_len(buffer_row) == 0
17333 }
17334
17335 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17336 let buffer_and_selection = maybe!({
17337 let selection = self.selections.newest::<Point>(cx);
17338 let selection_range = selection.range();
17339
17340 let multi_buffer = self.buffer().read(cx);
17341 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17342 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17343
17344 let (buffer, range, _) = if selection.reversed {
17345 buffer_ranges.first()
17346 } else {
17347 buffer_ranges.last()
17348 }?;
17349
17350 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17351 ..text::ToPoint::to_point(&range.end, &buffer).row;
17352 Some((
17353 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17354 selection,
17355 ))
17356 });
17357
17358 let Some((buffer, selection)) = buffer_and_selection else {
17359 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17360 };
17361
17362 let Some(project) = self.project.as_ref() else {
17363 return Task::ready(Err(anyhow!("editor does not have project")));
17364 };
17365
17366 project.update(cx, |project, cx| {
17367 project.get_permalink_to_line(&buffer, selection, cx)
17368 })
17369 }
17370
17371 pub fn copy_permalink_to_line(
17372 &mut self,
17373 _: &CopyPermalinkToLine,
17374 window: &mut Window,
17375 cx: &mut Context<Self>,
17376 ) {
17377 let permalink_task = self.get_permalink_to_line(cx);
17378 let workspace = self.workspace();
17379
17380 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17381 Ok(permalink) => {
17382 cx.update(|_, cx| {
17383 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17384 })
17385 .ok();
17386 }
17387 Err(err) => {
17388 let message = format!("Failed to copy permalink: {err}");
17389
17390 anyhow::Result::<()>::Err(err).log_err();
17391
17392 if let Some(workspace) = workspace {
17393 workspace
17394 .update_in(cx, |workspace, _, cx| {
17395 struct CopyPermalinkToLine;
17396
17397 workspace.show_toast(
17398 Toast::new(
17399 NotificationId::unique::<CopyPermalinkToLine>(),
17400 message,
17401 ),
17402 cx,
17403 )
17404 })
17405 .ok();
17406 }
17407 }
17408 })
17409 .detach();
17410 }
17411
17412 pub fn copy_file_location(
17413 &mut self,
17414 _: &CopyFileLocation,
17415 _: &mut Window,
17416 cx: &mut Context<Self>,
17417 ) {
17418 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17419 if let Some(file) = self.target_file(cx) {
17420 if let Some(path) = file.path().to_str() {
17421 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17422 }
17423 }
17424 }
17425
17426 pub fn open_permalink_to_line(
17427 &mut self,
17428 _: &OpenPermalinkToLine,
17429 window: &mut Window,
17430 cx: &mut Context<Self>,
17431 ) {
17432 let permalink_task = self.get_permalink_to_line(cx);
17433 let workspace = self.workspace();
17434
17435 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17436 Ok(permalink) => {
17437 cx.update(|_, cx| {
17438 cx.open_url(permalink.as_ref());
17439 })
17440 .ok();
17441 }
17442 Err(err) => {
17443 let message = format!("Failed to open permalink: {err}");
17444
17445 anyhow::Result::<()>::Err(err).log_err();
17446
17447 if let Some(workspace) = workspace {
17448 workspace
17449 .update(cx, |workspace, cx| {
17450 struct OpenPermalinkToLine;
17451
17452 workspace.show_toast(
17453 Toast::new(
17454 NotificationId::unique::<OpenPermalinkToLine>(),
17455 message,
17456 ),
17457 cx,
17458 )
17459 })
17460 .ok();
17461 }
17462 }
17463 })
17464 .detach();
17465 }
17466
17467 pub fn insert_uuid_v4(
17468 &mut self,
17469 _: &InsertUuidV4,
17470 window: &mut Window,
17471 cx: &mut Context<Self>,
17472 ) {
17473 self.insert_uuid(UuidVersion::V4, window, cx);
17474 }
17475
17476 pub fn insert_uuid_v7(
17477 &mut self,
17478 _: &InsertUuidV7,
17479 window: &mut Window,
17480 cx: &mut Context<Self>,
17481 ) {
17482 self.insert_uuid(UuidVersion::V7, window, cx);
17483 }
17484
17485 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17486 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17487 self.transact(window, cx, |this, window, cx| {
17488 let edits = this
17489 .selections
17490 .all::<Point>(cx)
17491 .into_iter()
17492 .map(|selection| {
17493 let uuid = match version {
17494 UuidVersion::V4 => uuid::Uuid::new_v4(),
17495 UuidVersion::V7 => uuid::Uuid::now_v7(),
17496 };
17497
17498 (selection.range(), uuid.to_string())
17499 });
17500 this.edit(edits, cx);
17501 this.refresh_inline_completion(true, false, window, cx);
17502 });
17503 }
17504
17505 pub fn open_selections_in_multibuffer(
17506 &mut self,
17507 _: &OpenSelectionsInMultibuffer,
17508 window: &mut Window,
17509 cx: &mut Context<Self>,
17510 ) {
17511 let multibuffer = self.buffer.read(cx);
17512
17513 let Some(buffer) = multibuffer.as_singleton() else {
17514 return;
17515 };
17516
17517 let Some(workspace) = self.workspace() else {
17518 return;
17519 };
17520
17521 let locations = self
17522 .selections
17523 .disjoint_anchors()
17524 .iter()
17525 .map(|selection| {
17526 let range = if selection.reversed {
17527 selection.end.text_anchor..selection.start.text_anchor
17528 } else {
17529 selection.start.text_anchor..selection.end.text_anchor
17530 };
17531 Location {
17532 buffer: buffer.clone(),
17533 range,
17534 }
17535 })
17536 .collect::<Vec<_>>();
17537
17538 let title = multibuffer.title(cx).to_string();
17539
17540 cx.spawn_in(window, async move |_, cx| {
17541 workspace.update_in(cx, |workspace, window, cx| {
17542 Self::open_locations_in_multibuffer(
17543 workspace,
17544 locations,
17545 format!("Selections for '{title}'"),
17546 false,
17547 MultibufferSelectionMode::All,
17548 window,
17549 cx,
17550 );
17551 })
17552 })
17553 .detach();
17554 }
17555
17556 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17557 /// last highlight added will be used.
17558 ///
17559 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17560 pub fn highlight_rows<T: 'static>(
17561 &mut self,
17562 range: Range<Anchor>,
17563 color: Hsla,
17564 options: RowHighlightOptions,
17565 cx: &mut Context<Self>,
17566 ) {
17567 let snapshot = self.buffer().read(cx).snapshot(cx);
17568 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17569 let ix = row_highlights.binary_search_by(|highlight| {
17570 Ordering::Equal
17571 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17572 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17573 });
17574
17575 if let Err(mut ix) = ix {
17576 let index = post_inc(&mut self.highlight_order);
17577
17578 // If this range intersects with the preceding highlight, then merge it with
17579 // the preceding highlight. Otherwise insert a new highlight.
17580 let mut merged = false;
17581 if ix > 0 {
17582 let prev_highlight = &mut row_highlights[ix - 1];
17583 if prev_highlight
17584 .range
17585 .end
17586 .cmp(&range.start, &snapshot)
17587 .is_ge()
17588 {
17589 ix -= 1;
17590 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17591 prev_highlight.range.end = range.end;
17592 }
17593 merged = true;
17594 prev_highlight.index = index;
17595 prev_highlight.color = color;
17596 prev_highlight.options = options;
17597 }
17598 }
17599
17600 if !merged {
17601 row_highlights.insert(
17602 ix,
17603 RowHighlight {
17604 range: range.clone(),
17605 index,
17606 color,
17607 options,
17608 type_id: TypeId::of::<T>(),
17609 },
17610 );
17611 }
17612
17613 // If any of the following highlights intersect with this one, merge them.
17614 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17615 let highlight = &row_highlights[ix];
17616 if next_highlight
17617 .range
17618 .start
17619 .cmp(&highlight.range.end, &snapshot)
17620 .is_le()
17621 {
17622 if next_highlight
17623 .range
17624 .end
17625 .cmp(&highlight.range.end, &snapshot)
17626 .is_gt()
17627 {
17628 row_highlights[ix].range.end = next_highlight.range.end;
17629 }
17630 row_highlights.remove(ix + 1);
17631 } else {
17632 break;
17633 }
17634 }
17635 }
17636 }
17637
17638 /// Remove any highlighted row ranges of the given type that intersect the
17639 /// given ranges.
17640 pub fn remove_highlighted_rows<T: 'static>(
17641 &mut self,
17642 ranges_to_remove: Vec<Range<Anchor>>,
17643 cx: &mut Context<Self>,
17644 ) {
17645 let snapshot = self.buffer().read(cx).snapshot(cx);
17646 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17647 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17648 row_highlights.retain(|highlight| {
17649 while let Some(range_to_remove) = ranges_to_remove.peek() {
17650 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17651 Ordering::Less | Ordering::Equal => {
17652 ranges_to_remove.next();
17653 }
17654 Ordering::Greater => {
17655 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17656 Ordering::Less | Ordering::Equal => {
17657 return false;
17658 }
17659 Ordering::Greater => break,
17660 }
17661 }
17662 }
17663 }
17664
17665 true
17666 })
17667 }
17668
17669 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17670 pub fn clear_row_highlights<T: 'static>(&mut self) {
17671 self.highlighted_rows.remove(&TypeId::of::<T>());
17672 }
17673
17674 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17675 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17676 self.highlighted_rows
17677 .get(&TypeId::of::<T>())
17678 .map_or(&[] as &[_], |vec| vec.as_slice())
17679 .iter()
17680 .map(|highlight| (highlight.range.clone(), highlight.color))
17681 }
17682
17683 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17684 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17685 /// Allows to ignore certain kinds of highlights.
17686 pub fn highlighted_display_rows(
17687 &self,
17688 window: &mut Window,
17689 cx: &mut App,
17690 ) -> BTreeMap<DisplayRow, LineHighlight> {
17691 let snapshot = self.snapshot(window, cx);
17692 let mut used_highlight_orders = HashMap::default();
17693 self.highlighted_rows
17694 .iter()
17695 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17696 .fold(
17697 BTreeMap::<DisplayRow, LineHighlight>::new(),
17698 |mut unique_rows, highlight| {
17699 let start = highlight.range.start.to_display_point(&snapshot);
17700 let end = highlight.range.end.to_display_point(&snapshot);
17701 let start_row = start.row().0;
17702 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17703 && end.column() == 0
17704 {
17705 end.row().0.saturating_sub(1)
17706 } else {
17707 end.row().0
17708 };
17709 for row in start_row..=end_row {
17710 let used_index =
17711 used_highlight_orders.entry(row).or_insert(highlight.index);
17712 if highlight.index >= *used_index {
17713 *used_index = highlight.index;
17714 unique_rows.insert(
17715 DisplayRow(row),
17716 LineHighlight {
17717 include_gutter: highlight.options.include_gutter,
17718 border: None,
17719 background: highlight.color.into(),
17720 type_id: Some(highlight.type_id),
17721 },
17722 );
17723 }
17724 }
17725 unique_rows
17726 },
17727 )
17728 }
17729
17730 pub fn highlighted_display_row_for_autoscroll(
17731 &self,
17732 snapshot: &DisplaySnapshot,
17733 ) -> Option<DisplayRow> {
17734 self.highlighted_rows
17735 .values()
17736 .flat_map(|highlighted_rows| highlighted_rows.iter())
17737 .filter_map(|highlight| {
17738 if highlight.options.autoscroll {
17739 Some(highlight.range.start.to_display_point(snapshot).row())
17740 } else {
17741 None
17742 }
17743 })
17744 .min()
17745 }
17746
17747 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17748 self.highlight_background::<SearchWithinRange>(
17749 ranges,
17750 |colors| colors.editor_document_highlight_read_background,
17751 cx,
17752 )
17753 }
17754
17755 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17756 self.breadcrumb_header = Some(new_header);
17757 }
17758
17759 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17760 self.clear_background_highlights::<SearchWithinRange>(cx);
17761 }
17762
17763 pub fn highlight_background<T: 'static>(
17764 &mut self,
17765 ranges: &[Range<Anchor>],
17766 color_fetcher: fn(&ThemeColors) -> Hsla,
17767 cx: &mut Context<Self>,
17768 ) {
17769 self.background_highlights
17770 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17771 self.scrollbar_marker_state.dirty = true;
17772 cx.notify();
17773 }
17774
17775 pub fn clear_background_highlights<T: 'static>(
17776 &mut self,
17777 cx: &mut Context<Self>,
17778 ) -> Option<BackgroundHighlight> {
17779 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17780 if !text_highlights.1.is_empty() {
17781 self.scrollbar_marker_state.dirty = true;
17782 cx.notify();
17783 }
17784 Some(text_highlights)
17785 }
17786
17787 pub fn highlight_gutter<T: 'static>(
17788 &mut self,
17789 ranges: &[Range<Anchor>],
17790 color_fetcher: fn(&App) -> Hsla,
17791 cx: &mut Context<Self>,
17792 ) {
17793 self.gutter_highlights
17794 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17795 cx.notify();
17796 }
17797
17798 pub fn clear_gutter_highlights<T: 'static>(
17799 &mut self,
17800 cx: &mut Context<Self>,
17801 ) -> Option<GutterHighlight> {
17802 cx.notify();
17803 self.gutter_highlights.remove(&TypeId::of::<T>())
17804 }
17805
17806 #[cfg(feature = "test-support")]
17807 pub fn all_text_background_highlights(
17808 &self,
17809 window: &mut Window,
17810 cx: &mut Context<Self>,
17811 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17812 let snapshot = self.snapshot(window, cx);
17813 let buffer = &snapshot.buffer_snapshot;
17814 let start = buffer.anchor_before(0);
17815 let end = buffer.anchor_after(buffer.len());
17816 let theme = cx.theme().colors();
17817 self.background_highlights_in_range(start..end, &snapshot, theme)
17818 }
17819
17820 #[cfg(feature = "test-support")]
17821 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17822 let snapshot = self.buffer().read(cx).snapshot(cx);
17823
17824 let highlights = self
17825 .background_highlights
17826 .get(&TypeId::of::<items::BufferSearchHighlights>());
17827
17828 if let Some((_color, ranges)) = highlights {
17829 ranges
17830 .iter()
17831 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17832 .collect_vec()
17833 } else {
17834 vec![]
17835 }
17836 }
17837
17838 fn document_highlights_for_position<'a>(
17839 &'a self,
17840 position: Anchor,
17841 buffer: &'a MultiBufferSnapshot,
17842 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17843 let read_highlights = self
17844 .background_highlights
17845 .get(&TypeId::of::<DocumentHighlightRead>())
17846 .map(|h| &h.1);
17847 let write_highlights = self
17848 .background_highlights
17849 .get(&TypeId::of::<DocumentHighlightWrite>())
17850 .map(|h| &h.1);
17851 let left_position = position.bias_left(buffer);
17852 let right_position = position.bias_right(buffer);
17853 read_highlights
17854 .into_iter()
17855 .chain(write_highlights)
17856 .flat_map(move |ranges| {
17857 let start_ix = match ranges.binary_search_by(|probe| {
17858 let cmp = probe.end.cmp(&left_position, buffer);
17859 if cmp.is_ge() {
17860 Ordering::Greater
17861 } else {
17862 Ordering::Less
17863 }
17864 }) {
17865 Ok(i) | Err(i) => i,
17866 };
17867
17868 ranges[start_ix..]
17869 .iter()
17870 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17871 })
17872 }
17873
17874 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17875 self.background_highlights
17876 .get(&TypeId::of::<T>())
17877 .map_or(false, |(_, highlights)| !highlights.is_empty())
17878 }
17879
17880 pub fn background_highlights_in_range(
17881 &self,
17882 search_range: Range<Anchor>,
17883 display_snapshot: &DisplaySnapshot,
17884 theme: &ThemeColors,
17885 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17886 let mut results = Vec::new();
17887 for (color_fetcher, ranges) in self.background_highlights.values() {
17888 let color = color_fetcher(theme);
17889 let start_ix = match ranges.binary_search_by(|probe| {
17890 let cmp = probe
17891 .end
17892 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17893 if cmp.is_gt() {
17894 Ordering::Greater
17895 } else {
17896 Ordering::Less
17897 }
17898 }) {
17899 Ok(i) | Err(i) => i,
17900 };
17901 for range in &ranges[start_ix..] {
17902 if range
17903 .start
17904 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17905 .is_ge()
17906 {
17907 break;
17908 }
17909
17910 let start = range.start.to_display_point(display_snapshot);
17911 let end = range.end.to_display_point(display_snapshot);
17912 results.push((start..end, color))
17913 }
17914 }
17915 results
17916 }
17917
17918 pub fn background_highlight_row_ranges<T: 'static>(
17919 &self,
17920 search_range: Range<Anchor>,
17921 display_snapshot: &DisplaySnapshot,
17922 count: usize,
17923 ) -> Vec<RangeInclusive<DisplayPoint>> {
17924 let mut results = Vec::new();
17925 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17926 return vec![];
17927 };
17928
17929 let start_ix = match ranges.binary_search_by(|probe| {
17930 let cmp = probe
17931 .end
17932 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17933 if cmp.is_gt() {
17934 Ordering::Greater
17935 } else {
17936 Ordering::Less
17937 }
17938 }) {
17939 Ok(i) | Err(i) => i,
17940 };
17941 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17942 if let (Some(start_display), Some(end_display)) = (start, end) {
17943 results.push(
17944 start_display.to_display_point(display_snapshot)
17945 ..=end_display.to_display_point(display_snapshot),
17946 );
17947 }
17948 };
17949 let mut start_row: Option<Point> = None;
17950 let mut end_row: Option<Point> = None;
17951 if ranges.len() > count {
17952 return Vec::new();
17953 }
17954 for range in &ranges[start_ix..] {
17955 if range
17956 .start
17957 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17958 .is_ge()
17959 {
17960 break;
17961 }
17962 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17963 if let Some(current_row) = &end_row {
17964 if end.row == current_row.row {
17965 continue;
17966 }
17967 }
17968 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17969 if start_row.is_none() {
17970 assert_eq!(end_row, None);
17971 start_row = Some(start);
17972 end_row = Some(end);
17973 continue;
17974 }
17975 if let Some(current_end) = end_row.as_mut() {
17976 if start.row > current_end.row + 1 {
17977 push_region(start_row, end_row);
17978 start_row = Some(start);
17979 end_row = Some(end);
17980 } else {
17981 // Merge two hunks.
17982 *current_end = end;
17983 }
17984 } else {
17985 unreachable!();
17986 }
17987 }
17988 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17989 push_region(start_row, end_row);
17990 results
17991 }
17992
17993 pub fn gutter_highlights_in_range(
17994 &self,
17995 search_range: Range<Anchor>,
17996 display_snapshot: &DisplaySnapshot,
17997 cx: &App,
17998 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17999 let mut results = Vec::new();
18000 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18001 let color = color_fetcher(cx);
18002 let start_ix = match ranges.binary_search_by(|probe| {
18003 let cmp = probe
18004 .end
18005 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18006 if cmp.is_gt() {
18007 Ordering::Greater
18008 } else {
18009 Ordering::Less
18010 }
18011 }) {
18012 Ok(i) | Err(i) => i,
18013 };
18014 for range in &ranges[start_ix..] {
18015 if range
18016 .start
18017 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18018 .is_ge()
18019 {
18020 break;
18021 }
18022
18023 let start = range.start.to_display_point(display_snapshot);
18024 let end = range.end.to_display_point(display_snapshot);
18025 results.push((start..end, color))
18026 }
18027 }
18028 results
18029 }
18030
18031 /// Get the text ranges corresponding to the redaction query
18032 pub fn redacted_ranges(
18033 &self,
18034 search_range: Range<Anchor>,
18035 display_snapshot: &DisplaySnapshot,
18036 cx: &App,
18037 ) -> Vec<Range<DisplayPoint>> {
18038 display_snapshot
18039 .buffer_snapshot
18040 .redacted_ranges(search_range, |file| {
18041 if let Some(file) = file {
18042 file.is_private()
18043 && EditorSettings::get(
18044 Some(SettingsLocation {
18045 worktree_id: file.worktree_id(cx),
18046 path: file.path().as_ref(),
18047 }),
18048 cx,
18049 )
18050 .redact_private_values
18051 } else {
18052 false
18053 }
18054 })
18055 .map(|range| {
18056 range.start.to_display_point(display_snapshot)
18057 ..range.end.to_display_point(display_snapshot)
18058 })
18059 .collect()
18060 }
18061
18062 pub fn highlight_text<T: 'static>(
18063 &mut self,
18064 ranges: Vec<Range<Anchor>>,
18065 style: HighlightStyle,
18066 cx: &mut Context<Self>,
18067 ) {
18068 self.display_map.update(cx, |map, _| {
18069 map.highlight_text(TypeId::of::<T>(), ranges, style)
18070 });
18071 cx.notify();
18072 }
18073
18074 pub(crate) fn highlight_inlays<T: 'static>(
18075 &mut self,
18076 highlights: Vec<InlayHighlight>,
18077 style: HighlightStyle,
18078 cx: &mut Context<Self>,
18079 ) {
18080 self.display_map.update(cx, |map, _| {
18081 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18082 });
18083 cx.notify();
18084 }
18085
18086 pub fn text_highlights<'a, T: 'static>(
18087 &'a self,
18088 cx: &'a App,
18089 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18090 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18091 }
18092
18093 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18094 let cleared = self
18095 .display_map
18096 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18097 if cleared {
18098 cx.notify();
18099 }
18100 }
18101
18102 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18103 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18104 && self.focus_handle.is_focused(window)
18105 }
18106
18107 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18108 self.show_cursor_when_unfocused = is_enabled;
18109 cx.notify();
18110 }
18111
18112 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18113 cx.notify();
18114 }
18115
18116 fn on_debug_session_event(
18117 &mut self,
18118 _session: Entity<Session>,
18119 event: &SessionEvent,
18120 cx: &mut Context<Self>,
18121 ) {
18122 match event {
18123 SessionEvent::InvalidateInlineValue => {
18124 self.refresh_inline_values(cx);
18125 }
18126 _ => {}
18127 }
18128 }
18129
18130 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18131 let Some(project) = self.project.clone() else {
18132 return;
18133 };
18134
18135 if !self.inline_value_cache.enabled {
18136 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18137 self.splice_inlays(&inlays, Vec::new(), cx);
18138 return;
18139 }
18140
18141 let current_execution_position = self
18142 .highlighted_rows
18143 .get(&TypeId::of::<ActiveDebugLine>())
18144 .and_then(|lines| lines.last().map(|line| line.range.start));
18145
18146 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18147 let inline_values = editor
18148 .update(cx, |editor, cx| {
18149 let Some(current_execution_position) = current_execution_position else {
18150 return Some(Task::ready(Ok(Vec::new())));
18151 };
18152
18153 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18154 let snapshot = buffer.snapshot(cx);
18155
18156 let excerpt = snapshot.excerpt_containing(
18157 current_execution_position..current_execution_position,
18158 )?;
18159
18160 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18161 })?;
18162
18163 let range =
18164 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18165
18166 project.inline_values(buffer, range, cx)
18167 })
18168 .ok()
18169 .flatten()?
18170 .await
18171 .context("refreshing debugger inlays")
18172 .log_err()?;
18173
18174 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18175
18176 for (buffer_id, inline_value) in inline_values
18177 .into_iter()
18178 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18179 {
18180 buffer_inline_values
18181 .entry(buffer_id)
18182 .or_default()
18183 .push(inline_value);
18184 }
18185
18186 editor
18187 .update(cx, |editor, cx| {
18188 let snapshot = editor.buffer.read(cx).snapshot(cx);
18189 let mut new_inlays = Vec::default();
18190
18191 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18192 let buffer_id = buffer_snapshot.remote_id();
18193 buffer_inline_values
18194 .get(&buffer_id)
18195 .into_iter()
18196 .flatten()
18197 .for_each(|hint| {
18198 let inlay = Inlay::debugger_hint(
18199 post_inc(&mut editor.next_inlay_id),
18200 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18201 hint.text(),
18202 );
18203
18204 new_inlays.push(inlay);
18205 });
18206 }
18207
18208 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18209 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18210
18211 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18212 })
18213 .ok()?;
18214 Some(())
18215 });
18216 }
18217
18218 fn on_buffer_event(
18219 &mut self,
18220 multibuffer: &Entity<MultiBuffer>,
18221 event: &multi_buffer::Event,
18222 window: &mut Window,
18223 cx: &mut Context<Self>,
18224 ) {
18225 match event {
18226 multi_buffer::Event::Edited {
18227 singleton_buffer_edited,
18228 edited_buffer: buffer_edited,
18229 } => {
18230 self.scrollbar_marker_state.dirty = true;
18231 self.active_indent_guides_state.dirty = true;
18232 self.refresh_active_diagnostics(cx);
18233 self.refresh_code_actions(window, cx);
18234 self.refresh_selected_text_highlights(true, window, cx);
18235 refresh_matching_bracket_highlights(self, window, cx);
18236 if self.has_active_inline_completion() {
18237 self.update_visible_inline_completion(window, cx);
18238 }
18239 if let Some(buffer) = buffer_edited {
18240 let buffer_id = buffer.read(cx).remote_id();
18241 if !self.registered_buffers.contains_key(&buffer_id) {
18242 if let Some(project) = self.project.as_ref() {
18243 project.update(cx, |project, cx| {
18244 self.registered_buffers.insert(
18245 buffer_id,
18246 project.register_buffer_with_language_servers(&buffer, cx),
18247 );
18248 })
18249 }
18250 }
18251 }
18252 cx.emit(EditorEvent::BufferEdited);
18253 cx.emit(SearchEvent::MatchesInvalidated);
18254 if *singleton_buffer_edited {
18255 if let Some(project) = &self.project {
18256 #[allow(clippy::mutable_key_type)]
18257 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18258 multibuffer
18259 .all_buffers()
18260 .into_iter()
18261 .filter_map(|buffer| {
18262 buffer.update(cx, |buffer, cx| {
18263 let language = buffer.language()?;
18264 let should_discard = project.update(cx, |project, cx| {
18265 project.is_local()
18266 && !project.has_language_servers_for(buffer, cx)
18267 });
18268 should_discard.not().then_some(language.clone())
18269 })
18270 })
18271 .collect::<HashSet<_>>()
18272 });
18273 if !languages_affected.is_empty() {
18274 self.refresh_inlay_hints(
18275 InlayHintRefreshReason::BufferEdited(languages_affected),
18276 cx,
18277 );
18278 }
18279 }
18280 }
18281
18282 let Some(project) = &self.project else { return };
18283 let (telemetry, is_via_ssh) = {
18284 let project = project.read(cx);
18285 let telemetry = project.client().telemetry().clone();
18286 let is_via_ssh = project.is_via_ssh();
18287 (telemetry, is_via_ssh)
18288 };
18289 refresh_linked_ranges(self, window, cx);
18290 telemetry.log_edit_event("editor", is_via_ssh);
18291 }
18292 multi_buffer::Event::ExcerptsAdded {
18293 buffer,
18294 predecessor,
18295 excerpts,
18296 } => {
18297 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18298 let buffer_id = buffer.read(cx).remote_id();
18299 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18300 if let Some(project) = &self.project {
18301 update_uncommitted_diff_for_buffer(
18302 cx.entity(),
18303 project,
18304 [buffer.clone()],
18305 self.buffer.clone(),
18306 cx,
18307 )
18308 .detach();
18309 }
18310 }
18311 cx.emit(EditorEvent::ExcerptsAdded {
18312 buffer: buffer.clone(),
18313 predecessor: *predecessor,
18314 excerpts: excerpts.clone(),
18315 });
18316 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18317 }
18318 multi_buffer::Event::ExcerptsRemoved {
18319 ids,
18320 removed_buffer_ids,
18321 } => {
18322 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18323 let buffer = self.buffer.read(cx);
18324 self.registered_buffers
18325 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18326 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18327 cx.emit(EditorEvent::ExcerptsRemoved {
18328 ids: ids.clone(),
18329 removed_buffer_ids: removed_buffer_ids.clone(),
18330 })
18331 }
18332 multi_buffer::Event::ExcerptsEdited {
18333 excerpt_ids,
18334 buffer_ids,
18335 } => {
18336 self.display_map.update(cx, |map, cx| {
18337 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18338 });
18339 cx.emit(EditorEvent::ExcerptsEdited {
18340 ids: excerpt_ids.clone(),
18341 })
18342 }
18343 multi_buffer::Event::ExcerptsExpanded { ids } => {
18344 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18345 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18346 }
18347 multi_buffer::Event::Reparsed(buffer_id) => {
18348 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18349 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18350
18351 cx.emit(EditorEvent::Reparsed(*buffer_id));
18352 }
18353 multi_buffer::Event::DiffHunksToggled => {
18354 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18355 }
18356 multi_buffer::Event::LanguageChanged(buffer_id) => {
18357 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18358 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18359 cx.emit(EditorEvent::Reparsed(*buffer_id));
18360 cx.notify();
18361 }
18362 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18363 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18364 multi_buffer::Event::FileHandleChanged
18365 | multi_buffer::Event::Reloaded
18366 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18367 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18368 multi_buffer::Event::DiagnosticsUpdated => {
18369 self.refresh_active_diagnostics(cx);
18370 self.refresh_inline_diagnostics(true, window, cx);
18371 self.scrollbar_marker_state.dirty = true;
18372 cx.notify();
18373 }
18374 _ => {}
18375 };
18376 }
18377
18378 pub fn start_temporary_diff_override(&mut self) {
18379 self.load_diff_task.take();
18380 self.temporary_diff_override = true;
18381 }
18382
18383 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18384 self.temporary_diff_override = false;
18385 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18386 self.buffer.update(cx, |buffer, cx| {
18387 buffer.set_all_diff_hunks_collapsed(cx);
18388 });
18389
18390 if let Some(project) = self.project.clone() {
18391 self.load_diff_task = Some(
18392 update_uncommitted_diff_for_buffer(
18393 cx.entity(),
18394 &project,
18395 self.buffer.read(cx).all_buffers(),
18396 self.buffer.clone(),
18397 cx,
18398 )
18399 .shared(),
18400 );
18401 }
18402 }
18403
18404 fn on_display_map_changed(
18405 &mut self,
18406 _: Entity<DisplayMap>,
18407 _: &mut Window,
18408 cx: &mut Context<Self>,
18409 ) {
18410 cx.notify();
18411 }
18412
18413 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18414 let new_severity = if self.diagnostics_enabled() {
18415 EditorSettings::get_global(cx)
18416 .diagnostics_max_severity
18417 .unwrap_or(DiagnosticSeverity::Hint)
18418 } else {
18419 DiagnosticSeverity::Off
18420 };
18421 self.set_max_diagnostics_severity(new_severity, cx);
18422 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18423 self.update_edit_prediction_settings(cx);
18424 self.refresh_inline_completion(true, false, window, cx);
18425 self.refresh_inlay_hints(
18426 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18427 self.selections.newest_anchor().head(),
18428 &self.buffer.read(cx).snapshot(cx),
18429 cx,
18430 )),
18431 cx,
18432 );
18433
18434 let old_cursor_shape = self.cursor_shape;
18435
18436 {
18437 let editor_settings = EditorSettings::get_global(cx);
18438 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18439 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18440 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18441 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18442 }
18443
18444 if old_cursor_shape != self.cursor_shape {
18445 cx.emit(EditorEvent::CursorShapeChanged);
18446 }
18447
18448 let project_settings = ProjectSettings::get_global(cx);
18449 self.serialize_dirty_buffers =
18450 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18451
18452 if self.mode.is_full() {
18453 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18454 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18455 if self.show_inline_diagnostics != show_inline_diagnostics {
18456 self.show_inline_diagnostics = show_inline_diagnostics;
18457 self.refresh_inline_diagnostics(false, window, cx);
18458 }
18459
18460 if self.git_blame_inline_enabled != inline_blame_enabled {
18461 self.toggle_git_blame_inline_internal(false, window, cx);
18462 }
18463
18464 let minimap_settings = EditorSettings::get_global(cx).minimap;
18465 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18466 self.set_minimap_visibility(
18467 self.minimap_visibility.toggle_visibility(),
18468 window,
18469 cx,
18470 );
18471 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18472 minimap_entity.update(cx, |minimap_editor, cx| {
18473 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18474 })
18475 }
18476 }
18477
18478 cx.notify();
18479 }
18480
18481 pub fn set_searchable(&mut self, searchable: bool) {
18482 self.searchable = searchable;
18483 }
18484
18485 pub fn searchable(&self) -> bool {
18486 self.searchable
18487 }
18488
18489 fn open_proposed_changes_editor(
18490 &mut self,
18491 _: &OpenProposedChangesEditor,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) {
18495 let Some(workspace) = self.workspace() else {
18496 cx.propagate();
18497 return;
18498 };
18499
18500 let selections = self.selections.all::<usize>(cx);
18501 let multi_buffer = self.buffer.read(cx);
18502 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18503 let mut new_selections_by_buffer = HashMap::default();
18504 for selection in selections {
18505 for (buffer, range, _) in
18506 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18507 {
18508 let mut range = range.to_point(buffer);
18509 range.start.column = 0;
18510 range.end.column = buffer.line_len(range.end.row);
18511 new_selections_by_buffer
18512 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18513 .or_insert(Vec::new())
18514 .push(range)
18515 }
18516 }
18517
18518 let proposed_changes_buffers = new_selections_by_buffer
18519 .into_iter()
18520 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18521 .collect::<Vec<_>>();
18522 let proposed_changes_editor = cx.new(|cx| {
18523 ProposedChangesEditor::new(
18524 "Proposed changes",
18525 proposed_changes_buffers,
18526 self.project.clone(),
18527 window,
18528 cx,
18529 )
18530 });
18531
18532 window.defer(cx, move |window, cx| {
18533 workspace.update(cx, |workspace, cx| {
18534 workspace.active_pane().update(cx, |pane, cx| {
18535 pane.add_item(
18536 Box::new(proposed_changes_editor),
18537 true,
18538 true,
18539 None,
18540 window,
18541 cx,
18542 );
18543 });
18544 });
18545 });
18546 }
18547
18548 pub fn open_excerpts_in_split(
18549 &mut self,
18550 _: &OpenExcerptsSplit,
18551 window: &mut Window,
18552 cx: &mut Context<Self>,
18553 ) {
18554 self.open_excerpts_common(None, true, window, cx)
18555 }
18556
18557 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18558 self.open_excerpts_common(None, false, window, cx)
18559 }
18560
18561 fn open_excerpts_common(
18562 &mut self,
18563 jump_data: Option<JumpData>,
18564 split: bool,
18565 window: &mut Window,
18566 cx: &mut Context<Self>,
18567 ) {
18568 let Some(workspace) = self.workspace() else {
18569 cx.propagate();
18570 return;
18571 };
18572
18573 if self.buffer.read(cx).is_singleton() {
18574 cx.propagate();
18575 return;
18576 }
18577
18578 let mut new_selections_by_buffer = HashMap::default();
18579 match &jump_data {
18580 Some(JumpData::MultiBufferPoint {
18581 excerpt_id,
18582 position,
18583 anchor,
18584 line_offset_from_top,
18585 }) => {
18586 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18587 if let Some(buffer) = multi_buffer_snapshot
18588 .buffer_id_for_excerpt(*excerpt_id)
18589 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18590 {
18591 let buffer_snapshot = buffer.read(cx).snapshot();
18592 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18593 language::ToPoint::to_point(anchor, &buffer_snapshot)
18594 } else {
18595 buffer_snapshot.clip_point(*position, Bias::Left)
18596 };
18597 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18598 new_selections_by_buffer.insert(
18599 buffer,
18600 (
18601 vec![jump_to_offset..jump_to_offset],
18602 Some(*line_offset_from_top),
18603 ),
18604 );
18605 }
18606 }
18607 Some(JumpData::MultiBufferRow {
18608 row,
18609 line_offset_from_top,
18610 }) => {
18611 let point = MultiBufferPoint::new(row.0, 0);
18612 if let Some((buffer, buffer_point, _)) =
18613 self.buffer.read(cx).point_to_buffer_point(point, cx)
18614 {
18615 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18616 new_selections_by_buffer
18617 .entry(buffer)
18618 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18619 .0
18620 .push(buffer_offset..buffer_offset)
18621 }
18622 }
18623 None => {
18624 let selections = self.selections.all::<usize>(cx);
18625 let multi_buffer = self.buffer.read(cx);
18626 for selection in selections {
18627 for (snapshot, range, _, anchor) in multi_buffer
18628 .snapshot(cx)
18629 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18630 {
18631 if let Some(anchor) = anchor {
18632 // selection is in a deleted hunk
18633 let Some(buffer_id) = anchor.buffer_id else {
18634 continue;
18635 };
18636 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18637 continue;
18638 };
18639 let offset = text::ToOffset::to_offset(
18640 &anchor.text_anchor,
18641 &buffer_handle.read(cx).snapshot(),
18642 );
18643 let range = offset..offset;
18644 new_selections_by_buffer
18645 .entry(buffer_handle)
18646 .or_insert((Vec::new(), None))
18647 .0
18648 .push(range)
18649 } else {
18650 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18651 else {
18652 continue;
18653 };
18654 new_selections_by_buffer
18655 .entry(buffer_handle)
18656 .or_insert((Vec::new(), None))
18657 .0
18658 .push(range)
18659 }
18660 }
18661 }
18662 }
18663 }
18664
18665 new_selections_by_buffer
18666 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18667
18668 if new_selections_by_buffer.is_empty() {
18669 return;
18670 }
18671
18672 // We defer the pane interaction because we ourselves are a workspace item
18673 // and activating a new item causes the pane to call a method on us reentrantly,
18674 // which panics if we're on the stack.
18675 window.defer(cx, move |window, cx| {
18676 workspace.update(cx, |workspace, cx| {
18677 let pane = if split {
18678 workspace.adjacent_pane(window, cx)
18679 } else {
18680 workspace.active_pane().clone()
18681 };
18682
18683 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18684 let editor = buffer
18685 .read(cx)
18686 .file()
18687 .is_none()
18688 .then(|| {
18689 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18690 // so `workspace.open_project_item` will never find them, always opening a new editor.
18691 // Instead, we try to activate the existing editor in the pane first.
18692 let (editor, pane_item_index) =
18693 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18694 let editor = item.downcast::<Editor>()?;
18695 let singleton_buffer =
18696 editor.read(cx).buffer().read(cx).as_singleton()?;
18697 if singleton_buffer == buffer {
18698 Some((editor, i))
18699 } else {
18700 None
18701 }
18702 })?;
18703 pane.update(cx, |pane, cx| {
18704 pane.activate_item(pane_item_index, true, true, window, cx)
18705 });
18706 Some(editor)
18707 })
18708 .flatten()
18709 .unwrap_or_else(|| {
18710 workspace.open_project_item::<Self>(
18711 pane.clone(),
18712 buffer,
18713 true,
18714 true,
18715 window,
18716 cx,
18717 )
18718 });
18719
18720 editor.update(cx, |editor, cx| {
18721 let autoscroll = match scroll_offset {
18722 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18723 None => Autoscroll::newest(),
18724 };
18725 let nav_history = editor.nav_history.take();
18726 editor.change_selections(Some(autoscroll), window, cx, |s| {
18727 s.select_ranges(ranges);
18728 });
18729 editor.nav_history = nav_history;
18730 });
18731 }
18732 })
18733 });
18734 }
18735
18736 // For now, don't allow opening excerpts in buffers that aren't backed by
18737 // regular project files.
18738 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18739 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18740 }
18741
18742 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18743 let snapshot = self.buffer.read(cx).read(cx);
18744 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18745 Some(
18746 ranges
18747 .iter()
18748 .map(move |range| {
18749 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18750 })
18751 .collect(),
18752 )
18753 }
18754
18755 fn selection_replacement_ranges(
18756 &self,
18757 range: Range<OffsetUtf16>,
18758 cx: &mut App,
18759 ) -> Vec<Range<OffsetUtf16>> {
18760 let selections = self.selections.all::<OffsetUtf16>(cx);
18761 let newest_selection = selections
18762 .iter()
18763 .max_by_key(|selection| selection.id)
18764 .unwrap();
18765 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18766 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18767 let snapshot = self.buffer.read(cx).read(cx);
18768 selections
18769 .into_iter()
18770 .map(|mut selection| {
18771 selection.start.0 =
18772 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18773 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18774 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18775 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18776 })
18777 .collect()
18778 }
18779
18780 fn report_editor_event(
18781 &self,
18782 event_type: &'static str,
18783 file_extension: Option<String>,
18784 cx: &App,
18785 ) {
18786 if cfg!(any(test, feature = "test-support")) {
18787 return;
18788 }
18789
18790 let Some(project) = &self.project else { return };
18791
18792 // If None, we are in a file without an extension
18793 let file = self
18794 .buffer
18795 .read(cx)
18796 .as_singleton()
18797 .and_then(|b| b.read(cx).file());
18798 let file_extension = file_extension.or(file
18799 .as_ref()
18800 .and_then(|file| Path::new(file.file_name(cx)).extension())
18801 .and_then(|e| e.to_str())
18802 .map(|a| a.to_string()));
18803
18804 let vim_mode = vim_enabled(cx);
18805
18806 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18807 let copilot_enabled = edit_predictions_provider
18808 == language::language_settings::EditPredictionProvider::Copilot;
18809 let copilot_enabled_for_language = self
18810 .buffer
18811 .read(cx)
18812 .language_settings(cx)
18813 .show_edit_predictions;
18814
18815 let project = project.read(cx);
18816 telemetry::event!(
18817 event_type,
18818 file_extension,
18819 vim_mode,
18820 copilot_enabled,
18821 copilot_enabled_for_language,
18822 edit_predictions_provider,
18823 is_via_ssh = project.is_via_ssh(),
18824 );
18825 }
18826
18827 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18828 /// with each line being an array of {text, highlight} objects.
18829 fn copy_highlight_json(
18830 &mut self,
18831 _: &CopyHighlightJson,
18832 window: &mut Window,
18833 cx: &mut Context<Self>,
18834 ) {
18835 #[derive(Serialize)]
18836 struct Chunk<'a> {
18837 text: String,
18838 highlight: Option<&'a str>,
18839 }
18840
18841 let snapshot = self.buffer.read(cx).snapshot(cx);
18842 let range = self
18843 .selected_text_range(false, window, cx)
18844 .and_then(|selection| {
18845 if selection.range.is_empty() {
18846 None
18847 } else {
18848 Some(selection.range)
18849 }
18850 })
18851 .unwrap_or_else(|| 0..snapshot.len());
18852
18853 let chunks = snapshot.chunks(range, true);
18854 let mut lines = Vec::new();
18855 let mut line: VecDeque<Chunk> = VecDeque::new();
18856
18857 let Some(style) = self.style.as_ref() else {
18858 return;
18859 };
18860
18861 for chunk in chunks {
18862 let highlight = chunk
18863 .syntax_highlight_id
18864 .and_then(|id| id.name(&style.syntax));
18865 let mut chunk_lines = chunk.text.split('\n').peekable();
18866 while let Some(text) = chunk_lines.next() {
18867 let mut merged_with_last_token = false;
18868 if let Some(last_token) = line.back_mut() {
18869 if last_token.highlight == highlight {
18870 last_token.text.push_str(text);
18871 merged_with_last_token = true;
18872 }
18873 }
18874
18875 if !merged_with_last_token {
18876 line.push_back(Chunk {
18877 text: text.into(),
18878 highlight,
18879 });
18880 }
18881
18882 if chunk_lines.peek().is_some() {
18883 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18884 line.pop_front();
18885 }
18886 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18887 line.pop_back();
18888 }
18889
18890 lines.push(mem::take(&mut line));
18891 }
18892 }
18893 }
18894
18895 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18896 return;
18897 };
18898 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18899 }
18900
18901 pub fn open_context_menu(
18902 &mut self,
18903 _: &OpenContextMenu,
18904 window: &mut Window,
18905 cx: &mut Context<Self>,
18906 ) {
18907 self.request_autoscroll(Autoscroll::newest(), cx);
18908 let position = self.selections.newest_display(cx).start;
18909 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18910 }
18911
18912 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18913 &self.inlay_hint_cache
18914 }
18915
18916 pub fn replay_insert_event(
18917 &mut self,
18918 text: &str,
18919 relative_utf16_range: Option<Range<isize>>,
18920 window: &mut Window,
18921 cx: &mut Context<Self>,
18922 ) {
18923 if !self.input_enabled {
18924 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18925 return;
18926 }
18927 if let Some(relative_utf16_range) = relative_utf16_range {
18928 let selections = self.selections.all::<OffsetUtf16>(cx);
18929 self.change_selections(None, window, cx, |s| {
18930 let new_ranges = selections.into_iter().map(|range| {
18931 let start = OffsetUtf16(
18932 range
18933 .head()
18934 .0
18935 .saturating_add_signed(relative_utf16_range.start),
18936 );
18937 let end = OffsetUtf16(
18938 range
18939 .head()
18940 .0
18941 .saturating_add_signed(relative_utf16_range.end),
18942 );
18943 start..end
18944 });
18945 s.select_ranges(new_ranges);
18946 });
18947 }
18948
18949 self.handle_input(text, window, cx);
18950 }
18951
18952 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18953 let Some(provider) = self.semantics_provider.as_ref() else {
18954 return false;
18955 };
18956
18957 let mut supports = false;
18958 self.buffer().update(cx, |this, cx| {
18959 this.for_each_buffer(|buffer| {
18960 supports |= provider.supports_inlay_hints(buffer, cx);
18961 });
18962 });
18963
18964 supports
18965 }
18966
18967 pub fn is_focused(&self, window: &Window) -> bool {
18968 self.focus_handle.is_focused(window)
18969 }
18970
18971 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18972 cx.emit(EditorEvent::Focused);
18973
18974 if let Some(descendant) = self
18975 .last_focused_descendant
18976 .take()
18977 .and_then(|descendant| descendant.upgrade())
18978 {
18979 window.focus(&descendant);
18980 } else {
18981 if let Some(blame) = self.blame.as_ref() {
18982 blame.update(cx, GitBlame::focus)
18983 }
18984
18985 self.blink_manager.update(cx, BlinkManager::enable);
18986 self.show_cursor_names(window, cx);
18987 self.buffer.update(cx, |buffer, cx| {
18988 buffer.finalize_last_transaction(cx);
18989 if self.leader_id.is_none() {
18990 buffer.set_active_selections(
18991 &self.selections.disjoint_anchors(),
18992 self.selections.line_mode,
18993 self.cursor_shape,
18994 cx,
18995 );
18996 }
18997 });
18998 }
18999 }
19000
19001 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19002 cx.emit(EditorEvent::FocusedIn)
19003 }
19004
19005 fn handle_focus_out(
19006 &mut self,
19007 event: FocusOutEvent,
19008 _window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 if event.blurred != self.focus_handle {
19012 self.last_focused_descendant = Some(event.blurred);
19013 }
19014 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19015 }
19016
19017 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19018 self.blink_manager.update(cx, BlinkManager::disable);
19019 self.buffer
19020 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19021
19022 if let Some(blame) = self.blame.as_ref() {
19023 blame.update(cx, GitBlame::blur)
19024 }
19025 if !self.hover_state.focused(window, cx) {
19026 hide_hover(self, cx);
19027 }
19028 if !self
19029 .context_menu
19030 .borrow()
19031 .as_ref()
19032 .is_some_and(|context_menu| context_menu.focused(window, cx))
19033 {
19034 self.hide_context_menu(window, cx);
19035 }
19036 self.discard_inline_completion(false, cx);
19037 cx.emit(EditorEvent::Blurred);
19038 cx.notify();
19039 }
19040
19041 pub fn register_action<A: Action>(
19042 &mut self,
19043 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19044 ) -> Subscription {
19045 let id = self.next_editor_action_id.post_inc();
19046 let listener = Arc::new(listener);
19047 self.editor_actions.borrow_mut().insert(
19048 id,
19049 Box::new(move |window, _| {
19050 let listener = listener.clone();
19051 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19052 let action = action.downcast_ref().unwrap();
19053 if phase == DispatchPhase::Bubble {
19054 listener(action, window, cx)
19055 }
19056 })
19057 }),
19058 );
19059
19060 let editor_actions = self.editor_actions.clone();
19061 Subscription::new(move || {
19062 editor_actions.borrow_mut().remove(&id);
19063 })
19064 }
19065
19066 pub fn file_header_size(&self) -> u32 {
19067 FILE_HEADER_HEIGHT
19068 }
19069
19070 pub fn restore(
19071 &mut self,
19072 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19073 window: &mut Window,
19074 cx: &mut Context<Self>,
19075 ) {
19076 let workspace = self.workspace();
19077 let project = self.project.as_ref();
19078 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19079 let mut tasks = Vec::new();
19080 for (buffer_id, changes) in revert_changes {
19081 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19082 buffer.update(cx, |buffer, cx| {
19083 buffer.edit(
19084 changes
19085 .into_iter()
19086 .map(|(range, text)| (range, text.to_string())),
19087 None,
19088 cx,
19089 );
19090 });
19091
19092 if let Some(project) =
19093 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19094 {
19095 project.update(cx, |project, cx| {
19096 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19097 })
19098 }
19099 }
19100 }
19101 tasks
19102 });
19103 cx.spawn_in(window, async move |_, cx| {
19104 for (buffer, task) in save_tasks {
19105 let result = task.await;
19106 if result.is_err() {
19107 let Some(path) = buffer
19108 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19109 .ok()
19110 else {
19111 continue;
19112 };
19113 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19114 let Some(task) = cx
19115 .update_window_entity(&workspace, |workspace, window, cx| {
19116 workspace
19117 .open_path_preview(path, None, false, false, false, window, cx)
19118 })
19119 .ok()
19120 else {
19121 continue;
19122 };
19123 task.await.log_err();
19124 }
19125 }
19126 }
19127 })
19128 .detach();
19129 self.change_selections(None, window, cx, |selections| selections.refresh());
19130 }
19131
19132 pub fn to_pixel_point(
19133 &self,
19134 source: multi_buffer::Anchor,
19135 editor_snapshot: &EditorSnapshot,
19136 window: &mut Window,
19137 ) -> Option<gpui::Point<Pixels>> {
19138 let source_point = source.to_display_point(editor_snapshot);
19139 self.display_to_pixel_point(source_point, editor_snapshot, window)
19140 }
19141
19142 pub fn display_to_pixel_point(
19143 &self,
19144 source: DisplayPoint,
19145 editor_snapshot: &EditorSnapshot,
19146 window: &mut Window,
19147 ) -> Option<gpui::Point<Pixels>> {
19148 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19149 let text_layout_details = self.text_layout_details(window);
19150 let scroll_top = text_layout_details
19151 .scroll_anchor
19152 .scroll_position(editor_snapshot)
19153 .y;
19154
19155 if source.row().as_f32() < scroll_top.floor() {
19156 return None;
19157 }
19158 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19159 let source_y = line_height * (source.row().as_f32() - scroll_top);
19160 Some(gpui::Point::new(source_x, source_y))
19161 }
19162
19163 pub fn has_visible_completions_menu(&self) -> bool {
19164 !self.edit_prediction_preview_is_active()
19165 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19166 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19167 })
19168 }
19169
19170 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19171 if self.mode.is_minimap() {
19172 return;
19173 }
19174 self.addons
19175 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19176 }
19177
19178 pub fn unregister_addon<T: Addon>(&mut self) {
19179 self.addons.remove(&std::any::TypeId::of::<T>());
19180 }
19181
19182 pub fn addon<T: Addon>(&self) -> Option<&T> {
19183 let type_id = std::any::TypeId::of::<T>();
19184 self.addons
19185 .get(&type_id)
19186 .and_then(|item| item.to_any().downcast_ref::<T>())
19187 }
19188
19189 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19190 let type_id = std::any::TypeId::of::<T>();
19191 self.addons
19192 .get_mut(&type_id)
19193 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19194 }
19195
19196 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19197 let text_layout_details = self.text_layout_details(window);
19198 let style = &text_layout_details.editor_style;
19199 let font_id = window.text_system().resolve_font(&style.text.font());
19200 let font_size = style.text.font_size.to_pixels(window.rem_size());
19201 let line_height = style.text.line_height_in_pixels(window.rem_size());
19202 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19203
19204 gpui::Size::new(em_width, line_height)
19205 }
19206
19207 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19208 self.load_diff_task.clone()
19209 }
19210
19211 fn read_metadata_from_db(
19212 &mut self,
19213 item_id: u64,
19214 workspace_id: WorkspaceId,
19215 window: &mut Window,
19216 cx: &mut Context<Editor>,
19217 ) {
19218 if self.is_singleton(cx)
19219 && !self.mode.is_minimap()
19220 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19221 {
19222 let buffer_snapshot = OnceCell::new();
19223
19224 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19225 if !folds.is_empty() {
19226 let snapshot =
19227 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19228 self.fold_ranges(
19229 folds
19230 .into_iter()
19231 .map(|(start, end)| {
19232 snapshot.clip_offset(start, Bias::Left)
19233 ..snapshot.clip_offset(end, Bias::Right)
19234 })
19235 .collect(),
19236 false,
19237 window,
19238 cx,
19239 );
19240 }
19241 }
19242
19243 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19244 if !selections.is_empty() {
19245 let snapshot =
19246 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19247 self.change_selections(None, window, cx, |s| {
19248 s.select_ranges(selections.into_iter().map(|(start, end)| {
19249 snapshot.clip_offset(start, Bias::Left)
19250 ..snapshot.clip_offset(end, Bias::Right)
19251 }));
19252 });
19253 }
19254 };
19255 }
19256
19257 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19258 }
19259}
19260
19261fn vim_enabled(cx: &App) -> bool {
19262 cx.global::<SettingsStore>()
19263 .raw_user_settings()
19264 .get("vim_mode")
19265 == Some(&serde_json::Value::Bool(true))
19266}
19267
19268// Consider user intent and default settings
19269fn choose_completion_range(
19270 completion: &Completion,
19271 intent: CompletionIntent,
19272 buffer: &Entity<Buffer>,
19273 cx: &mut Context<Editor>,
19274) -> Range<usize> {
19275 fn should_replace(
19276 completion: &Completion,
19277 insert_range: &Range<text::Anchor>,
19278 intent: CompletionIntent,
19279 completion_mode_setting: LspInsertMode,
19280 buffer: &Buffer,
19281 ) -> bool {
19282 // specific actions take precedence over settings
19283 match intent {
19284 CompletionIntent::CompleteWithInsert => return false,
19285 CompletionIntent::CompleteWithReplace => return true,
19286 CompletionIntent::Complete | CompletionIntent::Compose => {}
19287 }
19288
19289 match completion_mode_setting {
19290 LspInsertMode::Insert => false,
19291 LspInsertMode::Replace => true,
19292 LspInsertMode::ReplaceSubsequence => {
19293 let mut text_to_replace = buffer.chars_for_range(
19294 buffer.anchor_before(completion.replace_range.start)
19295 ..buffer.anchor_after(completion.replace_range.end),
19296 );
19297 let mut completion_text = completion.new_text.chars();
19298
19299 // is `text_to_replace` a subsequence of `completion_text`
19300 text_to_replace
19301 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19302 }
19303 LspInsertMode::ReplaceSuffix => {
19304 let range_after_cursor = insert_range.end..completion.replace_range.end;
19305
19306 let text_after_cursor = buffer
19307 .text_for_range(
19308 buffer.anchor_before(range_after_cursor.start)
19309 ..buffer.anchor_after(range_after_cursor.end),
19310 )
19311 .collect::<String>();
19312 completion.new_text.ends_with(&text_after_cursor)
19313 }
19314 }
19315 }
19316
19317 let buffer = buffer.read(cx);
19318
19319 if let CompletionSource::Lsp {
19320 insert_range: Some(insert_range),
19321 ..
19322 } = &completion.source
19323 {
19324 let completion_mode_setting =
19325 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19326 .completions
19327 .lsp_insert_mode;
19328
19329 if !should_replace(
19330 completion,
19331 &insert_range,
19332 intent,
19333 completion_mode_setting,
19334 buffer,
19335 ) {
19336 return insert_range.to_offset(buffer);
19337 }
19338 }
19339
19340 completion.replace_range.to_offset(buffer)
19341}
19342
19343fn insert_extra_newline_brackets(
19344 buffer: &MultiBufferSnapshot,
19345 range: Range<usize>,
19346 language: &language::LanguageScope,
19347) -> bool {
19348 let leading_whitespace_len = buffer
19349 .reversed_chars_at(range.start)
19350 .take_while(|c| c.is_whitespace() && *c != '\n')
19351 .map(|c| c.len_utf8())
19352 .sum::<usize>();
19353 let trailing_whitespace_len = buffer
19354 .chars_at(range.end)
19355 .take_while(|c| c.is_whitespace() && *c != '\n')
19356 .map(|c| c.len_utf8())
19357 .sum::<usize>();
19358 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19359
19360 language.brackets().any(|(pair, enabled)| {
19361 let pair_start = pair.start.trim_end();
19362 let pair_end = pair.end.trim_start();
19363
19364 enabled
19365 && pair.newline
19366 && buffer.contains_str_at(range.end, pair_end)
19367 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19368 })
19369}
19370
19371fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19372 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19373 [(buffer, range, _)] => (*buffer, range.clone()),
19374 _ => return false,
19375 };
19376 let pair = {
19377 let mut result: Option<BracketMatch> = None;
19378
19379 for pair in buffer
19380 .all_bracket_ranges(range.clone())
19381 .filter(move |pair| {
19382 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19383 })
19384 {
19385 let len = pair.close_range.end - pair.open_range.start;
19386
19387 if let Some(existing) = &result {
19388 let existing_len = existing.close_range.end - existing.open_range.start;
19389 if len > existing_len {
19390 continue;
19391 }
19392 }
19393
19394 result = Some(pair);
19395 }
19396
19397 result
19398 };
19399 let Some(pair) = pair else {
19400 return false;
19401 };
19402 pair.newline_only
19403 && buffer
19404 .chars_for_range(pair.open_range.end..range.start)
19405 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19406 .all(|c| c.is_whitespace() && c != '\n')
19407}
19408
19409fn update_uncommitted_diff_for_buffer(
19410 editor: Entity<Editor>,
19411 project: &Entity<Project>,
19412 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19413 buffer: Entity<MultiBuffer>,
19414 cx: &mut App,
19415) -> Task<()> {
19416 let mut tasks = Vec::new();
19417 project.update(cx, |project, cx| {
19418 for buffer in buffers {
19419 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19420 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19421 }
19422 }
19423 });
19424 cx.spawn(async move |cx| {
19425 let diffs = future::join_all(tasks).await;
19426 if editor
19427 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19428 .unwrap_or(false)
19429 {
19430 return;
19431 }
19432
19433 buffer
19434 .update(cx, |buffer, cx| {
19435 for diff in diffs.into_iter().flatten() {
19436 buffer.add_diff(diff, cx);
19437 }
19438 })
19439 .ok();
19440 })
19441}
19442
19443pub trait CollaborationHub {
19444 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19445 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19446 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19447}
19448
19449impl CollaborationHub for Entity<Project> {
19450 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19451 self.read(cx).collaborators()
19452 }
19453
19454 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19455 self.read(cx).user_store().read(cx).participant_indices()
19456 }
19457
19458 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19459 let this = self.read(cx);
19460 let user_ids = this.collaborators().values().map(|c| c.user_id);
19461 this.user_store().read_with(cx, |user_store, cx| {
19462 user_store.participant_names(user_ids, cx)
19463 })
19464 }
19465}
19466
19467pub trait SemanticsProvider {
19468 fn hover(
19469 &self,
19470 buffer: &Entity<Buffer>,
19471 position: text::Anchor,
19472 cx: &mut App,
19473 ) -> Option<Task<Vec<project::Hover>>>;
19474
19475 fn inline_values(
19476 &self,
19477 buffer_handle: Entity<Buffer>,
19478 range: Range<text::Anchor>,
19479 cx: &mut App,
19480 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19481
19482 fn inlay_hints(
19483 &self,
19484 buffer_handle: Entity<Buffer>,
19485 range: Range<text::Anchor>,
19486 cx: &mut App,
19487 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19488
19489 fn resolve_inlay_hint(
19490 &self,
19491 hint: InlayHint,
19492 buffer_handle: Entity<Buffer>,
19493 server_id: LanguageServerId,
19494 cx: &mut App,
19495 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19496
19497 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19498
19499 fn document_highlights(
19500 &self,
19501 buffer: &Entity<Buffer>,
19502 position: text::Anchor,
19503 cx: &mut App,
19504 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19505
19506 fn definitions(
19507 &self,
19508 buffer: &Entity<Buffer>,
19509 position: text::Anchor,
19510 kind: GotoDefinitionKind,
19511 cx: &mut App,
19512 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19513
19514 fn range_for_rename(
19515 &self,
19516 buffer: &Entity<Buffer>,
19517 position: text::Anchor,
19518 cx: &mut App,
19519 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19520
19521 fn perform_rename(
19522 &self,
19523 buffer: &Entity<Buffer>,
19524 position: text::Anchor,
19525 new_name: String,
19526 cx: &mut App,
19527 ) -> Option<Task<Result<ProjectTransaction>>>;
19528}
19529
19530pub trait CompletionProvider {
19531 fn completions(
19532 &self,
19533 excerpt_id: ExcerptId,
19534 buffer: &Entity<Buffer>,
19535 buffer_position: text::Anchor,
19536 trigger: CompletionContext,
19537 window: &mut Window,
19538 cx: &mut Context<Editor>,
19539 ) -> Task<Result<Option<Vec<Completion>>>>;
19540
19541 fn resolve_completions(
19542 &self,
19543 buffer: Entity<Buffer>,
19544 completion_indices: Vec<usize>,
19545 completions: Rc<RefCell<Box<[Completion]>>>,
19546 cx: &mut Context<Editor>,
19547 ) -> Task<Result<bool>>;
19548
19549 fn apply_additional_edits_for_completion(
19550 &self,
19551 _buffer: Entity<Buffer>,
19552 _completions: Rc<RefCell<Box<[Completion]>>>,
19553 _completion_index: usize,
19554 _push_to_history: bool,
19555 _cx: &mut Context<Editor>,
19556 ) -> Task<Result<Option<language::Transaction>>> {
19557 Task::ready(Ok(None))
19558 }
19559
19560 fn is_completion_trigger(
19561 &self,
19562 buffer: &Entity<Buffer>,
19563 position: language::Anchor,
19564 text: &str,
19565 trigger_in_words: bool,
19566 cx: &mut Context<Editor>,
19567 ) -> bool;
19568
19569 fn sort_completions(&self) -> bool {
19570 true
19571 }
19572
19573 fn filter_completions(&self) -> bool {
19574 true
19575 }
19576}
19577
19578pub trait CodeActionProvider {
19579 fn id(&self) -> Arc<str>;
19580
19581 fn code_actions(
19582 &self,
19583 buffer: &Entity<Buffer>,
19584 range: Range<text::Anchor>,
19585 window: &mut Window,
19586 cx: &mut App,
19587 ) -> Task<Result<Vec<CodeAction>>>;
19588
19589 fn apply_code_action(
19590 &self,
19591 buffer_handle: Entity<Buffer>,
19592 action: CodeAction,
19593 excerpt_id: ExcerptId,
19594 push_to_history: bool,
19595 window: &mut Window,
19596 cx: &mut App,
19597 ) -> Task<Result<ProjectTransaction>>;
19598}
19599
19600impl CodeActionProvider for Entity<Project> {
19601 fn id(&self) -> Arc<str> {
19602 "project".into()
19603 }
19604
19605 fn code_actions(
19606 &self,
19607 buffer: &Entity<Buffer>,
19608 range: Range<text::Anchor>,
19609 _window: &mut Window,
19610 cx: &mut App,
19611 ) -> Task<Result<Vec<CodeAction>>> {
19612 self.update(cx, |project, cx| {
19613 let code_lens = project.code_lens(buffer, range.clone(), cx);
19614 let code_actions = project.code_actions(buffer, range, None, cx);
19615 cx.background_spawn(async move {
19616 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19617 Ok(code_lens
19618 .context("code lens fetch")?
19619 .into_iter()
19620 .chain(code_actions.context("code action fetch")?)
19621 .collect())
19622 })
19623 })
19624 }
19625
19626 fn apply_code_action(
19627 &self,
19628 buffer_handle: Entity<Buffer>,
19629 action: CodeAction,
19630 _excerpt_id: ExcerptId,
19631 push_to_history: bool,
19632 _window: &mut Window,
19633 cx: &mut App,
19634 ) -> Task<Result<ProjectTransaction>> {
19635 self.update(cx, |project, cx| {
19636 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19637 })
19638 }
19639}
19640
19641fn snippet_completions(
19642 project: &Project,
19643 buffer: &Entity<Buffer>,
19644 buffer_position: text::Anchor,
19645 cx: &mut App,
19646) -> Task<Result<Vec<Completion>>> {
19647 let languages = buffer.read(cx).languages_at(buffer_position);
19648 let snippet_store = project.snippets().read(cx);
19649
19650 let scopes: Vec<_> = languages
19651 .iter()
19652 .filter_map(|language| {
19653 let language_name = language.lsp_id();
19654 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19655
19656 if snippets.is_empty() {
19657 None
19658 } else {
19659 Some((language.default_scope(), snippets))
19660 }
19661 })
19662 .collect();
19663
19664 if scopes.is_empty() {
19665 return Task::ready(Ok(vec![]));
19666 }
19667
19668 let snapshot = buffer.read(cx).text_snapshot();
19669 let chars: String = snapshot
19670 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19671 .collect();
19672 let executor = cx.background_executor().clone();
19673
19674 cx.background_spawn(async move {
19675 let mut all_results: Vec<Completion> = Vec::new();
19676 for (scope, snippets) in scopes.into_iter() {
19677 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19678 let mut last_word = chars
19679 .chars()
19680 .take_while(|c| classifier.is_word(*c))
19681 .collect::<String>();
19682 last_word = last_word.chars().rev().collect();
19683
19684 if last_word.is_empty() {
19685 return Ok(vec![]);
19686 }
19687
19688 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19689 let to_lsp = |point: &text::Anchor| {
19690 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19691 point_to_lsp(end)
19692 };
19693 let lsp_end = to_lsp(&buffer_position);
19694
19695 let candidates = snippets
19696 .iter()
19697 .enumerate()
19698 .flat_map(|(ix, snippet)| {
19699 snippet
19700 .prefix
19701 .iter()
19702 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19703 })
19704 .collect::<Vec<StringMatchCandidate>>();
19705
19706 let mut matches = fuzzy::match_strings(
19707 &candidates,
19708 &last_word,
19709 last_word.chars().any(|c| c.is_uppercase()),
19710 100,
19711 &Default::default(),
19712 executor.clone(),
19713 )
19714 .await;
19715
19716 // Remove all candidates where the query's start does not match the start of any word in the candidate
19717 if let Some(query_start) = last_word.chars().next() {
19718 matches.retain(|string_match| {
19719 split_words(&string_match.string).any(|word| {
19720 // Check that the first codepoint of the word as lowercase matches the first
19721 // codepoint of the query as lowercase
19722 word.chars()
19723 .flat_map(|codepoint| codepoint.to_lowercase())
19724 .zip(query_start.to_lowercase())
19725 .all(|(word_cp, query_cp)| word_cp == query_cp)
19726 })
19727 });
19728 }
19729
19730 let matched_strings = matches
19731 .into_iter()
19732 .map(|m| m.string)
19733 .collect::<HashSet<_>>();
19734
19735 let mut result: Vec<Completion> = snippets
19736 .iter()
19737 .filter_map(|snippet| {
19738 let matching_prefix = snippet
19739 .prefix
19740 .iter()
19741 .find(|prefix| matched_strings.contains(*prefix))?;
19742 let start = as_offset - last_word.len();
19743 let start = snapshot.anchor_before(start);
19744 let range = start..buffer_position;
19745 let lsp_start = to_lsp(&start);
19746 let lsp_range = lsp::Range {
19747 start: lsp_start,
19748 end: lsp_end,
19749 };
19750 Some(Completion {
19751 replace_range: range,
19752 new_text: snippet.body.clone(),
19753 source: CompletionSource::Lsp {
19754 insert_range: None,
19755 server_id: LanguageServerId(usize::MAX),
19756 resolved: true,
19757 lsp_completion: Box::new(lsp::CompletionItem {
19758 label: snippet.prefix.first().unwrap().clone(),
19759 kind: Some(CompletionItemKind::SNIPPET),
19760 label_details: snippet.description.as_ref().map(|description| {
19761 lsp::CompletionItemLabelDetails {
19762 detail: Some(description.clone()),
19763 description: None,
19764 }
19765 }),
19766 insert_text_format: Some(InsertTextFormat::SNIPPET),
19767 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19768 lsp::InsertReplaceEdit {
19769 new_text: snippet.body.clone(),
19770 insert: lsp_range,
19771 replace: lsp_range,
19772 },
19773 )),
19774 filter_text: Some(snippet.body.clone()),
19775 sort_text: Some(char::MAX.to_string()),
19776 ..lsp::CompletionItem::default()
19777 }),
19778 lsp_defaults: None,
19779 },
19780 label: CodeLabel {
19781 text: matching_prefix.clone(),
19782 runs: Vec::new(),
19783 filter_range: 0..matching_prefix.len(),
19784 },
19785 icon_path: None,
19786 documentation: Some(
19787 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19788 single_line: snippet.name.clone().into(),
19789 plain_text: snippet
19790 .description
19791 .clone()
19792 .map(|description| description.into()),
19793 },
19794 ),
19795 insert_text_mode: None,
19796 confirm: None,
19797 })
19798 })
19799 .collect();
19800
19801 all_results.append(&mut result);
19802 }
19803
19804 Ok(all_results)
19805 })
19806}
19807
19808impl CompletionProvider for Entity<Project> {
19809 fn completions(
19810 &self,
19811 _excerpt_id: ExcerptId,
19812 buffer: &Entity<Buffer>,
19813 buffer_position: text::Anchor,
19814 options: CompletionContext,
19815 _window: &mut Window,
19816 cx: &mut Context<Editor>,
19817 ) -> Task<Result<Option<Vec<Completion>>>> {
19818 self.update(cx, |project, cx| {
19819 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19820 let project_completions = project.completions(buffer, buffer_position, options, cx);
19821 cx.background_spawn(async move {
19822 let snippets_completions = snippets.await?;
19823 match project_completions.await? {
19824 Some(mut completions) => {
19825 completions.extend(snippets_completions);
19826 Ok(Some(completions))
19827 }
19828 None => {
19829 if snippets_completions.is_empty() {
19830 Ok(None)
19831 } else {
19832 Ok(Some(snippets_completions))
19833 }
19834 }
19835 }
19836 })
19837 })
19838 }
19839
19840 fn resolve_completions(
19841 &self,
19842 buffer: Entity<Buffer>,
19843 completion_indices: Vec<usize>,
19844 completions: Rc<RefCell<Box<[Completion]>>>,
19845 cx: &mut Context<Editor>,
19846 ) -> Task<Result<bool>> {
19847 self.update(cx, |project, cx| {
19848 project.lsp_store().update(cx, |lsp_store, cx| {
19849 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19850 })
19851 })
19852 }
19853
19854 fn apply_additional_edits_for_completion(
19855 &self,
19856 buffer: Entity<Buffer>,
19857 completions: Rc<RefCell<Box<[Completion]>>>,
19858 completion_index: usize,
19859 push_to_history: bool,
19860 cx: &mut Context<Editor>,
19861 ) -> Task<Result<Option<language::Transaction>>> {
19862 self.update(cx, |project, cx| {
19863 project.lsp_store().update(cx, |lsp_store, cx| {
19864 lsp_store.apply_additional_edits_for_completion(
19865 buffer,
19866 completions,
19867 completion_index,
19868 push_to_history,
19869 cx,
19870 )
19871 })
19872 })
19873 }
19874
19875 fn is_completion_trigger(
19876 &self,
19877 buffer: &Entity<Buffer>,
19878 position: language::Anchor,
19879 text: &str,
19880 trigger_in_words: bool,
19881 cx: &mut Context<Editor>,
19882 ) -> bool {
19883 let mut chars = text.chars();
19884 let char = if let Some(char) = chars.next() {
19885 char
19886 } else {
19887 return false;
19888 };
19889 if chars.next().is_some() {
19890 return false;
19891 }
19892
19893 let buffer = buffer.read(cx);
19894 let snapshot = buffer.snapshot();
19895 if !snapshot.settings_at(position, cx).show_completions_on_input {
19896 return false;
19897 }
19898 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19899 if trigger_in_words && classifier.is_word(char) {
19900 return true;
19901 }
19902
19903 buffer.completion_triggers().contains(text)
19904 }
19905}
19906
19907impl SemanticsProvider for Entity<Project> {
19908 fn hover(
19909 &self,
19910 buffer: &Entity<Buffer>,
19911 position: text::Anchor,
19912 cx: &mut App,
19913 ) -> Option<Task<Vec<project::Hover>>> {
19914 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19915 }
19916
19917 fn document_highlights(
19918 &self,
19919 buffer: &Entity<Buffer>,
19920 position: text::Anchor,
19921 cx: &mut App,
19922 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19923 Some(self.update(cx, |project, cx| {
19924 project.document_highlights(buffer, position, cx)
19925 }))
19926 }
19927
19928 fn definitions(
19929 &self,
19930 buffer: &Entity<Buffer>,
19931 position: text::Anchor,
19932 kind: GotoDefinitionKind,
19933 cx: &mut App,
19934 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19935 Some(self.update(cx, |project, cx| match kind {
19936 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19937 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19938 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19939 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19940 }))
19941 }
19942
19943 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19944 // TODO: make this work for remote projects
19945 self.update(cx, |project, cx| {
19946 if project
19947 .active_debug_session(cx)
19948 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19949 {
19950 return true;
19951 }
19952
19953 buffer.update(cx, |buffer, cx| {
19954 project.any_language_server_supports_inlay_hints(buffer, cx)
19955 })
19956 })
19957 }
19958
19959 fn inline_values(
19960 &self,
19961 buffer_handle: Entity<Buffer>,
19962
19963 range: Range<text::Anchor>,
19964 cx: &mut App,
19965 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19966 self.update(cx, |project, cx| {
19967 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19968
19969 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19970 })
19971 }
19972
19973 fn inlay_hints(
19974 &self,
19975 buffer_handle: Entity<Buffer>,
19976 range: Range<text::Anchor>,
19977 cx: &mut App,
19978 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19979 Some(self.update(cx, |project, cx| {
19980 project.inlay_hints(buffer_handle, range, cx)
19981 }))
19982 }
19983
19984 fn resolve_inlay_hint(
19985 &self,
19986 hint: InlayHint,
19987 buffer_handle: Entity<Buffer>,
19988 server_id: LanguageServerId,
19989 cx: &mut App,
19990 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19991 Some(self.update(cx, |project, cx| {
19992 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19993 }))
19994 }
19995
19996 fn range_for_rename(
19997 &self,
19998 buffer: &Entity<Buffer>,
19999 position: text::Anchor,
20000 cx: &mut App,
20001 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20002 Some(self.update(cx, |project, cx| {
20003 let buffer = buffer.clone();
20004 let task = project.prepare_rename(buffer.clone(), position, cx);
20005 cx.spawn(async move |_, cx| {
20006 Ok(match task.await? {
20007 PrepareRenameResponse::Success(range) => Some(range),
20008 PrepareRenameResponse::InvalidPosition => None,
20009 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20010 // Fallback on using TreeSitter info to determine identifier range
20011 buffer.update(cx, |buffer, _| {
20012 let snapshot = buffer.snapshot();
20013 let (range, kind) = snapshot.surrounding_word(position);
20014 if kind != Some(CharKind::Word) {
20015 return None;
20016 }
20017 Some(
20018 snapshot.anchor_before(range.start)
20019 ..snapshot.anchor_after(range.end),
20020 )
20021 })?
20022 }
20023 })
20024 })
20025 }))
20026 }
20027
20028 fn perform_rename(
20029 &self,
20030 buffer: &Entity<Buffer>,
20031 position: text::Anchor,
20032 new_name: String,
20033 cx: &mut App,
20034 ) -> Option<Task<Result<ProjectTransaction>>> {
20035 Some(self.update(cx, |project, cx| {
20036 project.perform_rename(buffer.clone(), position, new_name, cx)
20037 }))
20038 }
20039}
20040
20041fn inlay_hint_settings(
20042 location: Anchor,
20043 snapshot: &MultiBufferSnapshot,
20044 cx: &mut Context<Editor>,
20045) -> InlayHintSettings {
20046 let file = snapshot.file_at(location);
20047 let language = snapshot.language_at(location).map(|l| l.name());
20048 language_settings(language, file, cx).inlay_hints
20049}
20050
20051fn consume_contiguous_rows(
20052 contiguous_row_selections: &mut Vec<Selection<Point>>,
20053 selection: &Selection<Point>,
20054 display_map: &DisplaySnapshot,
20055 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20056) -> (MultiBufferRow, MultiBufferRow) {
20057 contiguous_row_selections.push(selection.clone());
20058 let start_row = MultiBufferRow(selection.start.row);
20059 let mut end_row = ending_row(selection, display_map);
20060
20061 while let Some(next_selection) = selections.peek() {
20062 if next_selection.start.row <= end_row.0 {
20063 end_row = ending_row(next_selection, display_map);
20064 contiguous_row_selections.push(selections.next().unwrap().clone());
20065 } else {
20066 break;
20067 }
20068 }
20069 (start_row, end_row)
20070}
20071
20072fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20073 if next_selection.end.column > 0 || next_selection.is_empty() {
20074 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20075 } else {
20076 MultiBufferRow(next_selection.end.row)
20077 }
20078}
20079
20080impl EditorSnapshot {
20081 pub fn remote_selections_in_range<'a>(
20082 &'a self,
20083 range: &'a Range<Anchor>,
20084 collaboration_hub: &dyn CollaborationHub,
20085 cx: &'a App,
20086 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20087 let participant_names = collaboration_hub.user_names(cx);
20088 let participant_indices = collaboration_hub.user_participant_indices(cx);
20089 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20090 let collaborators_by_replica_id = collaborators_by_peer_id
20091 .values()
20092 .map(|collaborator| (collaborator.replica_id, collaborator))
20093 .collect::<HashMap<_, _>>();
20094 self.buffer_snapshot
20095 .selections_in_range(range, false)
20096 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20097 if replica_id == AGENT_REPLICA_ID {
20098 Some(RemoteSelection {
20099 replica_id,
20100 selection,
20101 cursor_shape,
20102 line_mode,
20103 collaborator_id: CollaboratorId::Agent,
20104 user_name: Some("Agent".into()),
20105 color: cx.theme().players().agent(),
20106 })
20107 } else {
20108 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20109 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20110 let user_name = participant_names.get(&collaborator.user_id).cloned();
20111 Some(RemoteSelection {
20112 replica_id,
20113 selection,
20114 cursor_shape,
20115 line_mode,
20116 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20117 user_name,
20118 color: if let Some(index) = participant_index {
20119 cx.theme().players().color_for_participant(index.0)
20120 } else {
20121 cx.theme().players().absent()
20122 },
20123 })
20124 }
20125 })
20126 }
20127
20128 pub fn hunks_for_ranges(
20129 &self,
20130 ranges: impl IntoIterator<Item = Range<Point>>,
20131 ) -> Vec<MultiBufferDiffHunk> {
20132 let mut hunks = Vec::new();
20133 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20134 HashMap::default();
20135 for query_range in ranges {
20136 let query_rows =
20137 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20138 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20139 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20140 ) {
20141 // Include deleted hunks that are adjacent to the query range, because
20142 // otherwise they would be missed.
20143 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20144 if hunk.status().is_deleted() {
20145 intersects_range |= hunk.row_range.start == query_rows.end;
20146 intersects_range |= hunk.row_range.end == query_rows.start;
20147 }
20148 if intersects_range {
20149 if !processed_buffer_rows
20150 .entry(hunk.buffer_id)
20151 .or_default()
20152 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20153 {
20154 continue;
20155 }
20156 hunks.push(hunk);
20157 }
20158 }
20159 }
20160
20161 hunks
20162 }
20163
20164 fn display_diff_hunks_for_rows<'a>(
20165 &'a self,
20166 display_rows: Range<DisplayRow>,
20167 folded_buffers: &'a HashSet<BufferId>,
20168 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20169 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20170 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20171
20172 self.buffer_snapshot
20173 .diff_hunks_in_range(buffer_start..buffer_end)
20174 .filter_map(|hunk| {
20175 if folded_buffers.contains(&hunk.buffer_id) {
20176 return None;
20177 }
20178
20179 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20180 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20181
20182 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20183 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20184
20185 let display_hunk = if hunk_display_start.column() != 0 {
20186 DisplayDiffHunk::Folded {
20187 display_row: hunk_display_start.row(),
20188 }
20189 } else {
20190 let mut end_row = hunk_display_end.row();
20191 if hunk_display_end.column() > 0 {
20192 end_row.0 += 1;
20193 }
20194 let is_created_file = hunk.is_created_file();
20195 DisplayDiffHunk::Unfolded {
20196 status: hunk.status(),
20197 diff_base_byte_range: hunk.diff_base_byte_range,
20198 display_row_range: hunk_display_start.row()..end_row,
20199 multi_buffer_range: Anchor::range_in_buffer(
20200 hunk.excerpt_id,
20201 hunk.buffer_id,
20202 hunk.buffer_range,
20203 ),
20204 is_created_file,
20205 }
20206 };
20207
20208 Some(display_hunk)
20209 })
20210 }
20211
20212 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20213 self.display_snapshot.buffer_snapshot.language_at(position)
20214 }
20215
20216 pub fn is_focused(&self) -> bool {
20217 self.is_focused
20218 }
20219
20220 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20221 self.placeholder_text.as_ref()
20222 }
20223
20224 pub fn scroll_position(&self) -> gpui::Point<f32> {
20225 self.scroll_anchor.scroll_position(&self.display_snapshot)
20226 }
20227
20228 fn gutter_dimensions(
20229 &self,
20230 font_id: FontId,
20231 font_size: Pixels,
20232 max_line_number_width: Pixels,
20233 cx: &App,
20234 ) -> Option<GutterDimensions> {
20235 if !self.show_gutter {
20236 return None;
20237 }
20238
20239 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20240 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20241
20242 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20243 matches!(
20244 ProjectSettings::get_global(cx).git.git_gutter,
20245 Some(GitGutterSetting::TrackedFiles)
20246 )
20247 });
20248 let gutter_settings = EditorSettings::get_global(cx).gutter;
20249 let show_line_numbers = self
20250 .show_line_numbers
20251 .unwrap_or(gutter_settings.line_numbers);
20252 let line_gutter_width = if show_line_numbers {
20253 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20254 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20255 max_line_number_width.max(min_width_for_number_on_gutter)
20256 } else {
20257 0.0.into()
20258 };
20259
20260 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20261 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20262
20263 let git_blame_entries_width =
20264 self.git_blame_gutter_max_author_length
20265 .map(|max_author_length| {
20266 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20267 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20268
20269 /// The number of characters to dedicate to gaps and margins.
20270 const SPACING_WIDTH: usize = 4;
20271
20272 let max_char_count = max_author_length.min(renderer.max_author_length())
20273 + ::git::SHORT_SHA_LENGTH
20274 + MAX_RELATIVE_TIMESTAMP.len()
20275 + SPACING_WIDTH;
20276
20277 em_advance * max_char_count
20278 });
20279
20280 let is_singleton = self.buffer_snapshot.is_singleton();
20281
20282 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20283 left_padding += if !is_singleton {
20284 em_width * 4.0
20285 } else if show_runnables || show_breakpoints {
20286 em_width * 3.0
20287 } else if show_git_gutter && show_line_numbers {
20288 em_width * 2.0
20289 } else if show_git_gutter || show_line_numbers {
20290 em_width
20291 } else {
20292 px(0.)
20293 };
20294
20295 let shows_folds = is_singleton && gutter_settings.folds;
20296
20297 let right_padding = if shows_folds && show_line_numbers {
20298 em_width * 4.0
20299 } else if shows_folds || (!is_singleton && show_line_numbers) {
20300 em_width * 3.0
20301 } else if show_line_numbers {
20302 em_width
20303 } else {
20304 px(0.)
20305 };
20306
20307 Some(GutterDimensions {
20308 left_padding,
20309 right_padding,
20310 width: line_gutter_width + left_padding + right_padding,
20311 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20312 git_blame_entries_width,
20313 })
20314 }
20315
20316 pub fn render_crease_toggle(
20317 &self,
20318 buffer_row: MultiBufferRow,
20319 row_contains_cursor: bool,
20320 editor: Entity<Editor>,
20321 window: &mut Window,
20322 cx: &mut App,
20323 ) -> Option<AnyElement> {
20324 let folded = self.is_line_folded(buffer_row);
20325 let mut is_foldable = false;
20326
20327 if let Some(crease) = self
20328 .crease_snapshot
20329 .query_row(buffer_row, &self.buffer_snapshot)
20330 {
20331 is_foldable = true;
20332 match crease {
20333 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20334 if let Some(render_toggle) = render_toggle {
20335 let toggle_callback =
20336 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20337 if folded {
20338 editor.update(cx, |editor, cx| {
20339 editor.fold_at(buffer_row, window, cx)
20340 });
20341 } else {
20342 editor.update(cx, |editor, cx| {
20343 editor.unfold_at(buffer_row, window, cx)
20344 });
20345 }
20346 });
20347 return Some((render_toggle)(
20348 buffer_row,
20349 folded,
20350 toggle_callback,
20351 window,
20352 cx,
20353 ));
20354 }
20355 }
20356 }
20357 }
20358
20359 is_foldable |= self.starts_indent(buffer_row);
20360
20361 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20362 Some(
20363 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20364 .toggle_state(folded)
20365 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20366 if folded {
20367 this.unfold_at(buffer_row, window, cx);
20368 } else {
20369 this.fold_at(buffer_row, window, cx);
20370 }
20371 }))
20372 .into_any_element(),
20373 )
20374 } else {
20375 None
20376 }
20377 }
20378
20379 pub fn render_crease_trailer(
20380 &self,
20381 buffer_row: MultiBufferRow,
20382 window: &mut Window,
20383 cx: &mut App,
20384 ) -> Option<AnyElement> {
20385 let folded = self.is_line_folded(buffer_row);
20386 if let Crease::Inline { render_trailer, .. } = self
20387 .crease_snapshot
20388 .query_row(buffer_row, &self.buffer_snapshot)?
20389 {
20390 let render_trailer = render_trailer.as_ref()?;
20391 Some(render_trailer(buffer_row, folded, window, cx))
20392 } else {
20393 None
20394 }
20395 }
20396}
20397
20398impl Deref for EditorSnapshot {
20399 type Target = DisplaySnapshot;
20400
20401 fn deref(&self) -> &Self::Target {
20402 &self.display_snapshot
20403 }
20404}
20405
20406#[derive(Clone, Debug, PartialEq, Eq)]
20407pub enum EditorEvent {
20408 InputIgnored {
20409 text: Arc<str>,
20410 },
20411 InputHandled {
20412 utf16_range_to_replace: Option<Range<isize>>,
20413 text: Arc<str>,
20414 },
20415 ExcerptsAdded {
20416 buffer: Entity<Buffer>,
20417 predecessor: ExcerptId,
20418 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20419 },
20420 ExcerptsRemoved {
20421 ids: Vec<ExcerptId>,
20422 removed_buffer_ids: Vec<BufferId>,
20423 },
20424 BufferFoldToggled {
20425 ids: Vec<ExcerptId>,
20426 folded: bool,
20427 },
20428 ExcerptsEdited {
20429 ids: Vec<ExcerptId>,
20430 },
20431 ExcerptsExpanded {
20432 ids: Vec<ExcerptId>,
20433 },
20434 BufferEdited,
20435 Edited {
20436 transaction_id: clock::Lamport,
20437 },
20438 Reparsed(BufferId),
20439 Focused,
20440 FocusedIn,
20441 Blurred,
20442 DirtyChanged,
20443 Saved,
20444 TitleChanged,
20445 DiffBaseChanged,
20446 SelectionsChanged {
20447 local: bool,
20448 },
20449 ScrollPositionChanged {
20450 local: bool,
20451 autoscroll: bool,
20452 },
20453 Closed,
20454 TransactionUndone {
20455 transaction_id: clock::Lamport,
20456 },
20457 TransactionBegun {
20458 transaction_id: clock::Lamport,
20459 },
20460 Reloaded,
20461 CursorShapeChanged,
20462 PushedToNavHistory {
20463 anchor: Anchor,
20464 is_deactivate: bool,
20465 },
20466}
20467
20468impl EventEmitter<EditorEvent> for Editor {}
20469
20470impl Focusable for Editor {
20471 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20472 self.focus_handle.clone()
20473 }
20474}
20475
20476impl Render for Editor {
20477 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20478 let settings = ThemeSettings::get_global(cx);
20479
20480 let mut text_style = match self.mode {
20481 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20482 color: cx.theme().colors().editor_foreground,
20483 font_family: settings.ui_font.family.clone(),
20484 font_features: settings.ui_font.features.clone(),
20485 font_fallbacks: settings.ui_font.fallbacks.clone(),
20486 font_size: rems(0.875).into(),
20487 font_weight: settings.ui_font.weight,
20488 line_height: relative(settings.buffer_line_height.value()),
20489 ..Default::default()
20490 },
20491 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20492 color: cx.theme().colors().editor_foreground,
20493 font_family: settings.buffer_font.family.clone(),
20494 font_features: settings.buffer_font.features.clone(),
20495 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20496 font_size: settings.buffer_font_size(cx).into(),
20497 font_weight: settings.buffer_font.weight,
20498 line_height: relative(settings.buffer_line_height.value()),
20499 ..Default::default()
20500 },
20501 };
20502 if let Some(text_style_refinement) = &self.text_style_refinement {
20503 text_style.refine(text_style_refinement)
20504 }
20505
20506 let background = match self.mode {
20507 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20508 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20509 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20510 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20511 };
20512
20513 EditorElement::new(
20514 &cx.entity(),
20515 EditorStyle {
20516 background,
20517 local_player: cx.theme().players().local(),
20518 text: text_style,
20519 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20520 syntax: cx.theme().syntax().clone(),
20521 status: cx.theme().status().clone(),
20522 inlay_hints_style: make_inlay_hints_style(cx),
20523 inline_completion_styles: make_suggestion_styles(cx),
20524 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20525 show_underlines: !self.mode.is_minimap(),
20526 },
20527 )
20528 }
20529}
20530
20531impl EntityInputHandler for Editor {
20532 fn text_for_range(
20533 &mut self,
20534 range_utf16: Range<usize>,
20535 adjusted_range: &mut Option<Range<usize>>,
20536 _: &mut Window,
20537 cx: &mut Context<Self>,
20538 ) -> Option<String> {
20539 let snapshot = self.buffer.read(cx).read(cx);
20540 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20541 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20542 if (start.0..end.0) != range_utf16 {
20543 adjusted_range.replace(start.0..end.0);
20544 }
20545 Some(snapshot.text_for_range(start..end).collect())
20546 }
20547
20548 fn selected_text_range(
20549 &mut self,
20550 ignore_disabled_input: bool,
20551 _: &mut Window,
20552 cx: &mut Context<Self>,
20553 ) -> Option<UTF16Selection> {
20554 // Prevent the IME menu from appearing when holding down an alphabetic key
20555 // while input is disabled.
20556 if !ignore_disabled_input && !self.input_enabled {
20557 return None;
20558 }
20559
20560 let selection = self.selections.newest::<OffsetUtf16>(cx);
20561 let range = selection.range();
20562
20563 Some(UTF16Selection {
20564 range: range.start.0..range.end.0,
20565 reversed: selection.reversed,
20566 })
20567 }
20568
20569 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20570 let snapshot = self.buffer.read(cx).read(cx);
20571 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20572 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20573 }
20574
20575 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20576 self.clear_highlights::<InputComposition>(cx);
20577 self.ime_transaction.take();
20578 }
20579
20580 fn replace_text_in_range(
20581 &mut self,
20582 range_utf16: Option<Range<usize>>,
20583 text: &str,
20584 window: &mut Window,
20585 cx: &mut Context<Self>,
20586 ) {
20587 if !self.input_enabled {
20588 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20589 return;
20590 }
20591
20592 self.transact(window, cx, |this, window, cx| {
20593 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20594 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20595 Some(this.selection_replacement_ranges(range_utf16, cx))
20596 } else {
20597 this.marked_text_ranges(cx)
20598 };
20599
20600 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20601 let newest_selection_id = this.selections.newest_anchor().id;
20602 this.selections
20603 .all::<OffsetUtf16>(cx)
20604 .iter()
20605 .zip(ranges_to_replace.iter())
20606 .find_map(|(selection, range)| {
20607 if selection.id == newest_selection_id {
20608 Some(
20609 (range.start.0 as isize - selection.head().0 as isize)
20610 ..(range.end.0 as isize - selection.head().0 as isize),
20611 )
20612 } else {
20613 None
20614 }
20615 })
20616 });
20617
20618 cx.emit(EditorEvent::InputHandled {
20619 utf16_range_to_replace: range_to_replace,
20620 text: text.into(),
20621 });
20622
20623 if let Some(new_selected_ranges) = new_selected_ranges {
20624 this.change_selections(None, window, cx, |selections| {
20625 selections.select_ranges(new_selected_ranges)
20626 });
20627 this.backspace(&Default::default(), window, cx);
20628 }
20629
20630 this.handle_input(text, window, cx);
20631 });
20632
20633 if let Some(transaction) = self.ime_transaction {
20634 self.buffer.update(cx, |buffer, cx| {
20635 buffer.group_until_transaction(transaction, cx);
20636 });
20637 }
20638
20639 self.unmark_text(window, cx);
20640 }
20641
20642 fn replace_and_mark_text_in_range(
20643 &mut self,
20644 range_utf16: Option<Range<usize>>,
20645 text: &str,
20646 new_selected_range_utf16: Option<Range<usize>>,
20647 window: &mut Window,
20648 cx: &mut Context<Self>,
20649 ) {
20650 if !self.input_enabled {
20651 return;
20652 }
20653
20654 let transaction = self.transact(window, cx, |this, window, cx| {
20655 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20656 let snapshot = this.buffer.read(cx).read(cx);
20657 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20658 for marked_range in &mut marked_ranges {
20659 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20660 marked_range.start.0 += relative_range_utf16.start;
20661 marked_range.start =
20662 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20663 marked_range.end =
20664 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20665 }
20666 }
20667 Some(marked_ranges)
20668 } else if let Some(range_utf16) = range_utf16 {
20669 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20670 Some(this.selection_replacement_ranges(range_utf16, cx))
20671 } else {
20672 None
20673 };
20674
20675 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20676 let newest_selection_id = this.selections.newest_anchor().id;
20677 this.selections
20678 .all::<OffsetUtf16>(cx)
20679 .iter()
20680 .zip(ranges_to_replace.iter())
20681 .find_map(|(selection, range)| {
20682 if selection.id == newest_selection_id {
20683 Some(
20684 (range.start.0 as isize - selection.head().0 as isize)
20685 ..(range.end.0 as isize - selection.head().0 as isize),
20686 )
20687 } else {
20688 None
20689 }
20690 })
20691 });
20692
20693 cx.emit(EditorEvent::InputHandled {
20694 utf16_range_to_replace: range_to_replace,
20695 text: text.into(),
20696 });
20697
20698 if let Some(ranges) = ranges_to_replace {
20699 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20700 }
20701
20702 let marked_ranges = {
20703 let snapshot = this.buffer.read(cx).read(cx);
20704 this.selections
20705 .disjoint_anchors()
20706 .iter()
20707 .map(|selection| {
20708 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20709 })
20710 .collect::<Vec<_>>()
20711 };
20712
20713 if text.is_empty() {
20714 this.unmark_text(window, cx);
20715 } else {
20716 this.highlight_text::<InputComposition>(
20717 marked_ranges.clone(),
20718 HighlightStyle {
20719 underline: Some(UnderlineStyle {
20720 thickness: px(1.),
20721 color: None,
20722 wavy: false,
20723 }),
20724 ..Default::default()
20725 },
20726 cx,
20727 );
20728 }
20729
20730 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20731 let use_autoclose = this.use_autoclose;
20732 let use_auto_surround = this.use_auto_surround;
20733 this.set_use_autoclose(false);
20734 this.set_use_auto_surround(false);
20735 this.handle_input(text, window, cx);
20736 this.set_use_autoclose(use_autoclose);
20737 this.set_use_auto_surround(use_auto_surround);
20738
20739 if let Some(new_selected_range) = new_selected_range_utf16 {
20740 let snapshot = this.buffer.read(cx).read(cx);
20741 let new_selected_ranges = marked_ranges
20742 .into_iter()
20743 .map(|marked_range| {
20744 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20745 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20746 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20747 snapshot.clip_offset_utf16(new_start, Bias::Left)
20748 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20749 })
20750 .collect::<Vec<_>>();
20751
20752 drop(snapshot);
20753 this.change_selections(None, window, cx, |selections| {
20754 selections.select_ranges(new_selected_ranges)
20755 });
20756 }
20757 });
20758
20759 self.ime_transaction = self.ime_transaction.or(transaction);
20760 if let Some(transaction) = self.ime_transaction {
20761 self.buffer.update(cx, |buffer, cx| {
20762 buffer.group_until_transaction(transaction, cx);
20763 });
20764 }
20765
20766 if self.text_highlights::<InputComposition>(cx).is_none() {
20767 self.ime_transaction.take();
20768 }
20769 }
20770
20771 fn bounds_for_range(
20772 &mut self,
20773 range_utf16: Range<usize>,
20774 element_bounds: gpui::Bounds<Pixels>,
20775 window: &mut Window,
20776 cx: &mut Context<Self>,
20777 ) -> Option<gpui::Bounds<Pixels>> {
20778 let text_layout_details = self.text_layout_details(window);
20779 let gpui::Size {
20780 width: em_width,
20781 height: line_height,
20782 } = self.character_size(window);
20783
20784 let snapshot = self.snapshot(window, cx);
20785 let scroll_position = snapshot.scroll_position();
20786 let scroll_left = scroll_position.x * em_width;
20787
20788 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20789 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20790 + self.gutter_dimensions.width
20791 + self.gutter_dimensions.margin;
20792 let y = line_height * (start.row().as_f32() - scroll_position.y);
20793
20794 Some(Bounds {
20795 origin: element_bounds.origin + point(x, y),
20796 size: size(em_width, line_height),
20797 })
20798 }
20799
20800 fn character_index_for_point(
20801 &mut self,
20802 point: gpui::Point<Pixels>,
20803 _window: &mut Window,
20804 _cx: &mut Context<Self>,
20805 ) -> Option<usize> {
20806 let position_map = self.last_position_map.as_ref()?;
20807 if !position_map.text_hitbox.contains(&point) {
20808 return None;
20809 }
20810 let display_point = position_map.point_for_position(point).previous_valid;
20811 let anchor = position_map
20812 .snapshot
20813 .display_point_to_anchor(display_point, Bias::Left);
20814 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20815 Some(utf16_offset.0)
20816 }
20817}
20818
20819trait SelectionExt {
20820 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20821 fn spanned_rows(
20822 &self,
20823 include_end_if_at_line_start: bool,
20824 map: &DisplaySnapshot,
20825 ) -> Range<MultiBufferRow>;
20826}
20827
20828impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20829 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20830 let start = self
20831 .start
20832 .to_point(&map.buffer_snapshot)
20833 .to_display_point(map);
20834 let end = self
20835 .end
20836 .to_point(&map.buffer_snapshot)
20837 .to_display_point(map);
20838 if self.reversed {
20839 end..start
20840 } else {
20841 start..end
20842 }
20843 }
20844
20845 fn spanned_rows(
20846 &self,
20847 include_end_if_at_line_start: bool,
20848 map: &DisplaySnapshot,
20849 ) -> Range<MultiBufferRow> {
20850 let start = self.start.to_point(&map.buffer_snapshot);
20851 let mut end = self.end.to_point(&map.buffer_snapshot);
20852 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20853 end.row -= 1;
20854 }
20855
20856 let buffer_start = map.prev_line_boundary(start).0;
20857 let buffer_end = map.next_line_boundary(end).0;
20858 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20859 }
20860}
20861
20862impl<T: InvalidationRegion> InvalidationStack<T> {
20863 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20864 where
20865 S: Clone + ToOffset,
20866 {
20867 while let Some(region) = self.last() {
20868 let all_selections_inside_invalidation_ranges =
20869 if selections.len() == region.ranges().len() {
20870 selections
20871 .iter()
20872 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20873 .all(|(selection, invalidation_range)| {
20874 let head = selection.head().to_offset(buffer);
20875 invalidation_range.start <= head && invalidation_range.end >= head
20876 })
20877 } else {
20878 false
20879 };
20880
20881 if all_selections_inside_invalidation_ranges {
20882 break;
20883 } else {
20884 self.pop();
20885 }
20886 }
20887 }
20888}
20889
20890impl<T> Default for InvalidationStack<T> {
20891 fn default() -> Self {
20892 Self(Default::default())
20893 }
20894}
20895
20896impl<T> Deref for InvalidationStack<T> {
20897 type Target = Vec<T>;
20898
20899 fn deref(&self) -> &Self::Target {
20900 &self.0
20901 }
20902}
20903
20904impl<T> DerefMut for InvalidationStack<T> {
20905 fn deref_mut(&mut self) -> &mut Self::Target {
20906 &mut self.0
20907 }
20908}
20909
20910impl InvalidationRegion for SnippetState {
20911 fn ranges(&self) -> &[Range<Anchor>] {
20912 &self.ranges[self.active_index]
20913 }
20914}
20915
20916fn inline_completion_edit_text(
20917 current_snapshot: &BufferSnapshot,
20918 edits: &[(Range<Anchor>, String)],
20919 edit_preview: &EditPreview,
20920 include_deletions: bool,
20921 cx: &App,
20922) -> HighlightedText {
20923 let edits = edits
20924 .iter()
20925 .map(|(anchor, text)| {
20926 (
20927 anchor.start.text_anchor..anchor.end.text_anchor,
20928 text.clone(),
20929 )
20930 })
20931 .collect::<Vec<_>>();
20932
20933 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20934}
20935
20936pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20937 match severity {
20938 lsp::DiagnosticSeverity::ERROR => colors.error,
20939 lsp::DiagnosticSeverity::WARNING => colors.warning,
20940 lsp::DiagnosticSeverity::INFORMATION => colors.info,
20941 lsp::DiagnosticSeverity::HINT => colors.info,
20942 _ => colors.ignored,
20943 }
20944}
20945
20946pub fn styled_runs_for_code_label<'a>(
20947 label: &'a CodeLabel,
20948 syntax_theme: &'a theme::SyntaxTheme,
20949) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20950 let fade_out = HighlightStyle {
20951 fade_out: Some(0.35),
20952 ..Default::default()
20953 };
20954
20955 let mut prev_end = label.filter_range.end;
20956 label
20957 .runs
20958 .iter()
20959 .enumerate()
20960 .flat_map(move |(ix, (range, highlight_id))| {
20961 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20962 style
20963 } else {
20964 return Default::default();
20965 };
20966 let mut muted_style = style;
20967 muted_style.highlight(fade_out);
20968
20969 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20970 if range.start >= label.filter_range.end {
20971 if range.start > prev_end {
20972 runs.push((prev_end..range.start, fade_out));
20973 }
20974 runs.push((range.clone(), muted_style));
20975 } else if range.end <= label.filter_range.end {
20976 runs.push((range.clone(), style));
20977 } else {
20978 runs.push((range.start..label.filter_range.end, style));
20979 runs.push((label.filter_range.end..range.end, muted_style));
20980 }
20981 prev_end = cmp::max(prev_end, range.end);
20982
20983 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20984 runs.push((prev_end..label.text.len(), fade_out));
20985 }
20986
20987 runs
20988 })
20989}
20990
20991pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20992 let mut prev_index = 0;
20993 let mut prev_codepoint: Option<char> = None;
20994 text.char_indices()
20995 .chain([(text.len(), '\0')])
20996 .filter_map(move |(index, codepoint)| {
20997 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20998 let is_boundary = index == text.len()
20999 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21000 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21001 if is_boundary {
21002 let chunk = &text[prev_index..index];
21003 prev_index = index;
21004 Some(chunk)
21005 } else {
21006 None
21007 }
21008 })
21009}
21010
21011pub trait RangeToAnchorExt: Sized {
21012 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21013
21014 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21015 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21016 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21017 }
21018}
21019
21020impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21021 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21022 let start_offset = self.start.to_offset(snapshot);
21023 let end_offset = self.end.to_offset(snapshot);
21024 if start_offset == end_offset {
21025 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21026 } else {
21027 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21028 }
21029 }
21030}
21031
21032pub trait RowExt {
21033 fn as_f32(&self) -> f32;
21034
21035 fn next_row(&self) -> Self;
21036
21037 fn previous_row(&self) -> Self;
21038
21039 fn minus(&self, other: Self) -> u32;
21040}
21041
21042impl RowExt for DisplayRow {
21043 fn as_f32(&self) -> f32 {
21044 self.0 as f32
21045 }
21046
21047 fn next_row(&self) -> Self {
21048 Self(self.0 + 1)
21049 }
21050
21051 fn previous_row(&self) -> Self {
21052 Self(self.0.saturating_sub(1))
21053 }
21054
21055 fn minus(&self, other: Self) -> u32 {
21056 self.0 - other.0
21057 }
21058}
21059
21060impl RowExt for MultiBufferRow {
21061 fn as_f32(&self) -> f32 {
21062 self.0 as f32
21063 }
21064
21065 fn next_row(&self) -> Self {
21066 Self(self.0 + 1)
21067 }
21068
21069 fn previous_row(&self) -> Self {
21070 Self(self.0.saturating_sub(1))
21071 }
21072
21073 fn minus(&self, other: Self) -> u32 {
21074 self.0 - other.0
21075 }
21076}
21077
21078trait RowRangeExt {
21079 type Row;
21080
21081 fn len(&self) -> usize;
21082
21083 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21084}
21085
21086impl RowRangeExt for Range<MultiBufferRow> {
21087 type Row = MultiBufferRow;
21088
21089 fn len(&self) -> usize {
21090 (self.end.0 - self.start.0) as usize
21091 }
21092
21093 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21094 (self.start.0..self.end.0).map(MultiBufferRow)
21095 }
21096}
21097
21098impl RowRangeExt for Range<DisplayRow> {
21099 type Row = DisplayRow;
21100
21101 fn len(&self) -> usize {
21102 (self.end.0 - self.start.0) as usize
21103 }
21104
21105 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21106 (self.start.0..self.end.0).map(DisplayRow)
21107 }
21108}
21109
21110/// If select range has more than one line, we
21111/// just point the cursor to range.start.
21112fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21113 if range.start.row == range.end.row {
21114 range
21115 } else {
21116 range.start..range.start
21117 }
21118}
21119pub struct KillRing(ClipboardItem);
21120impl Global for KillRing {}
21121
21122const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21123
21124enum BreakpointPromptEditAction {
21125 Log,
21126 Condition,
21127 HitCondition,
21128}
21129
21130struct BreakpointPromptEditor {
21131 pub(crate) prompt: Entity<Editor>,
21132 editor: WeakEntity<Editor>,
21133 breakpoint_anchor: Anchor,
21134 breakpoint: Breakpoint,
21135 edit_action: BreakpointPromptEditAction,
21136 block_ids: HashSet<CustomBlockId>,
21137 editor_margins: Arc<Mutex<EditorMargins>>,
21138 _subscriptions: Vec<Subscription>,
21139}
21140
21141impl BreakpointPromptEditor {
21142 const MAX_LINES: u8 = 4;
21143
21144 fn new(
21145 editor: WeakEntity<Editor>,
21146 breakpoint_anchor: Anchor,
21147 breakpoint: Breakpoint,
21148 edit_action: BreakpointPromptEditAction,
21149 window: &mut Window,
21150 cx: &mut Context<Self>,
21151 ) -> Self {
21152 let base_text = match edit_action {
21153 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21154 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21155 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21156 }
21157 .map(|msg| msg.to_string())
21158 .unwrap_or_default();
21159
21160 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21161 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21162
21163 let prompt = cx.new(|cx| {
21164 let mut prompt = Editor::new(
21165 EditorMode::AutoHeight {
21166 max_lines: Self::MAX_LINES as usize,
21167 },
21168 buffer,
21169 None,
21170 window,
21171 cx,
21172 );
21173 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21174 prompt.set_show_cursor_when_unfocused(false, cx);
21175 prompt.set_placeholder_text(
21176 match edit_action {
21177 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21178 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21179 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21180 },
21181 cx,
21182 );
21183
21184 prompt
21185 });
21186
21187 Self {
21188 prompt,
21189 editor,
21190 breakpoint_anchor,
21191 breakpoint,
21192 edit_action,
21193 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21194 block_ids: Default::default(),
21195 _subscriptions: vec![],
21196 }
21197 }
21198
21199 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21200 self.block_ids.extend(block_ids)
21201 }
21202
21203 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21204 if let Some(editor) = self.editor.upgrade() {
21205 let message = self
21206 .prompt
21207 .read(cx)
21208 .buffer
21209 .read(cx)
21210 .as_singleton()
21211 .expect("A multi buffer in breakpoint prompt isn't possible")
21212 .read(cx)
21213 .as_rope()
21214 .to_string();
21215
21216 editor.update(cx, |editor, cx| {
21217 editor.edit_breakpoint_at_anchor(
21218 self.breakpoint_anchor,
21219 self.breakpoint.clone(),
21220 match self.edit_action {
21221 BreakpointPromptEditAction::Log => {
21222 BreakpointEditAction::EditLogMessage(message.into())
21223 }
21224 BreakpointPromptEditAction::Condition => {
21225 BreakpointEditAction::EditCondition(message.into())
21226 }
21227 BreakpointPromptEditAction::HitCondition => {
21228 BreakpointEditAction::EditHitCondition(message.into())
21229 }
21230 },
21231 cx,
21232 );
21233
21234 editor.remove_blocks(self.block_ids.clone(), None, cx);
21235 cx.focus_self(window);
21236 });
21237 }
21238 }
21239
21240 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21241 self.editor
21242 .update(cx, |editor, cx| {
21243 editor.remove_blocks(self.block_ids.clone(), None, cx);
21244 window.focus(&editor.focus_handle);
21245 })
21246 .log_err();
21247 }
21248
21249 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21250 let settings = ThemeSettings::get_global(cx);
21251 let text_style = TextStyle {
21252 color: if self.prompt.read(cx).read_only(cx) {
21253 cx.theme().colors().text_disabled
21254 } else {
21255 cx.theme().colors().text
21256 },
21257 font_family: settings.buffer_font.family.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 EditorElement::new(
21265 &self.prompt,
21266 EditorStyle {
21267 background: cx.theme().colors().editor_background,
21268 local_player: cx.theme().players().local(),
21269 text: text_style,
21270 ..Default::default()
21271 },
21272 )
21273 }
21274}
21275
21276impl Render for BreakpointPromptEditor {
21277 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21278 let editor_margins = *self.editor_margins.lock();
21279 let gutter_dimensions = editor_margins.gutter;
21280 h_flex()
21281 .key_context("Editor")
21282 .bg(cx.theme().colors().editor_background)
21283 .border_y_1()
21284 .border_color(cx.theme().status().info_border)
21285 .size_full()
21286 .py(window.line_height() / 2.5)
21287 .on_action(cx.listener(Self::confirm))
21288 .on_action(cx.listener(Self::cancel))
21289 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21290 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21291 }
21292}
21293
21294impl Focusable for BreakpointPromptEditor {
21295 fn focus_handle(&self, cx: &App) -> FocusHandle {
21296 self.prompt.focus_handle(cx)
21297 }
21298}
21299
21300fn all_edits_insertions_or_deletions(
21301 edits: &Vec<(Range<Anchor>, String)>,
21302 snapshot: &MultiBufferSnapshot,
21303) -> bool {
21304 let mut all_insertions = true;
21305 let mut all_deletions = true;
21306
21307 for (range, new_text) in edits.iter() {
21308 let range_is_empty = range.to_offset(&snapshot).is_empty();
21309 let text_is_empty = new_text.is_empty();
21310
21311 if range_is_empty != text_is_empty {
21312 if range_is_empty {
21313 all_deletions = false;
21314 } else {
21315 all_insertions = false;
21316 }
21317 } else {
21318 return false;
21319 }
21320
21321 if !all_insertions && !all_deletions {
21322 return false;
21323 }
21324 }
21325 all_insertions || all_deletions
21326}
21327
21328struct MissingEditPredictionKeybindingTooltip;
21329
21330impl Render for MissingEditPredictionKeybindingTooltip {
21331 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21332 ui::tooltip_container(window, cx, |container, _, cx| {
21333 container
21334 .flex_shrink_0()
21335 .max_w_80()
21336 .min_h(rems_from_px(124.))
21337 .justify_between()
21338 .child(
21339 v_flex()
21340 .flex_1()
21341 .text_ui_sm(cx)
21342 .child(Label::new("Conflict with Accept Keybinding"))
21343 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21344 )
21345 .child(
21346 h_flex()
21347 .pb_1()
21348 .gap_1()
21349 .items_end()
21350 .w_full()
21351 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21352 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21353 }))
21354 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21355 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21356 })),
21357 )
21358 })
21359 }
21360}
21361
21362#[derive(Debug, Clone, Copy, PartialEq)]
21363pub struct LineHighlight {
21364 pub background: Background,
21365 pub border: Option<gpui::Hsla>,
21366 pub include_gutter: bool,
21367 pub type_id: Option<TypeId>,
21368}
21369
21370fn render_diff_hunk_controls(
21371 row: u32,
21372 status: &DiffHunkStatus,
21373 hunk_range: Range<Anchor>,
21374 is_created_file: bool,
21375 line_height: Pixels,
21376 editor: &Entity<Editor>,
21377 _window: &mut Window,
21378 cx: &mut App,
21379) -> AnyElement {
21380 h_flex()
21381 .h(line_height)
21382 .mr_1()
21383 .gap_1()
21384 .px_0p5()
21385 .pb_1()
21386 .border_x_1()
21387 .border_b_1()
21388 .border_color(cx.theme().colors().border_variant)
21389 .rounded_b_lg()
21390 .bg(cx.theme().colors().editor_background)
21391 .gap_1()
21392 .occlude()
21393 .shadow_md()
21394 .child(if status.has_secondary_hunk() {
21395 Button::new(("stage", row as u64), "Stage")
21396 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21397 .tooltip({
21398 let focus_handle = editor.focus_handle(cx);
21399 move |window, cx| {
21400 Tooltip::for_action_in(
21401 "Stage Hunk",
21402 &::git::ToggleStaged,
21403 &focus_handle,
21404 window,
21405 cx,
21406 )
21407 }
21408 })
21409 .on_click({
21410 let editor = editor.clone();
21411 move |_event, _window, cx| {
21412 editor.update(cx, |editor, cx| {
21413 editor.stage_or_unstage_diff_hunks(
21414 true,
21415 vec![hunk_range.start..hunk_range.start],
21416 cx,
21417 );
21418 });
21419 }
21420 })
21421 } else {
21422 Button::new(("unstage", row as u64), "Unstage")
21423 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21424 .tooltip({
21425 let focus_handle = editor.focus_handle(cx);
21426 move |window, cx| {
21427 Tooltip::for_action_in(
21428 "Unstage Hunk",
21429 &::git::ToggleStaged,
21430 &focus_handle,
21431 window,
21432 cx,
21433 )
21434 }
21435 })
21436 .on_click({
21437 let editor = editor.clone();
21438 move |_event, _window, cx| {
21439 editor.update(cx, |editor, cx| {
21440 editor.stage_or_unstage_diff_hunks(
21441 false,
21442 vec![hunk_range.start..hunk_range.start],
21443 cx,
21444 );
21445 });
21446 }
21447 })
21448 })
21449 .child(
21450 Button::new(("restore", row as u64), "Restore")
21451 .tooltip({
21452 let focus_handle = editor.focus_handle(cx);
21453 move |window, cx| {
21454 Tooltip::for_action_in(
21455 "Restore Hunk",
21456 &::git::Restore,
21457 &focus_handle,
21458 window,
21459 cx,
21460 )
21461 }
21462 })
21463 .on_click({
21464 let editor = editor.clone();
21465 move |_event, window, cx| {
21466 editor.update(cx, |editor, cx| {
21467 let snapshot = editor.snapshot(window, cx);
21468 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21469 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21470 });
21471 }
21472 })
21473 .disabled(is_created_file),
21474 )
21475 .when(
21476 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21477 |el| {
21478 el.child(
21479 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21480 .shape(IconButtonShape::Square)
21481 .icon_size(IconSize::Small)
21482 // .disabled(!has_multiple_hunks)
21483 .tooltip({
21484 let focus_handle = editor.focus_handle(cx);
21485 move |window, cx| {
21486 Tooltip::for_action_in(
21487 "Next Hunk",
21488 &GoToHunk,
21489 &focus_handle,
21490 window,
21491 cx,
21492 )
21493 }
21494 })
21495 .on_click({
21496 let editor = editor.clone();
21497 move |_event, window, cx| {
21498 editor.update(cx, |editor, cx| {
21499 let snapshot = editor.snapshot(window, cx);
21500 let position =
21501 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21502 editor.go_to_hunk_before_or_after_position(
21503 &snapshot,
21504 position,
21505 Direction::Next,
21506 window,
21507 cx,
21508 );
21509 editor.expand_selected_diff_hunks(cx);
21510 });
21511 }
21512 }),
21513 )
21514 .child(
21515 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21516 .shape(IconButtonShape::Square)
21517 .icon_size(IconSize::Small)
21518 // .disabled(!has_multiple_hunks)
21519 .tooltip({
21520 let focus_handle = editor.focus_handle(cx);
21521 move |window, cx| {
21522 Tooltip::for_action_in(
21523 "Previous Hunk",
21524 &GoToPreviousHunk,
21525 &focus_handle,
21526 window,
21527 cx,
21528 )
21529 }
21530 })
21531 .on_click({
21532 let editor = editor.clone();
21533 move |_event, window, cx| {
21534 editor.update(cx, |editor, cx| {
21535 let snapshot = editor.snapshot(window, cx);
21536 let point =
21537 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21538 editor.go_to_hunk_before_or_after_position(
21539 &snapshot,
21540 point,
21541 Direction::Prev,
21542 window,
21543 cx,
21544 );
21545 editor.expand_selected_diff_hunks(cx);
21546 });
21547 }
21548 }),
21549 )
21550 },
21551 )
21552 .into_any_element()
21553}