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;
18mod 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 display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
66 ShowScrollbar,
67};
68use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
111 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
112 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 BreakpointWithPosition, ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
129 BreakpointStoreEvent,
130 },
131 session::{Session, SessionEvent},
132 },
133 project_settings::DiagnosticSeverity,
134};
135
136pub use git::blame::BlameRenderer;
137pub use proposed_changes_editor::{
138 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
139};
140use smallvec::smallvec;
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;
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::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)]
779struct 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 disable_expand_excerpt_buttons: bool,
927 show_line_numbers: Option<bool>,
928 use_relative_line_numbers: Option<bool>,
929 show_git_diff_gutter: Option<bool>,
930 show_code_actions: Option<bool>,
931 show_runnables: Option<bool>,
932 show_breakpoints: Option<bool>,
933 show_wrap_guides: Option<bool>,
934 show_indent_guides: Option<bool>,
935 placeholder_text: Option<Arc<str>>,
936 highlight_order: usize,
937 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
938 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
939 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
940 scrollbar_marker_state: ScrollbarMarkerState,
941 active_indent_guides_state: ActiveIndentGuidesState,
942 nav_history: Option<ItemNavHistory>,
943 context_menu: RefCell<Option<CodeContextMenu>>,
944 context_menu_options: Option<ContextMenuOptions>,
945 mouse_context_menu: Option<MouseContextMenu>,
946 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
947 inline_blame_popover: Option<InlineBlamePopover>,
948 signature_help_state: SignatureHelpState,
949 auto_signature_help: Option<bool>,
950 find_all_references_task_sources: Vec<Anchor>,
951 next_completion_id: CompletionId,
952 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
953 code_actions_task: Option<Task<Result<()>>>,
954 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
955 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 document_highlights_task: Option<Task<()>>,
957 linked_editing_range_task: Option<Task<Option<()>>>,
958 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
959 pending_rename: Option<RenameState>,
960 searchable: bool,
961 cursor_shape: CursorShape,
962 current_line_highlight: Option<CurrentLineHighlight>,
963 collapse_matches: bool,
964 autoindent_mode: Option<AutoindentMode>,
965 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
966 input_enabled: bool,
967 use_modal_editing: bool,
968 read_only: bool,
969 leader_id: Option<CollaboratorId>,
970 remote_id: Option<ViewId>,
971 pub hover_state: HoverState,
972 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
973 gutter_hovered: bool,
974 hovered_link_state: Option<HoveredLinkState>,
975 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
976 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
977 active_inline_completion: Option<InlineCompletionState>,
978 /// Used to prevent flickering as the user types while the menu is open
979 stale_inline_completion_in_menu: Option<InlineCompletionState>,
980 edit_prediction_settings: EditPredictionSettings,
981 inline_completions_hidden_for_vim_mode: bool,
982 show_inline_completions_override: Option<bool>,
983 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
984 edit_prediction_preview: EditPredictionPreview,
985 edit_prediction_indent_conflict: bool,
986 edit_prediction_requires_modifier_in_indent_conflict: bool,
987 inlay_hint_cache: InlayHintCache,
988 next_inlay_id: usize,
989 _subscriptions: Vec<Subscription>,
990 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
991 gutter_dimensions: GutterDimensions,
992 style: Option<EditorStyle>,
993 text_style_refinement: Option<TextStyleRefinement>,
994 next_editor_action_id: EditorActionId,
995 editor_actions:
996 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
997 use_autoclose: bool,
998 use_auto_surround: bool,
999 auto_replace_emoji_shortcode: bool,
1000 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1001 show_git_blame_gutter: bool,
1002 show_git_blame_inline: bool,
1003 show_git_blame_inline_delay_task: Option<Task<()>>,
1004 git_blame_inline_enabled: bool,
1005 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1006 serialize_dirty_buffers: bool,
1007 show_selection_menu: Option<bool>,
1008 blame: Option<Entity<GitBlame>>,
1009 blame_subscription: Option<Subscription>,
1010 custom_context_menu: Option<
1011 Box<
1012 dyn 'static
1013 + Fn(
1014 &mut Self,
1015 DisplayPoint,
1016 &mut Window,
1017 &mut Context<Self>,
1018 ) -> Option<Entity<ui::ContextMenu>>,
1019 >,
1020 >,
1021 last_bounds: Option<Bounds<Pixels>>,
1022 last_position_map: Option<Rc<PositionMap>>,
1023 expect_bounds_change: Option<Bounds<Pixels>>,
1024 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1025 tasks_update_task: Option<Task<()>>,
1026 breakpoint_store: Option<Entity<BreakpointStore>>,
1027 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1028 in_project_search: bool,
1029 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1030 breadcrumb_header: Option<String>,
1031 focused_block: Option<FocusedBlock>,
1032 next_scroll_position: NextScrollCursorCenterTopBottom,
1033 addons: HashMap<TypeId, Box<dyn Addon>>,
1034 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1035 load_diff_task: Option<Shared<Task<()>>>,
1036 /// Whether we are temporarily displaying a diff other than git's
1037 temporary_diff_override: bool,
1038 selection_mark_mode: bool,
1039 toggle_fold_multiple_buffers: Task<()>,
1040 _scroll_cursor_center_top_bottom_task: Task<()>,
1041 serialize_selections: Task<()>,
1042 serialize_folds: Task<()>,
1043 mouse_cursor_hidden: bool,
1044 minimap: Option<Entity<Self>>,
1045 hide_mouse_mode: HideMouseMode,
1046 pub change_list: ChangeList,
1047 inline_value_cache: InlineValueCache,
1048}
1049
1050#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1051enum NextScrollCursorCenterTopBottom {
1052 #[default]
1053 Center,
1054 Top,
1055 Bottom,
1056}
1057
1058impl NextScrollCursorCenterTopBottom {
1059 fn next(&self) -> Self {
1060 match self {
1061 Self::Center => Self::Top,
1062 Self::Top => Self::Bottom,
1063 Self::Bottom => Self::Center,
1064 }
1065 }
1066}
1067
1068#[derive(Clone)]
1069pub struct EditorSnapshot {
1070 pub mode: EditorMode,
1071 show_gutter: bool,
1072 show_line_numbers: Option<bool>,
1073 show_git_diff_gutter: Option<bool>,
1074 show_runnables: Option<bool>,
1075 show_breakpoints: Option<bool>,
1076 git_blame_gutter_max_author_length: Option<usize>,
1077 pub display_snapshot: DisplaySnapshot,
1078 pub placeholder_text: Option<Arc<str>>,
1079 is_focused: bool,
1080 scroll_anchor: ScrollAnchor,
1081 ongoing_scroll: OngoingScroll,
1082 current_line_highlight: CurrentLineHighlight,
1083 gutter_hovered: bool,
1084}
1085
1086#[derive(Default, Debug, Clone, Copy)]
1087pub struct GutterDimensions {
1088 pub left_padding: Pixels,
1089 pub right_padding: Pixels,
1090 pub width: Pixels,
1091 pub margin: Pixels,
1092 pub git_blame_entries_width: Option<Pixels>,
1093}
1094
1095impl GutterDimensions {
1096 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1097 Self {
1098 margin: Self::default_gutter_margin(font_id, font_size, cx),
1099 ..Default::default()
1100 }
1101 }
1102
1103 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1104 -cx.text_system().descent(font_id, font_size)
1105 }
1106 /// The full width of the space taken up by the gutter.
1107 pub fn full_width(&self) -> Pixels {
1108 self.margin + self.width
1109 }
1110
1111 /// The width of the space reserved for the fold indicators,
1112 /// use alongside 'justify_end' and `gutter_width` to
1113 /// right align content with the line numbers
1114 pub fn fold_area_width(&self) -> Pixels {
1115 self.margin + self.right_padding
1116 }
1117}
1118
1119#[derive(Debug)]
1120pub struct RemoteSelection {
1121 pub replica_id: ReplicaId,
1122 pub selection: Selection<Anchor>,
1123 pub cursor_shape: CursorShape,
1124 pub collaborator_id: CollaboratorId,
1125 pub line_mode: bool,
1126 pub user_name: Option<SharedString>,
1127 pub color: PlayerColor,
1128}
1129
1130#[derive(Clone, Debug)]
1131struct SelectionHistoryEntry {
1132 selections: Arc<[Selection<Anchor>]>,
1133 select_next_state: Option<SelectNextState>,
1134 select_prev_state: Option<SelectNextState>,
1135 add_selections_state: Option<AddSelectionsState>,
1136}
1137
1138enum SelectionHistoryMode {
1139 Normal,
1140 Undoing,
1141 Redoing,
1142}
1143
1144#[derive(Clone, PartialEq, Eq, Hash)]
1145struct HoveredCursor {
1146 replica_id: u16,
1147 selection_id: usize,
1148}
1149
1150impl Default for SelectionHistoryMode {
1151 fn default() -> Self {
1152 Self::Normal
1153 }
1154}
1155
1156#[derive(Default)]
1157struct SelectionHistory {
1158 #[allow(clippy::type_complexity)]
1159 selections_by_transaction:
1160 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1161 mode: SelectionHistoryMode,
1162 undo_stack: VecDeque<SelectionHistoryEntry>,
1163 redo_stack: VecDeque<SelectionHistoryEntry>,
1164}
1165
1166impl SelectionHistory {
1167 fn insert_transaction(
1168 &mut self,
1169 transaction_id: TransactionId,
1170 selections: Arc<[Selection<Anchor>]>,
1171 ) {
1172 self.selections_by_transaction
1173 .insert(transaction_id, (selections, None));
1174 }
1175
1176 #[allow(clippy::type_complexity)]
1177 fn transaction(
1178 &self,
1179 transaction_id: TransactionId,
1180 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1181 self.selections_by_transaction.get(&transaction_id)
1182 }
1183
1184 #[allow(clippy::type_complexity)]
1185 fn transaction_mut(
1186 &mut self,
1187 transaction_id: TransactionId,
1188 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1189 self.selections_by_transaction.get_mut(&transaction_id)
1190 }
1191
1192 fn push(&mut self, entry: SelectionHistoryEntry) {
1193 if !entry.selections.is_empty() {
1194 match self.mode {
1195 SelectionHistoryMode::Normal => {
1196 self.push_undo(entry);
1197 self.redo_stack.clear();
1198 }
1199 SelectionHistoryMode::Undoing => self.push_redo(entry),
1200 SelectionHistoryMode::Redoing => self.push_undo(entry),
1201 }
1202 }
1203 }
1204
1205 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1206 if self
1207 .undo_stack
1208 .back()
1209 .map_or(true, |e| e.selections != entry.selections)
1210 {
1211 self.undo_stack.push_back(entry);
1212 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1213 self.undo_stack.pop_front();
1214 }
1215 }
1216 }
1217
1218 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1219 if self
1220 .redo_stack
1221 .back()
1222 .map_or(true, |e| e.selections != entry.selections)
1223 {
1224 self.redo_stack.push_back(entry);
1225 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1226 self.redo_stack.pop_front();
1227 }
1228 }
1229 }
1230}
1231
1232#[derive(Clone, Copy)]
1233pub struct RowHighlightOptions {
1234 pub autoscroll: bool,
1235 pub include_gutter: bool,
1236}
1237
1238impl Default for RowHighlightOptions {
1239 fn default() -> Self {
1240 Self {
1241 autoscroll: Default::default(),
1242 include_gutter: true,
1243 }
1244 }
1245}
1246
1247struct RowHighlight {
1248 index: usize,
1249 range: Range<Anchor>,
1250 color: Hsla,
1251 options: RowHighlightOptions,
1252 type_id: TypeId,
1253}
1254
1255#[derive(Clone, Debug)]
1256struct AddSelectionsState {
1257 above: bool,
1258 stack: Vec<usize>,
1259}
1260
1261#[derive(Clone)]
1262struct SelectNextState {
1263 query: AhoCorasick,
1264 wordwise: bool,
1265 done: bool,
1266}
1267
1268impl std::fmt::Debug for SelectNextState {
1269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270 f.debug_struct(std::any::type_name::<Self>())
1271 .field("wordwise", &self.wordwise)
1272 .field("done", &self.done)
1273 .finish()
1274 }
1275}
1276
1277#[derive(Debug)]
1278struct AutocloseRegion {
1279 selection_id: usize,
1280 range: Range<Anchor>,
1281 pair: BracketPair,
1282}
1283
1284#[derive(Debug)]
1285struct SnippetState {
1286 ranges: Vec<Vec<Range<Anchor>>>,
1287 active_index: usize,
1288 choices: Vec<Option<Vec<String>>>,
1289}
1290
1291#[doc(hidden)]
1292pub struct RenameState {
1293 pub range: Range<Anchor>,
1294 pub old_name: Arc<str>,
1295 pub editor: Entity<Editor>,
1296 block_id: CustomBlockId,
1297}
1298
1299struct InvalidationStack<T>(Vec<T>);
1300
1301struct RegisteredInlineCompletionProvider {
1302 provider: Arc<dyn InlineCompletionProviderHandle>,
1303 _subscription: Subscription,
1304}
1305
1306#[derive(Debug, PartialEq, Eq)]
1307pub struct ActiveDiagnosticGroup {
1308 pub active_range: Range<Anchor>,
1309 pub active_message: String,
1310 pub group_id: usize,
1311 pub blocks: HashSet<CustomBlockId>,
1312}
1313
1314#[derive(Debug, PartialEq, Eq)]
1315
1316pub(crate) enum ActiveDiagnostic {
1317 None,
1318 All,
1319 Group(ActiveDiagnosticGroup),
1320}
1321
1322#[derive(Serialize, Deserialize, Clone, Debug)]
1323pub struct ClipboardSelection {
1324 /// The number of bytes in this selection.
1325 pub len: usize,
1326 /// Whether this was a full-line selection.
1327 pub is_entire_line: bool,
1328 /// The indentation of the first line when this content was originally copied.
1329 pub first_line_indent: u32,
1330}
1331
1332// selections, scroll behavior, was newest selection reversed
1333type SelectSyntaxNodeHistoryState = (
1334 Box<[Selection<usize>]>,
1335 SelectSyntaxNodeScrollBehavior,
1336 bool,
1337);
1338
1339#[derive(Default)]
1340struct SelectSyntaxNodeHistory {
1341 stack: Vec<SelectSyntaxNodeHistoryState>,
1342 // disable temporarily to allow changing selections without losing the stack
1343 pub disable_clearing: bool,
1344}
1345
1346impl SelectSyntaxNodeHistory {
1347 pub fn try_clear(&mut self) {
1348 if !self.disable_clearing {
1349 self.stack.clear();
1350 }
1351 }
1352
1353 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1354 self.stack.push(selection);
1355 }
1356
1357 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1358 self.stack.pop()
1359 }
1360}
1361
1362enum SelectSyntaxNodeScrollBehavior {
1363 CursorTop,
1364 FitSelection,
1365 CursorBottom,
1366}
1367
1368#[derive(Debug)]
1369pub(crate) struct NavigationData {
1370 cursor_anchor: Anchor,
1371 cursor_position: Point,
1372 scroll_anchor: ScrollAnchor,
1373 scroll_top_row: u32,
1374}
1375
1376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1377pub enum GotoDefinitionKind {
1378 Symbol,
1379 Declaration,
1380 Type,
1381 Implementation,
1382}
1383
1384#[derive(Debug, Clone)]
1385enum InlayHintRefreshReason {
1386 ModifiersChanged(bool),
1387 Toggle(bool),
1388 SettingsChange(InlayHintSettings),
1389 NewLinesShown,
1390 BufferEdited(HashSet<Arc<Language>>),
1391 RefreshRequested,
1392 ExcerptsRemoved(Vec<ExcerptId>),
1393}
1394
1395impl InlayHintRefreshReason {
1396 fn description(&self) -> &'static str {
1397 match self {
1398 Self::ModifiersChanged(_) => "modifiers changed",
1399 Self::Toggle(_) => "toggle",
1400 Self::SettingsChange(_) => "settings change",
1401 Self::NewLinesShown => "new lines shown",
1402 Self::BufferEdited(_) => "buffer edited",
1403 Self::RefreshRequested => "refresh requested",
1404 Self::ExcerptsRemoved(_) => "excerpts removed",
1405 }
1406 }
1407}
1408
1409pub enum FormatTarget {
1410 Buffers,
1411 Ranges(Vec<Range<MultiBufferPoint>>),
1412}
1413
1414pub(crate) struct FocusedBlock {
1415 id: BlockId,
1416 focus_handle: WeakFocusHandle,
1417}
1418
1419#[derive(Clone)]
1420enum JumpData {
1421 MultiBufferRow {
1422 row: MultiBufferRow,
1423 line_offset_from_top: u32,
1424 },
1425 MultiBufferPoint {
1426 excerpt_id: ExcerptId,
1427 position: Point,
1428 anchor: text::Anchor,
1429 line_offset_from_top: u32,
1430 },
1431}
1432
1433pub enum MultibufferSelectionMode {
1434 First,
1435 All,
1436}
1437
1438#[derive(Clone, Copy, Debug, Default)]
1439pub struct RewrapOptions {
1440 pub override_language_settings: bool,
1441 pub preserve_existing_whitespace: bool,
1442}
1443
1444impl Editor {
1445 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1446 let buffer = cx.new(|cx| Buffer::local("", cx));
1447 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1448 Self::new(
1449 EditorMode::SingleLine { auto_width: false },
1450 buffer,
1451 None,
1452 window,
1453 cx,
1454 )
1455 }
1456
1457 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1458 let buffer = cx.new(|cx| Buffer::local("", cx));
1459 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1460 Self::new(EditorMode::full(), buffer, None, window, cx)
1461 }
1462
1463 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1464 let buffer = cx.new(|cx| Buffer::local("", cx));
1465 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1466 Self::new(
1467 EditorMode::SingleLine { auto_width: true },
1468 buffer,
1469 None,
1470 window,
1471 cx,
1472 )
1473 }
1474
1475 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1476 let buffer = cx.new(|cx| Buffer::local("", cx));
1477 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1478 Self::new(
1479 EditorMode::AutoHeight { max_lines },
1480 buffer,
1481 None,
1482 window,
1483 cx,
1484 )
1485 }
1486
1487 pub fn for_buffer(
1488 buffer: Entity<Buffer>,
1489 project: Option<Entity<Project>>,
1490 window: &mut Window,
1491 cx: &mut Context<Self>,
1492 ) -> Self {
1493 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1494 Self::new(EditorMode::full(), buffer, project, window, cx)
1495 }
1496
1497 pub fn for_multibuffer(
1498 buffer: Entity<MultiBuffer>,
1499 project: Option<Entity<Project>>,
1500 window: &mut Window,
1501 cx: &mut Context<Self>,
1502 ) -> Self {
1503 Self::new(EditorMode::full(), buffer, project, window, cx)
1504 }
1505
1506 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1507 let mut clone = Self::new(
1508 self.mode.clone(),
1509 self.buffer.clone(),
1510 self.project.clone(),
1511 window,
1512 cx,
1513 );
1514 self.display_map.update(cx, |display_map, cx| {
1515 let snapshot = display_map.snapshot(cx);
1516 clone.display_map.update(cx, |display_map, cx| {
1517 display_map.set_state(&snapshot, cx);
1518 });
1519 });
1520 clone.folds_did_change(cx);
1521 clone.selections.clone_state(&self.selections);
1522 clone.scroll_manager.clone_state(&self.scroll_manager);
1523 clone.searchable = self.searchable;
1524 clone.read_only = self.read_only;
1525 clone
1526 }
1527
1528 pub fn new(
1529 mode: EditorMode,
1530 buffer: Entity<MultiBuffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 Editor::new_internal(mode, buffer, project, None, window, cx)
1536 }
1537
1538 fn new_internal(
1539 mode: EditorMode,
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 display_map: Option<Entity<DisplayMap>>,
1543 window: &mut Window,
1544 cx: &mut Context<Self>,
1545 ) -> Self {
1546 debug_assert!(
1547 display_map.is_none() || mode.is_minimap(),
1548 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1549 );
1550
1551 let full_mode = mode.is_full();
1552 let diagnostics_max_severity = if full_mode {
1553 EditorSettings::get_global(cx)
1554 .diagnostics_max_severity
1555 .unwrap_or(DiagnosticSeverity::Hint)
1556 } else {
1557 DiagnosticSeverity::Off
1558 };
1559 let style = window.text_style();
1560 let font_size = style.font_size.to_pixels(window.rem_size());
1561 let editor = cx.entity().downgrade();
1562 let fold_placeholder = FoldPlaceholder {
1563 constrain_width: true,
1564 render: Arc::new(move |fold_id, fold_range, cx| {
1565 let editor = editor.clone();
1566 div()
1567 .id(fold_id)
1568 .bg(cx.theme().colors().ghost_element_background)
1569 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1570 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1571 .rounded_xs()
1572 .size_full()
1573 .cursor_pointer()
1574 .child("⋯")
1575 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1576 .on_click(move |_, _window, cx| {
1577 editor
1578 .update(cx, |editor, cx| {
1579 editor.unfold_ranges(
1580 &[fold_range.start..fold_range.end],
1581 true,
1582 false,
1583 cx,
1584 );
1585 cx.stop_propagation();
1586 })
1587 .ok();
1588 })
1589 .into_any()
1590 }),
1591 merge_adjacent: true,
1592 ..FoldPlaceholder::default()
1593 };
1594 let display_map = display_map.unwrap_or_else(|| {
1595 cx.new(|cx| {
1596 DisplayMap::new(
1597 buffer.clone(),
1598 style.font(),
1599 font_size,
1600 None,
1601 FILE_HEADER_HEIGHT,
1602 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1603 fold_placeholder,
1604 diagnostics_max_severity,
1605 cx,
1606 )
1607 })
1608 });
1609
1610 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1611
1612 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1613
1614 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1615 .then(|| language_settings::SoftWrap::None);
1616
1617 let mut project_subscriptions = Vec::new();
1618 if mode.is_full() {
1619 if let Some(project) = project.as_ref() {
1620 project_subscriptions.push(cx.subscribe_in(
1621 project,
1622 window,
1623 |editor, _, event, window, cx| match event {
1624 project::Event::RefreshCodeLens => {
1625 // we always query lens with actions, without storing them, always refreshing them
1626 }
1627 project::Event::RefreshInlayHints => {
1628 editor
1629 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1630 }
1631 project::Event::SnippetEdit(id, snippet_edits) => {
1632 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1633 let focus_handle = editor.focus_handle(cx);
1634 if focus_handle.is_focused(window) {
1635 let snapshot = buffer.read(cx).snapshot();
1636 for (range, snippet) in snippet_edits {
1637 let editor_range =
1638 language::range_from_lsp(*range).to_offset(&snapshot);
1639 editor
1640 .insert_snippet(
1641 &[editor_range],
1642 snippet.clone(),
1643 window,
1644 cx,
1645 )
1646 .ok();
1647 }
1648 }
1649 }
1650 }
1651 _ => {}
1652 },
1653 ));
1654 if let Some(task_inventory) = project
1655 .read(cx)
1656 .task_store()
1657 .read(cx)
1658 .task_inventory()
1659 .cloned()
1660 {
1661 project_subscriptions.push(cx.observe_in(
1662 &task_inventory,
1663 window,
1664 |editor, _, window, cx| {
1665 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1666 },
1667 ));
1668 };
1669
1670 project_subscriptions.push(cx.subscribe_in(
1671 &project.read(cx).breakpoint_store(),
1672 window,
1673 |editor, _, event, window, cx| match event {
1674 BreakpointStoreEvent::ClearDebugLines => {
1675 editor.clear_row_highlights::<ActiveDebugLine>();
1676 editor.refresh_inline_values(cx);
1677 }
1678 BreakpointStoreEvent::SetDebugLine => {
1679 if editor.go_to_active_debug_line(window, cx) {
1680 cx.stop_propagation();
1681 }
1682
1683 editor.refresh_inline_values(cx);
1684 }
1685 _ => {}
1686 },
1687 ));
1688 }
1689 }
1690
1691 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1692
1693 let inlay_hint_settings =
1694 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1695 let focus_handle = cx.focus_handle();
1696 cx.on_focus(&focus_handle, window, Self::handle_focus)
1697 .detach();
1698 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1699 .detach();
1700 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1701 .detach();
1702 cx.on_blur(&focus_handle, window, Self::handle_blur)
1703 .detach();
1704
1705 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1706 Some(false)
1707 } else {
1708 None
1709 };
1710
1711 let breakpoint_store = match (&mode, project.as_ref()) {
1712 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1713 _ => None,
1714 };
1715
1716 let mut code_action_providers = Vec::new();
1717 let mut load_uncommitted_diff = None;
1718 if let Some(project) = project.clone() {
1719 load_uncommitted_diff = Some(
1720 update_uncommitted_diff_for_buffer(
1721 cx.entity(),
1722 &project,
1723 buffer.read(cx).all_buffers(),
1724 buffer.clone(),
1725 cx,
1726 )
1727 .shared(),
1728 );
1729 code_action_providers.push(Rc::new(project) as Rc<_>);
1730 }
1731
1732 let mut this = Self {
1733 focus_handle,
1734 show_cursor_when_unfocused: false,
1735 last_focused_descendant: None,
1736 buffer: buffer.clone(),
1737 display_map: display_map.clone(),
1738 selections,
1739 scroll_manager: ScrollManager::new(cx),
1740 columnar_selection_tail: None,
1741 add_selections_state: None,
1742 select_next_state: None,
1743 select_prev_state: None,
1744 selection_history: SelectionHistory::default(),
1745 autoclose_regions: Vec::new(),
1746 snippet_stack: InvalidationStack::default(),
1747 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1748 ime_transaction: None,
1749 active_diagnostics: ActiveDiagnostic::None,
1750 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1751 inline_diagnostics_update: Task::ready(()),
1752 inline_diagnostics: Vec::new(),
1753 soft_wrap_mode_override,
1754 diagnostics_max_severity,
1755 hard_wrap: None,
1756 completion_provider: project.clone().map(|project| Box::new(project) as _),
1757 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1758 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1759 project,
1760 blink_manager: blink_manager.clone(),
1761 show_local_selections: true,
1762 show_scrollbars: full_mode,
1763 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1764 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1765 show_gutter: mode.is_full(),
1766 show_line_numbers: None,
1767 use_relative_line_numbers: None,
1768 disable_expand_excerpt_buttons: false,
1769 show_git_diff_gutter: None,
1770 show_code_actions: None,
1771 show_runnables: None,
1772 show_breakpoints: None,
1773 show_wrap_guides: None,
1774 show_indent_guides,
1775 placeholder_text: None,
1776 highlight_order: 0,
1777 highlighted_rows: HashMap::default(),
1778 background_highlights: TreeMap::default(),
1779 gutter_highlights: TreeMap::default(),
1780 scrollbar_marker_state: ScrollbarMarkerState::default(),
1781 active_indent_guides_state: ActiveIndentGuidesState::default(),
1782 nav_history: None,
1783 context_menu: RefCell::new(None),
1784 context_menu_options: None,
1785 mouse_context_menu: None,
1786 completion_tasks: Vec::new(),
1787 inline_blame_popover: None,
1788 signature_help_state: SignatureHelpState::default(),
1789 auto_signature_help: None,
1790 find_all_references_task_sources: Vec::new(),
1791 next_completion_id: 0,
1792 next_inlay_id: 0,
1793 code_action_providers,
1794 available_code_actions: None,
1795 code_actions_task: None,
1796 quick_selection_highlight_task: None,
1797 debounced_selection_highlight_task: None,
1798 document_highlights_task: None,
1799 linked_editing_range_task: None,
1800 pending_rename: None,
1801 searchable: true,
1802 cursor_shape: EditorSettings::get_global(cx)
1803 .cursor_shape
1804 .unwrap_or_default(),
1805 current_line_highlight: None,
1806 autoindent_mode: Some(AutoindentMode::EachLine),
1807 collapse_matches: false,
1808 workspace: None,
1809 input_enabled: true,
1810 use_modal_editing: mode.is_full(),
1811 read_only: mode.is_minimap(),
1812 use_autoclose: true,
1813 use_auto_surround: true,
1814 auto_replace_emoji_shortcode: false,
1815 jsx_tag_auto_close_enabled_in_any_buffer: false,
1816 leader_id: None,
1817 remote_id: None,
1818 hover_state: HoverState::default(),
1819 pending_mouse_down: None,
1820 hovered_link_state: None,
1821 edit_prediction_provider: None,
1822 active_inline_completion: None,
1823 stale_inline_completion_in_menu: None,
1824 edit_prediction_preview: EditPredictionPreview::Inactive {
1825 released_too_fast: false,
1826 },
1827 inline_diagnostics_enabled: mode.is_full(),
1828 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1829 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1830
1831 gutter_hovered: false,
1832 pixel_position_of_newest_cursor: None,
1833 last_bounds: None,
1834 last_position_map: None,
1835 expect_bounds_change: None,
1836 gutter_dimensions: GutterDimensions::default(),
1837 style: None,
1838 show_cursor_names: false,
1839 hovered_cursors: HashMap::default(),
1840 next_editor_action_id: EditorActionId::default(),
1841 editor_actions: Rc::default(),
1842 inline_completions_hidden_for_vim_mode: false,
1843 show_inline_completions_override: None,
1844 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1845 edit_prediction_settings: EditPredictionSettings::Disabled,
1846 edit_prediction_indent_conflict: false,
1847 edit_prediction_requires_modifier_in_indent_conflict: true,
1848 custom_context_menu: None,
1849 show_git_blame_gutter: false,
1850 show_git_blame_inline: false,
1851 show_selection_menu: None,
1852 show_git_blame_inline_delay_task: None,
1853 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1854 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1855 serialize_dirty_buffers: !mode.is_minimap()
1856 && ProjectSettings::get_global(cx)
1857 .session
1858 .restore_unsaved_buffers,
1859 blame: None,
1860 blame_subscription: None,
1861 tasks: BTreeMap::default(),
1862
1863 breakpoint_store,
1864 gutter_breakpoint_indicator: (None, None),
1865 _subscriptions: vec![
1866 cx.observe(&buffer, Self::on_buffer_changed),
1867 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1868 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1869 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1870 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1871 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1872 cx.observe_window_activation(window, |editor, window, cx| {
1873 let active = window.is_window_active();
1874 editor.blink_manager.update(cx, |blink_manager, cx| {
1875 if active {
1876 blink_manager.enable(cx);
1877 } else {
1878 blink_manager.disable(cx);
1879 }
1880 });
1881 }),
1882 ],
1883 tasks_update_task: None,
1884 linked_edit_ranges: Default::default(),
1885 in_project_search: false,
1886 previous_search_ranges: None,
1887 breadcrumb_header: None,
1888 focused_block: None,
1889 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1890 addons: HashMap::default(),
1891 registered_buffers: HashMap::default(),
1892 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1893 selection_mark_mode: false,
1894 toggle_fold_multiple_buffers: Task::ready(()),
1895 serialize_selections: Task::ready(()),
1896 serialize_folds: Task::ready(()),
1897 text_style_refinement: None,
1898 load_diff_task: load_uncommitted_diff,
1899 temporary_diff_override: false,
1900 mouse_cursor_hidden: false,
1901 minimap: None,
1902 hide_mouse_mode: EditorSettings::get_global(cx)
1903 .hide_mouse
1904 .unwrap_or_default(),
1905 change_list: ChangeList::new(),
1906 mode,
1907 };
1908 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1909 this._subscriptions
1910 .push(cx.observe(breakpoints, |_, _, cx| {
1911 cx.notify();
1912 }));
1913 }
1914 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1915 this._subscriptions.extend(project_subscriptions);
1916
1917 this._subscriptions.push(cx.subscribe_in(
1918 &cx.entity(),
1919 window,
1920 |editor, _, e: &EditorEvent, window, cx| match e {
1921 EditorEvent::ScrollPositionChanged { local, .. } => {
1922 if *local {
1923 let new_anchor = editor.scroll_manager.anchor();
1924 let snapshot = editor.snapshot(window, cx);
1925 editor.update_restoration_data(cx, move |data| {
1926 data.scroll_position = (
1927 new_anchor.top_row(&snapshot.buffer_snapshot),
1928 new_anchor.offset,
1929 );
1930 });
1931 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1932 editor.inline_blame_popover.take();
1933 }
1934 }
1935 EditorEvent::Edited { .. } => {
1936 if !vim_enabled(cx) {
1937 let (map, selections) = editor.selections.all_adjusted_display(cx);
1938 let pop_state = editor
1939 .change_list
1940 .last()
1941 .map(|previous| {
1942 previous.len() == selections.len()
1943 && previous.iter().enumerate().all(|(ix, p)| {
1944 p.to_display_point(&map).row()
1945 == selections[ix].head().row()
1946 })
1947 })
1948 .unwrap_or(false);
1949 let new_positions = selections
1950 .into_iter()
1951 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1952 .collect();
1953 editor
1954 .change_list
1955 .push_to_change_list(pop_state, new_positions);
1956 }
1957 }
1958 _ => (),
1959 },
1960 ));
1961
1962 if let Some(dap_store) = this
1963 .project
1964 .as_ref()
1965 .map(|project| project.read(cx).dap_store())
1966 {
1967 let weak_editor = cx.weak_entity();
1968
1969 this._subscriptions
1970 .push(
1971 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1972 let session_entity = cx.entity();
1973 weak_editor
1974 .update(cx, |editor, cx| {
1975 editor._subscriptions.push(
1976 cx.subscribe(&session_entity, Self::on_debug_session_event),
1977 );
1978 })
1979 .ok();
1980 }),
1981 );
1982
1983 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1984 this._subscriptions
1985 .push(cx.subscribe(&session, Self::on_debug_session_event));
1986 }
1987 }
1988
1989 this.end_selection(window, cx);
1990 this.scroll_manager.show_scrollbars(window, cx);
1991 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1992
1993 if full_mode {
1994 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1995 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1996
1997 if this.git_blame_inline_enabled {
1998 this.start_git_blame_inline(false, window, cx);
1999 }
2000
2001 this.go_to_active_debug_line(window, cx);
2002
2003 if let Some(buffer) = buffer.read(cx).as_singleton() {
2004 if let Some(project) = this.project.as_ref() {
2005 let handle = project.update(cx, |project, cx| {
2006 project.register_buffer_with_language_servers(&buffer, cx)
2007 });
2008 this.registered_buffers
2009 .insert(buffer.read(cx).remote_id(), handle);
2010 }
2011 }
2012
2013 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2014 }
2015
2016 this.report_editor_event("Editor Opened", None, cx);
2017 this
2018 }
2019
2020 pub fn deploy_mouse_context_menu(
2021 &mut self,
2022 position: gpui::Point<Pixels>,
2023 context_menu: Entity<ContextMenu>,
2024 window: &mut Window,
2025 cx: &mut Context<Self>,
2026 ) {
2027 self.mouse_context_menu = Some(MouseContextMenu::new(
2028 self,
2029 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2030 context_menu,
2031 window,
2032 cx,
2033 ));
2034 }
2035
2036 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2037 self.mouse_context_menu
2038 .as_ref()
2039 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2040 }
2041
2042 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2043 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2044 }
2045
2046 fn key_context_internal(
2047 &self,
2048 has_active_edit_prediction: bool,
2049 window: &Window,
2050 cx: &App,
2051 ) -> KeyContext {
2052 let mut key_context = KeyContext::new_with_defaults();
2053 key_context.add("Editor");
2054 let mode = match self.mode {
2055 EditorMode::SingleLine { .. } => "single_line",
2056 EditorMode::AutoHeight { .. } => "auto_height",
2057 EditorMode::Minimap { .. } => "minimap",
2058 EditorMode::Full { .. } => "full",
2059 };
2060
2061 if EditorSettings::jupyter_enabled(cx) {
2062 key_context.add("jupyter");
2063 }
2064
2065 key_context.set("mode", mode);
2066 if self.pending_rename.is_some() {
2067 key_context.add("renaming");
2068 }
2069
2070 match self.context_menu.borrow().as_ref() {
2071 Some(CodeContextMenu::Completions(_)) => {
2072 key_context.add("menu");
2073 key_context.add("showing_completions");
2074 }
2075 Some(CodeContextMenu::CodeActions(_)) => {
2076 key_context.add("menu");
2077 key_context.add("showing_code_actions")
2078 }
2079 None => {}
2080 }
2081
2082 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2083 if !self.focus_handle(cx).contains_focused(window, cx)
2084 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2085 {
2086 for addon in self.addons.values() {
2087 addon.extend_key_context(&mut key_context, cx)
2088 }
2089 }
2090
2091 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2092 if let Some(extension) = singleton_buffer
2093 .read(cx)
2094 .file()
2095 .and_then(|file| file.path().extension()?.to_str())
2096 {
2097 key_context.set("extension", extension.to_string());
2098 }
2099 } else {
2100 key_context.add("multibuffer");
2101 }
2102
2103 if has_active_edit_prediction {
2104 if self.edit_prediction_in_conflict() {
2105 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2106 } else {
2107 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2108 key_context.add("copilot_suggestion");
2109 }
2110 }
2111
2112 if self.selection_mark_mode {
2113 key_context.add("selection_mode");
2114 }
2115
2116 key_context
2117 }
2118
2119 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2120 self.mouse_cursor_hidden = match origin {
2121 HideMouseCursorOrigin::TypingAction => {
2122 matches!(
2123 self.hide_mouse_mode,
2124 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2125 )
2126 }
2127 HideMouseCursorOrigin::MovementAction => {
2128 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2129 }
2130 };
2131 }
2132
2133 pub fn edit_prediction_in_conflict(&self) -> bool {
2134 if !self.show_edit_predictions_in_menu() {
2135 return false;
2136 }
2137
2138 let showing_completions = self
2139 .context_menu
2140 .borrow()
2141 .as_ref()
2142 .map_or(false, |context| {
2143 matches!(context, CodeContextMenu::Completions(_))
2144 });
2145
2146 showing_completions
2147 || self.edit_prediction_requires_modifier()
2148 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2149 // bindings to insert tab characters.
2150 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2151 }
2152
2153 pub fn accept_edit_prediction_keybind(
2154 &self,
2155 window: &Window,
2156 cx: &App,
2157 ) -> AcceptEditPredictionBinding {
2158 let key_context = self.key_context_internal(true, window, cx);
2159 let in_conflict = self.edit_prediction_in_conflict();
2160
2161 AcceptEditPredictionBinding(
2162 window
2163 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2164 .into_iter()
2165 .filter(|binding| {
2166 !in_conflict
2167 || binding
2168 .keystrokes()
2169 .first()
2170 .map_or(false, |keystroke| keystroke.modifiers.modified())
2171 })
2172 .rev()
2173 .min_by_key(|binding| {
2174 binding
2175 .keystrokes()
2176 .first()
2177 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2178 }),
2179 )
2180 }
2181
2182 pub fn new_file(
2183 workspace: &mut Workspace,
2184 _: &workspace::NewFile,
2185 window: &mut Window,
2186 cx: &mut Context<Workspace>,
2187 ) {
2188 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2189 "Failed to create buffer",
2190 window,
2191 cx,
2192 |e, _, _| match e.error_code() {
2193 ErrorCode::RemoteUpgradeRequired => Some(format!(
2194 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2195 e.error_tag("required").unwrap_or("the latest version")
2196 )),
2197 _ => None,
2198 },
2199 );
2200 }
2201
2202 pub fn new_in_workspace(
2203 workspace: &mut Workspace,
2204 window: &mut Window,
2205 cx: &mut Context<Workspace>,
2206 ) -> Task<Result<Entity<Editor>>> {
2207 let project = workspace.project().clone();
2208 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2209
2210 cx.spawn_in(window, async move |workspace, cx| {
2211 let buffer = create.await?;
2212 workspace.update_in(cx, |workspace, window, cx| {
2213 let editor =
2214 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2215 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2216 editor
2217 })
2218 })
2219 }
2220
2221 fn new_file_vertical(
2222 workspace: &mut Workspace,
2223 _: &workspace::NewFileSplitVertical,
2224 window: &mut Window,
2225 cx: &mut Context<Workspace>,
2226 ) {
2227 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2228 }
2229
2230 fn new_file_horizontal(
2231 workspace: &mut Workspace,
2232 _: &workspace::NewFileSplitHorizontal,
2233 window: &mut Window,
2234 cx: &mut Context<Workspace>,
2235 ) {
2236 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2237 }
2238
2239 fn new_file_in_direction(
2240 workspace: &mut Workspace,
2241 direction: SplitDirection,
2242 window: &mut Window,
2243 cx: &mut Context<Workspace>,
2244 ) {
2245 let project = workspace.project().clone();
2246 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2247
2248 cx.spawn_in(window, async move |workspace, cx| {
2249 let buffer = create.await?;
2250 workspace.update_in(cx, move |workspace, window, cx| {
2251 workspace.split_item(
2252 direction,
2253 Box::new(
2254 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2255 ),
2256 window,
2257 cx,
2258 )
2259 })?;
2260 anyhow::Ok(())
2261 })
2262 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2263 match e.error_code() {
2264 ErrorCode::RemoteUpgradeRequired => Some(format!(
2265 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2266 e.error_tag("required").unwrap_or("the latest version")
2267 )),
2268 _ => None,
2269 }
2270 });
2271 }
2272
2273 pub fn leader_id(&self) -> Option<CollaboratorId> {
2274 self.leader_id
2275 }
2276
2277 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2278 &self.buffer
2279 }
2280
2281 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2282 self.workspace.as_ref()?.0.upgrade()
2283 }
2284
2285 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2286 self.buffer().read(cx).title(cx)
2287 }
2288
2289 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2290 let git_blame_gutter_max_author_length = self
2291 .render_git_blame_gutter(cx)
2292 .then(|| {
2293 if let Some(blame) = self.blame.as_ref() {
2294 let max_author_length =
2295 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2296 Some(max_author_length)
2297 } else {
2298 None
2299 }
2300 })
2301 .flatten();
2302
2303 EditorSnapshot {
2304 mode: self.mode.clone(),
2305 show_gutter: self.show_gutter,
2306 show_line_numbers: self.show_line_numbers,
2307 show_git_diff_gutter: self.show_git_diff_gutter,
2308 show_runnables: self.show_runnables,
2309 show_breakpoints: self.show_breakpoints,
2310 git_blame_gutter_max_author_length,
2311 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2312 scroll_anchor: self.scroll_manager.anchor(),
2313 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2314 placeholder_text: self.placeholder_text.clone(),
2315 is_focused: self.focus_handle.is_focused(window),
2316 current_line_highlight: self
2317 .current_line_highlight
2318 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2319 gutter_hovered: self.gutter_hovered,
2320 }
2321 }
2322
2323 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2324 self.buffer.read(cx).language_at(point, cx)
2325 }
2326
2327 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2328 self.buffer.read(cx).read(cx).file_at(point).cloned()
2329 }
2330
2331 pub fn active_excerpt(
2332 &self,
2333 cx: &App,
2334 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2335 self.buffer
2336 .read(cx)
2337 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2338 }
2339
2340 pub fn mode(&self) -> &EditorMode {
2341 &self.mode
2342 }
2343
2344 pub fn set_mode(&mut self, mode: EditorMode) {
2345 self.mode = mode;
2346 }
2347
2348 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2349 self.collaboration_hub.as_deref()
2350 }
2351
2352 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2353 self.collaboration_hub = Some(hub);
2354 }
2355
2356 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2357 self.in_project_search = in_project_search;
2358 }
2359
2360 pub fn set_custom_context_menu(
2361 &mut self,
2362 f: impl 'static
2363 + Fn(
2364 &mut Self,
2365 DisplayPoint,
2366 &mut Window,
2367 &mut Context<Self>,
2368 ) -> Option<Entity<ui::ContextMenu>>,
2369 ) {
2370 self.custom_context_menu = Some(Box::new(f))
2371 }
2372
2373 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2374 self.completion_provider = provider;
2375 }
2376
2377 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2378 self.semantics_provider.clone()
2379 }
2380
2381 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2382 self.semantics_provider = provider;
2383 }
2384
2385 pub fn set_edit_prediction_provider<T>(
2386 &mut self,
2387 provider: Option<Entity<T>>,
2388 window: &mut Window,
2389 cx: &mut Context<Self>,
2390 ) where
2391 T: EditPredictionProvider,
2392 {
2393 self.edit_prediction_provider =
2394 provider.map(|provider| RegisteredInlineCompletionProvider {
2395 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2396 if this.focus_handle.is_focused(window) {
2397 this.update_visible_inline_completion(window, cx);
2398 }
2399 }),
2400 provider: Arc::new(provider),
2401 });
2402 self.update_edit_prediction_settings(cx);
2403 self.refresh_inline_completion(false, false, window, cx);
2404 }
2405
2406 pub fn placeholder_text(&self) -> Option<&str> {
2407 self.placeholder_text.as_deref()
2408 }
2409
2410 pub fn set_placeholder_text(
2411 &mut self,
2412 placeholder_text: impl Into<Arc<str>>,
2413 cx: &mut Context<Self>,
2414 ) {
2415 let placeholder_text = Some(placeholder_text.into());
2416 if self.placeholder_text != placeholder_text {
2417 self.placeholder_text = placeholder_text;
2418 cx.notify();
2419 }
2420 }
2421
2422 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2423 self.cursor_shape = cursor_shape;
2424
2425 // Disrupt blink for immediate user feedback that the cursor shape has changed
2426 self.blink_manager.update(cx, BlinkManager::show_cursor);
2427
2428 cx.notify();
2429 }
2430
2431 pub fn set_current_line_highlight(
2432 &mut self,
2433 current_line_highlight: Option<CurrentLineHighlight>,
2434 ) {
2435 self.current_line_highlight = current_line_highlight;
2436 }
2437
2438 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2439 self.collapse_matches = collapse_matches;
2440 }
2441
2442 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2443 let buffers = self.buffer.read(cx).all_buffers();
2444 let Some(project) = self.project.as_ref() else {
2445 return;
2446 };
2447 project.update(cx, |project, cx| {
2448 for buffer in buffers {
2449 self.registered_buffers
2450 .entry(buffer.read(cx).remote_id())
2451 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2452 }
2453 })
2454 }
2455
2456 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2457 if self.collapse_matches {
2458 return range.start..range.start;
2459 }
2460 range.clone()
2461 }
2462
2463 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2464 if self.display_map.read(cx).clip_at_line_ends != clip {
2465 self.display_map
2466 .update(cx, |map, _| map.clip_at_line_ends = clip);
2467 }
2468 }
2469
2470 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2471 self.input_enabled = input_enabled;
2472 }
2473
2474 pub fn set_inline_completions_hidden_for_vim_mode(
2475 &mut self,
2476 hidden: bool,
2477 window: &mut Window,
2478 cx: &mut Context<Self>,
2479 ) {
2480 if hidden != self.inline_completions_hidden_for_vim_mode {
2481 self.inline_completions_hidden_for_vim_mode = hidden;
2482 if hidden {
2483 self.update_visible_inline_completion(window, cx);
2484 } else {
2485 self.refresh_inline_completion(true, false, window, cx);
2486 }
2487 }
2488 }
2489
2490 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2491 self.menu_inline_completions_policy = value;
2492 }
2493
2494 pub fn set_autoindent(&mut self, autoindent: bool) {
2495 if autoindent {
2496 self.autoindent_mode = Some(AutoindentMode::EachLine);
2497 } else {
2498 self.autoindent_mode = None;
2499 }
2500 }
2501
2502 pub fn read_only(&self, cx: &App) -> bool {
2503 self.read_only || self.buffer.read(cx).read_only()
2504 }
2505
2506 pub fn set_read_only(&mut self, read_only: bool) {
2507 self.read_only = read_only;
2508 }
2509
2510 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2511 self.use_autoclose = autoclose;
2512 }
2513
2514 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2515 self.use_auto_surround = auto_surround;
2516 }
2517
2518 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2519 self.auto_replace_emoji_shortcode = auto_replace;
2520 }
2521
2522 pub fn toggle_edit_predictions(
2523 &mut self,
2524 _: &ToggleEditPrediction,
2525 window: &mut Window,
2526 cx: &mut Context<Self>,
2527 ) {
2528 if self.show_inline_completions_override.is_some() {
2529 self.set_show_edit_predictions(None, window, cx);
2530 } else {
2531 let show_edit_predictions = !self.edit_predictions_enabled();
2532 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2533 }
2534 }
2535
2536 pub fn set_show_edit_predictions(
2537 &mut self,
2538 show_edit_predictions: Option<bool>,
2539 window: &mut Window,
2540 cx: &mut Context<Self>,
2541 ) {
2542 self.show_inline_completions_override = show_edit_predictions;
2543 self.update_edit_prediction_settings(cx);
2544
2545 if let Some(false) = show_edit_predictions {
2546 self.discard_inline_completion(false, cx);
2547 } else {
2548 self.refresh_inline_completion(false, true, window, cx);
2549 }
2550 }
2551
2552 fn inline_completions_disabled_in_scope(
2553 &self,
2554 buffer: &Entity<Buffer>,
2555 buffer_position: language::Anchor,
2556 cx: &App,
2557 ) -> bool {
2558 let snapshot = buffer.read(cx).snapshot();
2559 let settings = snapshot.settings_at(buffer_position, cx);
2560
2561 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2562 return false;
2563 };
2564
2565 scope.override_name().map_or(false, |scope_name| {
2566 settings
2567 .edit_predictions_disabled_in
2568 .iter()
2569 .any(|s| s == scope_name)
2570 })
2571 }
2572
2573 pub fn set_use_modal_editing(&mut self, to: bool) {
2574 self.use_modal_editing = to;
2575 }
2576
2577 pub fn use_modal_editing(&self) -> bool {
2578 self.use_modal_editing
2579 }
2580
2581 fn selections_did_change(
2582 &mut self,
2583 local: bool,
2584 old_cursor_position: &Anchor,
2585 show_completions: bool,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) {
2589 window.invalidate_character_coordinates();
2590
2591 // Copy selections to primary selection buffer
2592 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2593 if local {
2594 let selections = self.selections.all::<usize>(cx);
2595 let buffer_handle = self.buffer.read(cx).read(cx);
2596
2597 let mut text = String::new();
2598 for (index, selection) in selections.iter().enumerate() {
2599 let text_for_selection = buffer_handle
2600 .text_for_range(selection.start..selection.end)
2601 .collect::<String>();
2602
2603 text.push_str(&text_for_selection);
2604 if index != selections.len() - 1 {
2605 text.push('\n');
2606 }
2607 }
2608
2609 if !text.is_empty() {
2610 cx.write_to_primary(ClipboardItem::new_string(text));
2611 }
2612 }
2613
2614 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2615 self.buffer.update(cx, |buffer, cx| {
2616 buffer.set_active_selections(
2617 &self.selections.disjoint_anchors(),
2618 self.selections.line_mode,
2619 self.cursor_shape,
2620 cx,
2621 )
2622 });
2623 }
2624 let display_map = self
2625 .display_map
2626 .update(cx, |display_map, cx| display_map.snapshot(cx));
2627 let buffer = &display_map.buffer_snapshot;
2628 self.add_selections_state = None;
2629 self.select_next_state = None;
2630 self.select_prev_state = None;
2631 self.select_syntax_node_history.try_clear();
2632 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2633 self.snippet_stack
2634 .invalidate(&self.selections.disjoint_anchors(), buffer);
2635 self.take_rename(false, window, cx);
2636
2637 let new_cursor_position = self.selections.newest_anchor().head();
2638
2639 self.push_to_nav_history(
2640 *old_cursor_position,
2641 Some(new_cursor_position.to_point(buffer)),
2642 false,
2643 cx,
2644 );
2645
2646 if local {
2647 let new_cursor_position = self.selections.newest_anchor().head();
2648 let mut context_menu = self.context_menu.borrow_mut();
2649 let completion_menu = match context_menu.as_ref() {
2650 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2651 _ => {
2652 *context_menu = None;
2653 None
2654 }
2655 };
2656 if let Some(buffer_id) = new_cursor_position.buffer_id {
2657 if !self.registered_buffers.contains_key(&buffer_id) {
2658 if let Some(project) = self.project.as_ref() {
2659 project.update(cx, |project, cx| {
2660 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2661 return;
2662 };
2663 self.registered_buffers.insert(
2664 buffer_id,
2665 project.register_buffer_with_language_servers(&buffer, cx),
2666 );
2667 })
2668 }
2669 }
2670 }
2671
2672 if let Some(completion_menu) = completion_menu {
2673 let cursor_position = new_cursor_position.to_offset(buffer);
2674 let (word_range, kind) =
2675 buffer.surrounding_word(completion_menu.initial_position, true);
2676 if kind == Some(CharKind::Word)
2677 && word_range.to_inclusive().contains(&cursor_position)
2678 {
2679 let mut completion_menu = completion_menu.clone();
2680 drop(context_menu);
2681
2682 let query = Self::completion_query(buffer, cursor_position);
2683 cx.spawn(async move |this, cx| {
2684 completion_menu
2685 .filter(query.as_deref(), cx.background_executor().clone())
2686 .await;
2687
2688 this.update(cx, |this, cx| {
2689 let mut context_menu = this.context_menu.borrow_mut();
2690 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2691 else {
2692 return;
2693 };
2694
2695 if menu.id > completion_menu.id {
2696 return;
2697 }
2698
2699 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2700 drop(context_menu);
2701 cx.notify();
2702 })
2703 })
2704 .detach();
2705
2706 if show_completions {
2707 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2708 }
2709 } else {
2710 drop(context_menu);
2711 self.hide_context_menu(window, cx);
2712 }
2713 } else {
2714 drop(context_menu);
2715 }
2716
2717 hide_hover(self, cx);
2718
2719 if old_cursor_position.to_display_point(&display_map).row()
2720 != new_cursor_position.to_display_point(&display_map).row()
2721 {
2722 self.available_code_actions.take();
2723 }
2724 self.refresh_code_actions(window, cx);
2725 self.refresh_document_highlights(cx);
2726 self.refresh_selected_text_highlights(false, window, cx);
2727 refresh_matching_bracket_highlights(self, window, cx);
2728 self.update_visible_inline_completion(window, cx);
2729 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2730 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2731 self.inline_blame_popover.take();
2732 if self.git_blame_inline_enabled {
2733 self.start_inline_blame_timer(window, cx);
2734 }
2735 }
2736
2737 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2738 cx.emit(EditorEvent::SelectionsChanged { local });
2739
2740 let selections = &self.selections.disjoint;
2741 if selections.len() == 1 {
2742 cx.emit(SearchEvent::ActiveMatchChanged)
2743 }
2744 if local {
2745 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2746 let inmemory_selections = selections
2747 .iter()
2748 .map(|s| {
2749 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2750 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2751 })
2752 .collect();
2753 self.update_restoration_data(cx, |data| {
2754 data.selections = inmemory_selections;
2755 });
2756
2757 if WorkspaceSettings::get(None, cx).restore_on_startup
2758 != RestoreOnStartupBehavior::None
2759 {
2760 if let Some(workspace_id) =
2761 self.workspace.as_ref().and_then(|workspace| workspace.1)
2762 {
2763 let snapshot = self.buffer().read(cx).snapshot(cx);
2764 let selections = selections.clone();
2765 let background_executor = cx.background_executor().clone();
2766 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2767 self.serialize_selections = cx.background_spawn(async move {
2768 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2769 let db_selections = selections
2770 .iter()
2771 .map(|selection| {
2772 (
2773 selection.start.to_offset(&snapshot),
2774 selection.end.to_offset(&snapshot),
2775 )
2776 })
2777 .collect();
2778
2779 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2780 .await
2781 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2782 .log_err();
2783 });
2784 }
2785 }
2786 }
2787 }
2788
2789 cx.notify();
2790 }
2791
2792 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2793 use text::ToOffset as _;
2794 use text::ToPoint as _;
2795
2796 if self.mode.is_minimap()
2797 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2798 {
2799 return;
2800 }
2801
2802 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2803 return;
2804 };
2805
2806 let snapshot = singleton.read(cx).snapshot();
2807 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2808 let display_snapshot = display_map.snapshot(cx);
2809
2810 display_snapshot
2811 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2812 .map(|fold| {
2813 fold.range.start.text_anchor.to_point(&snapshot)
2814 ..fold.range.end.text_anchor.to_point(&snapshot)
2815 })
2816 .collect()
2817 });
2818 self.update_restoration_data(cx, |data| {
2819 data.folds = inmemory_folds;
2820 });
2821
2822 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2823 return;
2824 };
2825 let background_executor = cx.background_executor().clone();
2826 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2827 let db_folds = self.display_map.update(cx, |display_map, cx| {
2828 display_map
2829 .snapshot(cx)
2830 .folds_in_range(0..snapshot.len())
2831 .map(|fold| {
2832 (
2833 fold.range.start.text_anchor.to_offset(&snapshot),
2834 fold.range.end.text_anchor.to_offset(&snapshot),
2835 )
2836 })
2837 .collect()
2838 });
2839 self.serialize_folds = cx.background_spawn(async move {
2840 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2841 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2842 .await
2843 .with_context(|| {
2844 format!(
2845 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2846 )
2847 })
2848 .log_err();
2849 });
2850 }
2851
2852 pub fn sync_selections(
2853 &mut self,
2854 other: Entity<Editor>,
2855 cx: &mut Context<Self>,
2856 ) -> gpui::Subscription {
2857 let other_selections = other.read(cx).selections.disjoint.to_vec();
2858 self.selections.change_with(cx, |selections| {
2859 selections.select_anchors(other_selections);
2860 });
2861
2862 let other_subscription =
2863 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2864 EditorEvent::SelectionsChanged { local: true } => {
2865 let other_selections = other.read(cx).selections.disjoint.to_vec();
2866 if other_selections.is_empty() {
2867 return;
2868 }
2869 this.selections.change_with(cx, |selections| {
2870 selections.select_anchors(other_selections);
2871 });
2872 }
2873 _ => {}
2874 });
2875
2876 let this_subscription =
2877 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2878 EditorEvent::SelectionsChanged { local: true } => {
2879 let these_selections = this.selections.disjoint.to_vec();
2880 if these_selections.is_empty() {
2881 return;
2882 }
2883 other.update(cx, |other_editor, cx| {
2884 other_editor.selections.change_with(cx, |selections| {
2885 selections.select_anchors(these_selections);
2886 })
2887 });
2888 }
2889 _ => {}
2890 });
2891
2892 Subscription::join(other_subscription, this_subscription)
2893 }
2894
2895 pub fn change_selections<R>(
2896 &mut self,
2897 autoscroll: Option<Autoscroll>,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2901 ) -> R {
2902 self.change_selections_inner(autoscroll, true, window, cx, change)
2903 }
2904
2905 fn change_selections_inner<R>(
2906 &mut self,
2907 autoscroll: Option<Autoscroll>,
2908 request_completions: bool,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2912 ) -> R {
2913 let old_cursor_position = self.selections.newest_anchor().head();
2914 self.push_to_selection_history();
2915
2916 let (changed, result) = self.selections.change_with(cx, change);
2917
2918 if changed {
2919 if let Some(autoscroll) = autoscroll {
2920 self.request_autoscroll(autoscroll, cx);
2921 }
2922 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2923
2924 if self.should_open_signature_help_automatically(
2925 &old_cursor_position,
2926 self.signature_help_state.backspace_pressed(),
2927 cx,
2928 ) {
2929 self.show_signature_help(&ShowSignatureHelp, window, cx);
2930 }
2931 self.signature_help_state.set_backspace_pressed(false);
2932 }
2933
2934 result
2935 }
2936
2937 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2938 where
2939 I: IntoIterator<Item = (Range<S>, T)>,
2940 S: ToOffset,
2941 T: Into<Arc<str>>,
2942 {
2943 if self.read_only(cx) {
2944 return;
2945 }
2946
2947 self.buffer
2948 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2949 }
2950
2951 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2952 where
2953 I: IntoIterator<Item = (Range<S>, T)>,
2954 S: ToOffset,
2955 T: Into<Arc<str>>,
2956 {
2957 if self.read_only(cx) {
2958 return;
2959 }
2960
2961 self.buffer.update(cx, |buffer, cx| {
2962 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2963 });
2964 }
2965
2966 pub fn edit_with_block_indent<I, S, T>(
2967 &mut self,
2968 edits: I,
2969 original_indent_columns: Vec<Option<u32>>,
2970 cx: &mut Context<Self>,
2971 ) where
2972 I: IntoIterator<Item = (Range<S>, T)>,
2973 S: ToOffset,
2974 T: Into<Arc<str>>,
2975 {
2976 if self.read_only(cx) {
2977 return;
2978 }
2979
2980 self.buffer.update(cx, |buffer, cx| {
2981 buffer.edit(
2982 edits,
2983 Some(AutoindentMode::Block {
2984 original_indent_columns,
2985 }),
2986 cx,
2987 )
2988 });
2989 }
2990
2991 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2992 self.hide_context_menu(window, cx);
2993
2994 match phase {
2995 SelectPhase::Begin {
2996 position,
2997 add,
2998 click_count,
2999 } => self.begin_selection(position, add, click_count, window, cx),
3000 SelectPhase::BeginColumnar {
3001 position,
3002 goal_column,
3003 reset,
3004 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3005 SelectPhase::Extend {
3006 position,
3007 click_count,
3008 } => self.extend_selection(position, click_count, window, cx),
3009 SelectPhase::Update {
3010 position,
3011 goal_column,
3012 scroll_delta,
3013 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3014 SelectPhase::End => self.end_selection(window, cx),
3015 }
3016 }
3017
3018 fn extend_selection(
3019 &mut self,
3020 position: DisplayPoint,
3021 click_count: usize,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3026 let tail = self.selections.newest::<usize>(cx).tail();
3027 self.begin_selection(position, false, click_count, window, cx);
3028
3029 let position = position.to_offset(&display_map, Bias::Left);
3030 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3031
3032 let mut pending_selection = self
3033 .selections
3034 .pending_anchor()
3035 .expect("extend_selection not called with pending selection");
3036 if position >= tail {
3037 pending_selection.start = tail_anchor;
3038 } else {
3039 pending_selection.end = tail_anchor;
3040 pending_selection.reversed = true;
3041 }
3042
3043 let mut pending_mode = self.selections.pending_mode().unwrap();
3044 match &mut pending_mode {
3045 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3046 _ => {}
3047 }
3048
3049 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3050
3051 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3052 s.set_pending(pending_selection, pending_mode)
3053 });
3054 }
3055
3056 fn begin_selection(
3057 &mut self,
3058 position: DisplayPoint,
3059 add: bool,
3060 click_count: usize,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 ) {
3064 if !self.focus_handle.is_focused(window) {
3065 self.last_focused_descendant = None;
3066 window.focus(&self.focus_handle);
3067 }
3068
3069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3070 let buffer = &display_map.buffer_snapshot;
3071 let position = display_map.clip_point(position, Bias::Left);
3072
3073 let start;
3074 let end;
3075 let mode;
3076 let mut auto_scroll;
3077 match click_count {
3078 1 => {
3079 start = buffer.anchor_before(position.to_point(&display_map));
3080 end = start;
3081 mode = SelectMode::Character;
3082 auto_scroll = true;
3083 }
3084 2 => {
3085 let range = movement::surrounding_word(&display_map, position);
3086 start = buffer.anchor_before(range.start.to_point(&display_map));
3087 end = buffer.anchor_before(range.end.to_point(&display_map));
3088 mode = SelectMode::Word(start..end);
3089 auto_scroll = true;
3090 }
3091 3 => {
3092 let position = display_map
3093 .clip_point(position, Bias::Left)
3094 .to_point(&display_map);
3095 let line_start = display_map.prev_line_boundary(position).0;
3096 let next_line_start = buffer.clip_point(
3097 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3098 Bias::Left,
3099 );
3100 start = buffer.anchor_before(line_start);
3101 end = buffer.anchor_before(next_line_start);
3102 mode = SelectMode::Line(start..end);
3103 auto_scroll = true;
3104 }
3105 _ => {
3106 start = buffer.anchor_before(0);
3107 end = buffer.anchor_before(buffer.len());
3108 mode = SelectMode::All;
3109 auto_scroll = false;
3110 }
3111 }
3112 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3113
3114 let point_to_delete: Option<usize> = {
3115 let selected_points: Vec<Selection<Point>> =
3116 self.selections.disjoint_in_range(start..end, cx);
3117
3118 if !add || click_count > 1 {
3119 None
3120 } else if !selected_points.is_empty() {
3121 Some(selected_points[0].id)
3122 } else {
3123 let clicked_point_already_selected =
3124 self.selections.disjoint.iter().find(|selection| {
3125 selection.start.to_point(buffer) == start.to_point(buffer)
3126 || selection.end.to_point(buffer) == end.to_point(buffer)
3127 });
3128
3129 clicked_point_already_selected.map(|selection| selection.id)
3130 }
3131 };
3132
3133 let selections_count = self.selections.count();
3134
3135 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3136 if let Some(point_to_delete) = point_to_delete {
3137 s.delete(point_to_delete);
3138
3139 if selections_count == 1 {
3140 s.set_pending_anchor_range(start..end, mode);
3141 }
3142 } else {
3143 if !add {
3144 s.clear_disjoint();
3145 }
3146
3147 s.set_pending_anchor_range(start..end, mode);
3148 }
3149 });
3150 }
3151
3152 fn begin_columnar_selection(
3153 &mut self,
3154 position: DisplayPoint,
3155 goal_column: u32,
3156 reset: bool,
3157 window: &mut Window,
3158 cx: &mut Context<Self>,
3159 ) {
3160 if !self.focus_handle.is_focused(window) {
3161 self.last_focused_descendant = None;
3162 window.focus(&self.focus_handle);
3163 }
3164
3165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3166
3167 if reset {
3168 let pointer_position = display_map
3169 .buffer_snapshot
3170 .anchor_before(position.to_point(&display_map));
3171
3172 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3173 s.clear_disjoint();
3174 s.set_pending_anchor_range(
3175 pointer_position..pointer_position,
3176 SelectMode::Character,
3177 );
3178 });
3179 }
3180
3181 let tail = self.selections.newest::<Point>(cx).tail();
3182 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3183
3184 if !reset {
3185 self.select_columns(
3186 tail.to_display_point(&display_map),
3187 position,
3188 goal_column,
3189 &display_map,
3190 window,
3191 cx,
3192 );
3193 }
3194 }
3195
3196 fn update_selection(
3197 &mut self,
3198 position: DisplayPoint,
3199 goal_column: u32,
3200 scroll_delta: gpui::Point<f32>,
3201 window: &mut Window,
3202 cx: &mut Context<Self>,
3203 ) {
3204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3205
3206 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3207 let tail = tail.to_display_point(&display_map);
3208 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3209 } else if let Some(mut pending) = self.selections.pending_anchor() {
3210 let buffer = self.buffer.read(cx).snapshot(cx);
3211 let head;
3212 let tail;
3213 let mode = self.selections.pending_mode().unwrap();
3214 match &mode {
3215 SelectMode::Character => {
3216 head = position.to_point(&display_map);
3217 tail = pending.tail().to_point(&buffer);
3218 }
3219 SelectMode::Word(original_range) => {
3220 let original_display_range = original_range.start.to_display_point(&display_map)
3221 ..original_range.end.to_display_point(&display_map);
3222 let original_buffer_range = original_display_range.start.to_point(&display_map)
3223 ..original_display_range.end.to_point(&display_map);
3224 if movement::is_inside_word(&display_map, position)
3225 || original_display_range.contains(&position)
3226 {
3227 let word_range = movement::surrounding_word(&display_map, position);
3228 if word_range.start < original_display_range.start {
3229 head = word_range.start.to_point(&display_map);
3230 } else {
3231 head = word_range.end.to_point(&display_map);
3232 }
3233 } else {
3234 head = position.to_point(&display_map);
3235 }
3236
3237 if head <= original_buffer_range.start {
3238 tail = original_buffer_range.end;
3239 } else {
3240 tail = original_buffer_range.start;
3241 }
3242 }
3243 SelectMode::Line(original_range) => {
3244 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3245
3246 let position = display_map
3247 .clip_point(position, Bias::Left)
3248 .to_point(&display_map);
3249 let line_start = display_map.prev_line_boundary(position).0;
3250 let next_line_start = buffer.clip_point(
3251 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3252 Bias::Left,
3253 );
3254
3255 if line_start < original_range.start {
3256 head = line_start
3257 } else {
3258 head = next_line_start
3259 }
3260
3261 if head <= original_range.start {
3262 tail = original_range.end;
3263 } else {
3264 tail = original_range.start;
3265 }
3266 }
3267 SelectMode::All => {
3268 return;
3269 }
3270 };
3271
3272 if head < tail {
3273 pending.start = buffer.anchor_before(head);
3274 pending.end = buffer.anchor_before(tail);
3275 pending.reversed = true;
3276 } else {
3277 pending.start = buffer.anchor_before(tail);
3278 pending.end = buffer.anchor_before(head);
3279 pending.reversed = false;
3280 }
3281
3282 self.change_selections(None, window, cx, |s| {
3283 s.set_pending(pending, mode);
3284 });
3285 } else {
3286 log::error!("update_selection dispatched with no pending selection");
3287 return;
3288 }
3289
3290 self.apply_scroll_delta(scroll_delta, window, cx);
3291 cx.notify();
3292 }
3293
3294 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3295 self.columnar_selection_tail.take();
3296 if self.selections.pending_anchor().is_some() {
3297 let selections = self.selections.all::<usize>(cx);
3298 self.change_selections(None, window, cx, |s| {
3299 s.select(selections);
3300 s.clear_pending();
3301 });
3302 }
3303 }
3304
3305 fn select_columns(
3306 &mut self,
3307 tail: DisplayPoint,
3308 head: DisplayPoint,
3309 goal_column: u32,
3310 display_map: &DisplaySnapshot,
3311 window: &mut Window,
3312 cx: &mut Context<Self>,
3313 ) {
3314 let start_row = cmp::min(tail.row(), head.row());
3315 let end_row = cmp::max(tail.row(), head.row());
3316 let start_column = cmp::min(tail.column(), goal_column);
3317 let end_column = cmp::max(tail.column(), goal_column);
3318 let reversed = start_column < tail.column();
3319
3320 let selection_ranges = (start_row.0..=end_row.0)
3321 .map(DisplayRow)
3322 .filter_map(|row| {
3323 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3324 let start = display_map
3325 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3326 .to_point(display_map);
3327 let end = display_map
3328 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3329 .to_point(display_map);
3330 if reversed {
3331 Some(end..start)
3332 } else {
3333 Some(start..end)
3334 }
3335 } else {
3336 None
3337 }
3338 })
3339 .collect::<Vec<_>>();
3340
3341 self.change_selections(None, window, cx, |s| {
3342 s.select_ranges(selection_ranges);
3343 });
3344 cx.notify();
3345 }
3346
3347 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3348 self.selections
3349 .all_adjusted(cx)
3350 .iter()
3351 .any(|selection| !selection.is_empty())
3352 }
3353
3354 pub fn has_pending_nonempty_selection(&self) -> bool {
3355 let pending_nonempty_selection = match self.selections.pending_anchor() {
3356 Some(Selection { start, end, .. }) => start != end,
3357 None => false,
3358 };
3359
3360 pending_nonempty_selection
3361 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3362 }
3363
3364 pub fn has_pending_selection(&self) -> bool {
3365 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3366 }
3367
3368 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3369 self.selection_mark_mode = false;
3370
3371 if self.clear_expanded_diff_hunks(cx) {
3372 cx.notify();
3373 return;
3374 }
3375 if self.dismiss_menus_and_popups(true, window, cx) {
3376 return;
3377 }
3378
3379 if self.mode.is_full()
3380 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3381 {
3382 return;
3383 }
3384
3385 cx.propagate();
3386 }
3387
3388 pub fn dismiss_menus_and_popups(
3389 &mut self,
3390 is_user_requested: bool,
3391 window: &mut Window,
3392 cx: &mut Context<Self>,
3393 ) -> bool {
3394 if self.take_rename(false, window, cx).is_some() {
3395 return true;
3396 }
3397
3398 if hide_hover(self, cx) {
3399 return true;
3400 }
3401
3402 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3403 return true;
3404 }
3405
3406 if self.hide_context_menu(window, cx).is_some() {
3407 return true;
3408 }
3409
3410 if self.mouse_context_menu.take().is_some() {
3411 return true;
3412 }
3413
3414 if is_user_requested && self.discard_inline_completion(true, cx) {
3415 return true;
3416 }
3417
3418 if self.snippet_stack.pop().is_some() {
3419 return true;
3420 }
3421
3422 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3423 self.dismiss_diagnostics(cx);
3424 return true;
3425 }
3426
3427 false
3428 }
3429
3430 fn linked_editing_ranges_for(
3431 &self,
3432 selection: Range<text::Anchor>,
3433 cx: &App,
3434 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3435 if self.linked_edit_ranges.is_empty() {
3436 return None;
3437 }
3438 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3439 selection.end.buffer_id.and_then(|end_buffer_id| {
3440 if selection.start.buffer_id != Some(end_buffer_id) {
3441 return None;
3442 }
3443 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3444 let snapshot = buffer.read(cx).snapshot();
3445 self.linked_edit_ranges
3446 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3447 .map(|ranges| (ranges, snapshot, buffer))
3448 })?;
3449 use text::ToOffset as TO;
3450 // find offset from the start of current range to current cursor position
3451 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3452
3453 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3454 let start_difference = start_offset - start_byte_offset;
3455 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3456 let end_difference = end_offset - start_byte_offset;
3457 // Current range has associated linked ranges.
3458 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3459 for range in linked_ranges.iter() {
3460 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3461 let end_offset = start_offset + end_difference;
3462 let start_offset = start_offset + start_difference;
3463 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3464 continue;
3465 }
3466 if self.selections.disjoint_anchor_ranges().any(|s| {
3467 if s.start.buffer_id != selection.start.buffer_id
3468 || s.end.buffer_id != selection.end.buffer_id
3469 {
3470 return false;
3471 }
3472 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3473 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3474 }) {
3475 continue;
3476 }
3477 let start = buffer_snapshot.anchor_after(start_offset);
3478 let end = buffer_snapshot.anchor_after(end_offset);
3479 linked_edits
3480 .entry(buffer.clone())
3481 .or_default()
3482 .push(start..end);
3483 }
3484 Some(linked_edits)
3485 }
3486
3487 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3488 let text: Arc<str> = text.into();
3489
3490 if self.read_only(cx) {
3491 return;
3492 }
3493
3494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3495
3496 let selections = self.selections.all_adjusted(cx);
3497 let mut bracket_inserted = false;
3498 let mut edits = Vec::new();
3499 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3500 let mut new_selections = Vec::with_capacity(selections.len());
3501 let mut new_autoclose_regions = Vec::new();
3502 let snapshot = self.buffer.read(cx).read(cx);
3503 let mut clear_linked_edit_ranges = false;
3504
3505 for (selection, autoclose_region) in
3506 self.selections_with_autoclose_regions(selections, &snapshot)
3507 {
3508 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3509 // Determine if the inserted text matches the opening or closing
3510 // bracket of any of this language's bracket pairs.
3511 let mut bracket_pair = None;
3512 let mut is_bracket_pair_start = false;
3513 let mut is_bracket_pair_end = false;
3514 if !text.is_empty() {
3515 let mut bracket_pair_matching_end = None;
3516 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3517 // and they are removing the character that triggered IME popup.
3518 for (pair, enabled) in scope.brackets() {
3519 if !pair.close && !pair.surround {
3520 continue;
3521 }
3522
3523 if enabled && pair.start.ends_with(text.as_ref()) {
3524 let prefix_len = pair.start.len() - text.len();
3525 let preceding_text_matches_prefix = prefix_len == 0
3526 || (selection.start.column >= (prefix_len as u32)
3527 && snapshot.contains_str_at(
3528 Point::new(
3529 selection.start.row,
3530 selection.start.column - (prefix_len as u32),
3531 ),
3532 &pair.start[..prefix_len],
3533 ));
3534 if preceding_text_matches_prefix {
3535 bracket_pair = Some(pair.clone());
3536 is_bracket_pair_start = true;
3537 break;
3538 }
3539 }
3540 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3541 {
3542 // take first bracket pair matching end, but don't break in case a later bracket
3543 // pair matches start
3544 bracket_pair_matching_end = Some(pair.clone());
3545 }
3546 }
3547 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3548 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3549 is_bracket_pair_end = true;
3550 }
3551 }
3552
3553 if let Some(bracket_pair) = bracket_pair {
3554 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3555 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3556 let auto_surround =
3557 self.use_auto_surround && snapshot_settings.use_auto_surround;
3558 if selection.is_empty() {
3559 if is_bracket_pair_start {
3560 // If the inserted text is a suffix of an opening bracket and the
3561 // selection is preceded by the rest of the opening bracket, then
3562 // insert the closing bracket.
3563 let following_text_allows_autoclose = snapshot
3564 .chars_at(selection.start)
3565 .next()
3566 .map_or(true, |c| scope.should_autoclose_before(c));
3567
3568 let preceding_text_allows_autoclose = selection.start.column == 0
3569 || snapshot.reversed_chars_at(selection.start).next().map_or(
3570 true,
3571 |c| {
3572 bracket_pair.start != bracket_pair.end
3573 || !snapshot
3574 .char_classifier_at(selection.start)
3575 .is_word(c)
3576 },
3577 );
3578
3579 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3580 && bracket_pair.start.len() == 1
3581 {
3582 let target = bracket_pair.start.chars().next().unwrap();
3583 let current_line_count = snapshot
3584 .reversed_chars_at(selection.start)
3585 .take_while(|&c| c != '\n')
3586 .filter(|&c| c == target)
3587 .count();
3588 current_line_count % 2 == 1
3589 } else {
3590 false
3591 };
3592
3593 if autoclose
3594 && bracket_pair.close
3595 && following_text_allows_autoclose
3596 && preceding_text_allows_autoclose
3597 && !is_closing_quote
3598 {
3599 let anchor = snapshot.anchor_before(selection.end);
3600 new_selections.push((selection.map(|_| anchor), text.len()));
3601 new_autoclose_regions.push((
3602 anchor,
3603 text.len(),
3604 selection.id,
3605 bracket_pair.clone(),
3606 ));
3607 edits.push((
3608 selection.range(),
3609 format!("{}{}", text, bracket_pair.end).into(),
3610 ));
3611 bracket_inserted = true;
3612 continue;
3613 }
3614 }
3615
3616 if let Some(region) = autoclose_region {
3617 // If the selection is followed by an auto-inserted closing bracket,
3618 // then don't insert that closing bracket again; just move the selection
3619 // past the closing bracket.
3620 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3621 && text.as_ref() == region.pair.end.as_str();
3622 if should_skip {
3623 let anchor = snapshot.anchor_after(selection.end);
3624 new_selections
3625 .push((selection.map(|_| anchor), region.pair.end.len()));
3626 continue;
3627 }
3628 }
3629
3630 let always_treat_brackets_as_autoclosed = snapshot
3631 .language_settings_at(selection.start, cx)
3632 .always_treat_brackets_as_autoclosed;
3633 if always_treat_brackets_as_autoclosed
3634 && is_bracket_pair_end
3635 && snapshot.contains_str_at(selection.end, text.as_ref())
3636 {
3637 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3638 // and the inserted text is a closing bracket and the selection is followed
3639 // by the closing bracket then move the selection past the closing bracket.
3640 let anchor = snapshot.anchor_after(selection.end);
3641 new_selections.push((selection.map(|_| anchor), text.len()));
3642 continue;
3643 }
3644 }
3645 // If an opening bracket is 1 character long and is typed while
3646 // text is selected, then surround that text with the bracket pair.
3647 else if auto_surround
3648 && bracket_pair.surround
3649 && is_bracket_pair_start
3650 && bracket_pair.start.chars().count() == 1
3651 {
3652 edits.push((selection.start..selection.start, text.clone()));
3653 edits.push((
3654 selection.end..selection.end,
3655 bracket_pair.end.as_str().into(),
3656 ));
3657 bracket_inserted = true;
3658 new_selections.push((
3659 Selection {
3660 id: selection.id,
3661 start: snapshot.anchor_after(selection.start),
3662 end: snapshot.anchor_before(selection.end),
3663 reversed: selection.reversed,
3664 goal: selection.goal,
3665 },
3666 0,
3667 ));
3668 continue;
3669 }
3670 }
3671 }
3672
3673 if self.auto_replace_emoji_shortcode
3674 && selection.is_empty()
3675 && text.as_ref().ends_with(':')
3676 {
3677 if let Some(possible_emoji_short_code) =
3678 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3679 {
3680 if !possible_emoji_short_code.is_empty() {
3681 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3682 let emoji_shortcode_start = Point::new(
3683 selection.start.row,
3684 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3685 );
3686
3687 // Remove shortcode from buffer
3688 edits.push((
3689 emoji_shortcode_start..selection.start,
3690 "".to_string().into(),
3691 ));
3692 new_selections.push((
3693 Selection {
3694 id: selection.id,
3695 start: snapshot.anchor_after(emoji_shortcode_start),
3696 end: snapshot.anchor_before(selection.start),
3697 reversed: selection.reversed,
3698 goal: selection.goal,
3699 },
3700 0,
3701 ));
3702
3703 // Insert emoji
3704 let selection_start_anchor = snapshot.anchor_after(selection.start);
3705 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3706 edits.push((selection.start..selection.end, emoji.to_string().into()));
3707
3708 continue;
3709 }
3710 }
3711 }
3712 }
3713
3714 // If not handling any auto-close operation, then just replace the selected
3715 // text with the given input and move the selection to the end of the
3716 // newly inserted text.
3717 let anchor = snapshot.anchor_after(selection.end);
3718 if !self.linked_edit_ranges.is_empty() {
3719 let start_anchor = snapshot.anchor_before(selection.start);
3720
3721 let is_word_char = text.chars().next().map_or(true, |char| {
3722 let classifier = snapshot
3723 .char_classifier_at(start_anchor.to_offset(&snapshot))
3724 .ignore_punctuation(true);
3725 classifier.is_word(char)
3726 });
3727
3728 if is_word_char {
3729 if let Some(ranges) = self
3730 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3731 {
3732 for (buffer, edits) in ranges {
3733 linked_edits
3734 .entry(buffer.clone())
3735 .or_default()
3736 .extend(edits.into_iter().map(|range| (range, text.clone())));
3737 }
3738 }
3739 } else {
3740 clear_linked_edit_ranges = true;
3741 }
3742 }
3743
3744 new_selections.push((selection.map(|_| anchor), 0));
3745 edits.push((selection.start..selection.end, text.clone()));
3746 }
3747
3748 drop(snapshot);
3749
3750 self.transact(window, cx, |this, window, cx| {
3751 if clear_linked_edit_ranges {
3752 this.linked_edit_ranges.clear();
3753 }
3754 let initial_buffer_versions =
3755 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3756
3757 this.buffer.update(cx, |buffer, cx| {
3758 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3759 });
3760 for (buffer, edits) in linked_edits {
3761 buffer.update(cx, |buffer, cx| {
3762 let snapshot = buffer.snapshot();
3763 let edits = edits
3764 .into_iter()
3765 .map(|(range, text)| {
3766 use text::ToPoint as TP;
3767 let end_point = TP::to_point(&range.end, &snapshot);
3768 let start_point = TP::to_point(&range.start, &snapshot);
3769 (start_point..end_point, text)
3770 })
3771 .sorted_by_key(|(range, _)| range.start);
3772 buffer.edit(edits, None, cx);
3773 })
3774 }
3775 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3776 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3777 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3778 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3779 .zip(new_selection_deltas)
3780 .map(|(selection, delta)| Selection {
3781 id: selection.id,
3782 start: selection.start + delta,
3783 end: selection.end + delta,
3784 reversed: selection.reversed,
3785 goal: SelectionGoal::None,
3786 })
3787 .collect::<Vec<_>>();
3788
3789 let mut i = 0;
3790 for (position, delta, selection_id, pair) in new_autoclose_regions {
3791 let position = position.to_offset(&map.buffer_snapshot) + delta;
3792 let start = map.buffer_snapshot.anchor_before(position);
3793 let end = map.buffer_snapshot.anchor_after(position);
3794 while let Some(existing_state) = this.autoclose_regions.get(i) {
3795 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3796 Ordering::Less => i += 1,
3797 Ordering::Greater => break,
3798 Ordering::Equal => {
3799 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3800 Ordering::Less => i += 1,
3801 Ordering::Equal => break,
3802 Ordering::Greater => break,
3803 }
3804 }
3805 }
3806 }
3807 this.autoclose_regions.insert(
3808 i,
3809 AutocloseRegion {
3810 selection_id,
3811 range: start..end,
3812 pair,
3813 },
3814 );
3815 }
3816
3817 let had_active_inline_completion = this.has_active_inline_completion();
3818 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3819 s.select(new_selections)
3820 });
3821
3822 if !bracket_inserted {
3823 if let Some(on_type_format_task) =
3824 this.trigger_on_type_formatting(text.to_string(), window, cx)
3825 {
3826 on_type_format_task.detach_and_log_err(cx);
3827 }
3828 }
3829
3830 let editor_settings = EditorSettings::get_global(cx);
3831 if bracket_inserted
3832 && (editor_settings.auto_signature_help
3833 || editor_settings.show_signature_help_after_edits)
3834 {
3835 this.show_signature_help(&ShowSignatureHelp, window, cx);
3836 }
3837
3838 let trigger_in_words =
3839 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3840 if this.hard_wrap.is_some() {
3841 let latest: Range<Point> = this.selections.newest(cx).range();
3842 if latest.is_empty()
3843 && this
3844 .buffer()
3845 .read(cx)
3846 .snapshot(cx)
3847 .line_len(MultiBufferRow(latest.start.row))
3848 == latest.start.column
3849 {
3850 this.rewrap_impl(
3851 RewrapOptions {
3852 override_language_settings: true,
3853 preserve_existing_whitespace: true,
3854 },
3855 cx,
3856 )
3857 }
3858 }
3859 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3860 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3861 this.refresh_inline_completion(true, false, window, cx);
3862 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3863 });
3864 }
3865
3866 fn find_possible_emoji_shortcode_at_position(
3867 snapshot: &MultiBufferSnapshot,
3868 position: Point,
3869 ) -> Option<String> {
3870 let mut chars = Vec::new();
3871 let mut found_colon = false;
3872 for char in snapshot.reversed_chars_at(position).take(100) {
3873 // Found a possible emoji shortcode in the middle of the buffer
3874 if found_colon {
3875 if char.is_whitespace() {
3876 chars.reverse();
3877 return Some(chars.iter().collect());
3878 }
3879 // If the previous character is not a whitespace, we are in the middle of a word
3880 // and we only want to complete the shortcode if the word is made up of other emojis
3881 let mut containing_word = String::new();
3882 for ch in snapshot
3883 .reversed_chars_at(position)
3884 .skip(chars.len() + 1)
3885 .take(100)
3886 {
3887 if ch.is_whitespace() {
3888 break;
3889 }
3890 containing_word.push(ch);
3891 }
3892 let containing_word = containing_word.chars().rev().collect::<String>();
3893 if util::word_consists_of_emojis(containing_word.as_str()) {
3894 chars.reverse();
3895 return Some(chars.iter().collect());
3896 }
3897 }
3898
3899 if char.is_whitespace() || !char.is_ascii() {
3900 return None;
3901 }
3902 if char == ':' {
3903 found_colon = true;
3904 } else {
3905 chars.push(char);
3906 }
3907 }
3908 // Found a possible emoji shortcode at the beginning of the buffer
3909 chars.reverse();
3910 Some(chars.iter().collect())
3911 }
3912
3913 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3915 self.transact(window, cx, |this, window, cx| {
3916 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3917 let selections = this.selections.all::<usize>(cx);
3918 let multi_buffer = this.buffer.read(cx);
3919 let buffer = multi_buffer.snapshot(cx);
3920 selections
3921 .iter()
3922 .map(|selection| {
3923 let start_point = selection.start.to_point(&buffer);
3924 let mut existing_indent =
3925 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3926 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3927 let start = selection.start;
3928 let end = selection.end;
3929 let selection_is_empty = start == end;
3930 let language_scope = buffer.language_scope_at(start);
3931 let (
3932 comment_delimiter,
3933 doc_delimiter,
3934 insert_extra_newline,
3935 indent_on_newline,
3936 indent_on_extra_newline,
3937 ) = if let Some(language) = &language_scope {
3938 let mut insert_extra_newline =
3939 insert_extra_newline_brackets(&buffer, start..end, language)
3940 || insert_extra_newline_tree_sitter(&buffer, start..end);
3941
3942 // Comment extension on newline is allowed only for cursor selections
3943 let comment_delimiter = maybe!({
3944 if !selection_is_empty {
3945 return None;
3946 }
3947
3948 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3949 return None;
3950 }
3951
3952 let delimiters = language.line_comment_prefixes();
3953 let max_len_of_delimiter =
3954 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3955 let (snapshot, range) =
3956 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3957
3958 let num_of_whitespaces = snapshot
3959 .chars_for_range(range.clone())
3960 .take_while(|c| c.is_whitespace())
3961 .count();
3962 let comment_candidate = snapshot
3963 .chars_for_range(range)
3964 .skip(num_of_whitespaces)
3965 .take(max_len_of_delimiter)
3966 .collect::<String>();
3967 let (delimiter, trimmed_len) =
3968 delimiters.iter().find_map(|delimiter| {
3969 let trimmed = delimiter.trim_end();
3970 if comment_candidate.starts_with(trimmed) {
3971 Some((delimiter, trimmed.len()))
3972 } else {
3973 None
3974 }
3975 })?;
3976 let cursor_is_placed_after_comment_marker =
3977 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3978 if cursor_is_placed_after_comment_marker {
3979 Some(delimiter.clone())
3980 } else {
3981 None
3982 }
3983 });
3984
3985 let mut indent_on_newline = IndentSize::spaces(0);
3986 let mut indent_on_extra_newline = IndentSize::spaces(0);
3987
3988 let doc_delimiter = maybe!({
3989 if !selection_is_empty {
3990 return None;
3991 }
3992
3993 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3994 return None;
3995 }
3996
3997 let DocumentationConfig {
3998 start: start_tag,
3999 end: end_tag,
4000 prefix: delimiter,
4001 tab_size: len,
4002 } = language.documentation()?;
4003
4004 let (snapshot, range) =
4005 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4006
4007 let num_of_whitespaces = snapshot
4008 .chars_for_range(range.clone())
4009 .take_while(|c| c.is_whitespace())
4010 .count();
4011
4012 let cursor_is_after_start_tag = {
4013 let start_tag_len = start_tag.len();
4014 let start_tag_line = snapshot
4015 .chars_for_range(range.clone())
4016 .skip(num_of_whitespaces)
4017 .take(start_tag_len)
4018 .collect::<String>();
4019 if start_tag_line.starts_with(start_tag.as_ref()) {
4020 num_of_whitespaces + start_tag_len
4021 <= start_point.column as usize
4022 } else {
4023 false
4024 }
4025 };
4026
4027 let cursor_is_after_delimiter = {
4028 let delimiter_trim = delimiter.trim_end();
4029 let delimiter_line = snapshot
4030 .chars_for_range(range.clone())
4031 .skip(num_of_whitespaces)
4032 .take(delimiter_trim.len())
4033 .collect::<String>();
4034 if delimiter_line.starts_with(delimiter_trim) {
4035 num_of_whitespaces + delimiter_trim.len()
4036 <= start_point.column as usize
4037 } else {
4038 false
4039 }
4040 };
4041
4042 let cursor_is_before_end_tag_if_exists = {
4043 let num_of_whitespaces_rev = snapshot
4044 .reversed_chars_for_range(range.clone())
4045 .take_while(|c| c.is_whitespace())
4046 .count();
4047 let mut line_iter = snapshot
4048 .reversed_chars_for_range(range)
4049 .skip(num_of_whitespaces_rev);
4050 let end_tag_exists = end_tag
4051 .chars()
4052 .rev()
4053 .all(|char| line_iter.next() == Some(char));
4054 if end_tag_exists {
4055 let max_point = snapshot.line_len(start_point.row) as usize;
4056 let ordering = (num_of_whitespaces_rev
4057 + end_tag.len()
4058 + start_point.column as usize)
4059 .cmp(&max_point);
4060 let cursor_is_before_end_tag =
4061 ordering != Ordering::Greater;
4062 if cursor_is_after_start_tag {
4063 if cursor_is_before_end_tag {
4064 insert_extra_newline = true;
4065 }
4066 let cursor_is_at_start_of_end_tag =
4067 ordering == Ordering::Equal;
4068 if cursor_is_at_start_of_end_tag {
4069 indent_on_extra_newline.len = (*len).into();
4070 }
4071 }
4072 cursor_is_before_end_tag
4073 } else {
4074 true
4075 }
4076 };
4077
4078 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4079 && cursor_is_before_end_tag_if_exists
4080 {
4081 if cursor_is_after_start_tag {
4082 indent_on_newline.len = (*len).into();
4083 }
4084 Some(delimiter.clone())
4085 } else {
4086 None
4087 }
4088 });
4089
4090 (
4091 comment_delimiter,
4092 doc_delimiter,
4093 insert_extra_newline,
4094 indent_on_newline,
4095 indent_on_extra_newline,
4096 )
4097 } else {
4098 (
4099 None,
4100 None,
4101 false,
4102 IndentSize::default(),
4103 IndentSize::default(),
4104 )
4105 };
4106
4107 let prevent_auto_indent = doc_delimiter.is_some();
4108 let delimiter = comment_delimiter.or(doc_delimiter);
4109
4110 let capacity_for_delimiter =
4111 delimiter.as_deref().map(str::len).unwrap_or_default();
4112 let mut new_text = String::with_capacity(
4113 1 + capacity_for_delimiter
4114 + existing_indent.len as usize
4115 + indent_on_newline.len as usize
4116 + indent_on_extra_newline.len as usize,
4117 );
4118 new_text.push('\n');
4119 new_text.extend(existing_indent.chars());
4120 new_text.extend(indent_on_newline.chars());
4121
4122 if let Some(delimiter) = &delimiter {
4123 new_text.push_str(delimiter);
4124 }
4125
4126 if insert_extra_newline {
4127 new_text.push('\n');
4128 new_text.extend(existing_indent.chars());
4129 new_text.extend(indent_on_extra_newline.chars());
4130 }
4131
4132 let anchor = buffer.anchor_after(end);
4133 let new_selection = selection.map(|_| anchor);
4134 (
4135 ((start..end, new_text), prevent_auto_indent),
4136 (insert_extra_newline, new_selection),
4137 )
4138 })
4139 .unzip()
4140 };
4141
4142 let mut auto_indent_edits = Vec::new();
4143 let mut edits = Vec::new();
4144 for (edit, prevent_auto_indent) in edits_with_flags {
4145 if prevent_auto_indent {
4146 edits.push(edit);
4147 } else {
4148 auto_indent_edits.push(edit);
4149 }
4150 }
4151 if !edits.is_empty() {
4152 this.edit(edits, cx);
4153 }
4154 if !auto_indent_edits.is_empty() {
4155 this.edit_with_autoindent(auto_indent_edits, cx);
4156 }
4157
4158 let buffer = this.buffer.read(cx).snapshot(cx);
4159 let new_selections = selection_info
4160 .into_iter()
4161 .map(|(extra_newline_inserted, new_selection)| {
4162 let mut cursor = new_selection.end.to_point(&buffer);
4163 if extra_newline_inserted {
4164 cursor.row -= 1;
4165 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4166 }
4167 new_selection.map(|_| cursor)
4168 })
4169 .collect();
4170
4171 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4172 s.select(new_selections)
4173 });
4174 this.refresh_inline_completion(true, false, window, cx);
4175 });
4176 }
4177
4178 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4179 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4180
4181 let buffer = self.buffer.read(cx);
4182 let snapshot = buffer.snapshot(cx);
4183
4184 let mut edits = Vec::new();
4185 let mut rows = Vec::new();
4186
4187 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4188 let cursor = selection.head();
4189 let row = cursor.row;
4190
4191 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4192
4193 let newline = "\n".to_string();
4194 edits.push((start_of_line..start_of_line, newline));
4195
4196 rows.push(row + rows_inserted as u32);
4197 }
4198
4199 self.transact(window, cx, |editor, window, cx| {
4200 editor.edit(edits, cx);
4201
4202 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4203 let mut index = 0;
4204 s.move_cursors_with(|map, _, _| {
4205 let row = rows[index];
4206 index += 1;
4207
4208 let point = Point::new(row, 0);
4209 let boundary = map.next_line_boundary(point).1;
4210 let clipped = map.clip_point(boundary, Bias::Left);
4211
4212 (clipped, SelectionGoal::None)
4213 });
4214 });
4215
4216 let mut indent_edits = Vec::new();
4217 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4218 for row in rows {
4219 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4220 for (row, indent) in indents {
4221 if indent.len == 0 {
4222 continue;
4223 }
4224
4225 let text = match indent.kind {
4226 IndentKind::Space => " ".repeat(indent.len as usize),
4227 IndentKind::Tab => "\t".repeat(indent.len as usize),
4228 };
4229 let point = Point::new(row.0, 0);
4230 indent_edits.push((point..point, text));
4231 }
4232 }
4233 editor.edit(indent_edits, cx);
4234 });
4235 }
4236
4237 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4238 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4239
4240 let buffer = self.buffer.read(cx);
4241 let snapshot = buffer.snapshot(cx);
4242
4243 let mut edits = Vec::new();
4244 let mut rows = Vec::new();
4245 let mut rows_inserted = 0;
4246
4247 for selection in self.selections.all_adjusted(cx) {
4248 let cursor = selection.head();
4249 let row = cursor.row;
4250
4251 let point = Point::new(row + 1, 0);
4252 let start_of_line = snapshot.clip_point(point, Bias::Left);
4253
4254 let newline = "\n".to_string();
4255 edits.push((start_of_line..start_of_line, newline));
4256
4257 rows_inserted += 1;
4258 rows.push(row + rows_inserted);
4259 }
4260
4261 self.transact(window, cx, |editor, window, cx| {
4262 editor.edit(edits, cx);
4263
4264 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4265 let mut index = 0;
4266 s.move_cursors_with(|map, _, _| {
4267 let row = rows[index];
4268 index += 1;
4269
4270 let point = Point::new(row, 0);
4271 let boundary = map.next_line_boundary(point).1;
4272 let clipped = map.clip_point(boundary, Bias::Left);
4273
4274 (clipped, SelectionGoal::None)
4275 });
4276 });
4277
4278 let mut indent_edits = Vec::new();
4279 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4280 for row in rows {
4281 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4282 for (row, indent) in indents {
4283 if indent.len == 0 {
4284 continue;
4285 }
4286
4287 let text = match indent.kind {
4288 IndentKind::Space => " ".repeat(indent.len as usize),
4289 IndentKind::Tab => "\t".repeat(indent.len as usize),
4290 };
4291 let point = Point::new(row.0, 0);
4292 indent_edits.push((point..point, text));
4293 }
4294 }
4295 editor.edit(indent_edits, cx);
4296 });
4297 }
4298
4299 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4300 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4301 original_indent_columns: Vec::new(),
4302 });
4303 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4304 }
4305
4306 fn insert_with_autoindent_mode(
4307 &mut self,
4308 text: &str,
4309 autoindent_mode: Option<AutoindentMode>,
4310 window: &mut Window,
4311 cx: &mut Context<Self>,
4312 ) {
4313 if self.read_only(cx) {
4314 return;
4315 }
4316
4317 let text: Arc<str> = text.into();
4318 self.transact(window, cx, |this, window, cx| {
4319 let old_selections = this.selections.all_adjusted(cx);
4320 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4321 let anchors = {
4322 let snapshot = buffer.read(cx);
4323 old_selections
4324 .iter()
4325 .map(|s| {
4326 let anchor = snapshot.anchor_after(s.head());
4327 s.map(|_| anchor)
4328 })
4329 .collect::<Vec<_>>()
4330 };
4331 buffer.edit(
4332 old_selections
4333 .iter()
4334 .map(|s| (s.start..s.end, text.clone())),
4335 autoindent_mode,
4336 cx,
4337 );
4338 anchors
4339 });
4340
4341 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4342 s.select_anchors(selection_anchors);
4343 });
4344
4345 cx.notify();
4346 });
4347 }
4348
4349 fn trigger_completion_on_input(
4350 &mut self,
4351 text: &str,
4352 trigger_in_words: bool,
4353 window: &mut Window,
4354 cx: &mut Context<Self>,
4355 ) {
4356 let ignore_completion_provider = self
4357 .context_menu
4358 .borrow()
4359 .as_ref()
4360 .map(|menu| match menu {
4361 CodeContextMenu::Completions(completions_menu) => {
4362 completions_menu.ignore_completion_provider
4363 }
4364 CodeContextMenu::CodeActions(_) => false,
4365 })
4366 .unwrap_or(false);
4367
4368 if ignore_completion_provider {
4369 self.show_word_completions(&ShowWordCompletions, window, cx);
4370 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4371 self.show_completions(
4372 &ShowCompletions {
4373 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4374 },
4375 window,
4376 cx,
4377 );
4378 } else {
4379 self.hide_context_menu(window, cx);
4380 }
4381 }
4382
4383 fn is_completion_trigger(
4384 &self,
4385 text: &str,
4386 trigger_in_words: bool,
4387 cx: &mut Context<Self>,
4388 ) -> bool {
4389 let position = self.selections.newest_anchor().head();
4390 let multibuffer = self.buffer.read(cx);
4391 let Some(buffer) = position
4392 .buffer_id
4393 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4394 else {
4395 return false;
4396 };
4397
4398 if let Some(completion_provider) = &self.completion_provider {
4399 completion_provider.is_completion_trigger(
4400 &buffer,
4401 position.text_anchor,
4402 text,
4403 trigger_in_words,
4404 cx,
4405 )
4406 } else {
4407 false
4408 }
4409 }
4410
4411 /// If any empty selections is touching the start of its innermost containing autoclose
4412 /// region, expand it to select the brackets.
4413 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4414 let selections = self.selections.all::<usize>(cx);
4415 let buffer = self.buffer.read(cx).read(cx);
4416 let new_selections = self
4417 .selections_with_autoclose_regions(selections, &buffer)
4418 .map(|(mut selection, region)| {
4419 if !selection.is_empty() {
4420 return selection;
4421 }
4422
4423 if let Some(region) = region {
4424 let mut range = region.range.to_offset(&buffer);
4425 if selection.start == range.start && range.start >= region.pair.start.len() {
4426 range.start -= region.pair.start.len();
4427 if buffer.contains_str_at(range.start, ®ion.pair.start)
4428 && buffer.contains_str_at(range.end, ®ion.pair.end)
4429 {
4430 range.end += region.pair.end.len();
4431 selection.start = range.start;
4432 selection.end = range.end;
4433
4434 return selection;
4435 }
4436 }
4437 }
4438
4439 let always_treat_brackets_as_autoclosed = buffer
4440 .language_settings_at(selection.start, cx)
4441 .always_treat_brackets_as_autoclosed;
4442
4443 if !always_treat_brackets_as_autoclosed {
4444 return selection;
4445 }
4446
4447 if let Some(scope) = buffer.language_scope_at(selection.start) {
4448 for (pair, enabled) in scope.brackets() {
4449 if !enabled || !pair.close {
4450 continue;
4451 }
4452
4453 if buffer.contains_str_at(selection.start, &pair.end) {
4454 let pair_start_len = pair.start.len();
4455 if buffer.contains_str_at(
4456 selection.start.saturating_sub(pair_start_len),
4457 &pair.start,
4458 ) {
4459 selection.start -= pair_start_len;
4460 selection.end += pair.end.len();
4461
4462 return selection;
4463 }
4464 }
4465 }
4466 }
4467
4468 selection
4469 })
4470 .collect();
4471
4472 drop(buffer);
4473 self.change_selections(None, window, cx, |selections| {
4474 selections.select(new_selections)
4475 });
4476 }
4477
4478 /// Iterate the given selections, and for each one, find the smallest surrounding
4479 /// autoclose region. This uses the ordering of the selections and the autoclose
4480 /// regions to avoid repeated comparisons.
4481 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4482 &'a self,
4483 selections: impl IntoIterator<Item = Selection<D>>,
4484 buffer: &'a MultiBufferSnapshot,
4485 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4486 let mut i = 0;
4487 let mut regions = self.autoclose_regions.as_slice();
4488 selections.into_iter().map(move |selection| {
4489 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4490
4491 let mut enclosing = None;
4492 while let Some(pair_state) = regions.get(i) {
4493 if pair_state.range.end.to_offset(buffer) < range.start {
4494 regions = ®ions[i + 1..];
4495 i = 0;
4496 } else if pair_state.range.start.to_offset(buffer) > range.end {
4497 break;
4498 } else {
4499 if pair_state.selection_id == selection.id {
4500 enclosing = Some(pair_state);
4501 }
4502 i += 1;
4503 }
4504 }
4505
4506 (selection, enclosing)
4507 })
4508 }
4509
4510 /// Remove any autoclose regions that no longer contain their selection.
4511 fn invalidate_autoclose_regions(
4512 &mut self,
4513 mut selections: &[Selection<Anchor>],
4514 buffer: &MultiBufferSnapshot,
4515 ) {
4516 self.autoclose_regions.retain(|state| {
4517 let mut i = 0;
4518 while let Some(selection) = selections.get(i) {
4519 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4520 selections = &selections[1..];
4521 continue;
4522 }
4523 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4524 break;
4525 }
4526 if selection.id == state.selection_id {
4527 return true;
4528 } else {
4529 i += 1;
4530 }
4531 }
4532 false
4533 });
4534 }
4535
4536 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4537 let offset = position.to_offset(buffer);
4538 let (word_range, kind) = buffer.surrounding_word(offset, true);
4539 if offset > word_range.start && kind == Some(CharKind::Word) {
4540 Some(
4541 buffer
4542 .text_for_range(word_range.start..offset)
4543 .collect::<String>(),
4544 )
4545 } else {
4546 None
4547 }
4548 }
4549
4550 pub fn toggle_inline_values(
4551 &mut self,
4552 _: &ToggleInlineValues,
4553 _: &mut Window,
4554 cx: &mut Context<Self>,
4555 ) {
4556 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4557
4558 self.refresh_inline_values(cx);
4559 }
4560
4561 pub fn toggle_inlay_hints(
4562 &mut self,
4563 _: &ToggleInlayHints,
4564 _: &mut Window,
4565 cx: &mut Context<Self>,
4566 ) {
4567 self.refresh_inlay_hints(
4568 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4569 cx,
4570 );
4571 }
4572
4573 pub fn inlay_hints_enabled(&self) -> bool {
4574 self.inlay_hint_cache.enabled
4575 }
4576
4577 pub fn inline_values_enabled(&self) -> bool {
4578 self.inline_value_cache.enabled
4579 }
4580
4581 #[cfg(any(test, feature = "test-support"))]
4582 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4583 self.display_map
4584 .read(cx)
4585 .current_inlays()
4586 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4587 .cloned()
4588 .collect()
4589 }
4590
4591 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4592 if self.semantics_provider.is_none() || !self.mode.is_full() {
4593 return;
4594 }
4595
4596 let reason_description = reason.description();
4597 let ignore_debounce = matches!(
4598 reason,
4599 InlayHintRefreshReason::SettingsChange(_)
4600 | InlayHintRefreshReason::Toggle(_)
4601 | InlayHintRefreshReason::ExcerptsRemoved(_)
4602 | InlayHintRefreshReason::ModifiersChanged(_)
4603 );
4604 let (invalidate_cache, required_languages) = match reason {
4605 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4606 match self.inlay_hint_cache.modifiers_override(enabled) {
4607 Some(enabled) => {
4608 if enabled {
4609 (InvalidationStrategy::RefreshRequested, None)
4610 } else {
4611 self.splice_inlays(
4612 &self
4613 .visible_inlay_hints(cx)
4614 .iter()
4615 .map(|inlay| inlay.id)
4616 .collect::<Vec<InlayId>>(),
4617 Vec::new(),
4618 cx,
4619 );
4620 return;
4621 }
4622 }
4623 None => return,
4624 }
4625 }
4626 InlayHintRefreshReason::Toggle(enabled) => {
4627 if self.inlay_hint_cache.toggle(enabled) {
4628 if enabled {
4629 (InvalidationStrategy::RefreshRequested, None)
4630 } else {
4631 self.splice_inlays(
4632 &self
4633 .visible_inlay_hints(cx)
4634 .iter()
4635 .map(|inlay| inlay.id)
4636 .collect::<Vec<InlayId>>(),
4637 Vec::new(),
4638 cx,
4639 );
4640 return;
4641 }
4642 } else {
4643 return;
4644 }
4645 }
4646 InlayHintRefreshReason::SettingsChange(new_settings) => {
4647 match self.inlay_hint_cache.update_settings(
4648 &self.buffer,
4649 new_settings,
4650 self.visible_inlay_hints(cx),
4651 cx,
4652 ) {
4653 ControlFlow::Break(Some(InlaySplice {
4654 to_remove,
4655 to_insert,
4656 })) => {
4657 self.splice_inlays(&to_remove, to_insert, cx);
4658 return;
4659 }
4660 ControlFlow::Break(None) => return,
4661 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4662 }
4663 }
4664 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4665 if let Some(InlaySplice {
4666 to_remove,
4667 to_insert,
4668 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4669 {
4670 self.splice_inlays(&to_remove, to_insert, cx);
4671 }
4672 self.display_map.update(cx, |display_map, _| {
4673 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4674 });
4675 return;
4676 }
4677 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4678 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4679 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4680 }
4681 InlayHintRefreshReason::RefreshRequested => {
4682 (InvalidationStrategy::RefreshRequested, None)
4683 }
4684 };
4685
4686 if let Some(InlaySplice {
4687 to_remove,
4688 to_insert,
4689 }) = self.inlay_hint_cache.spawn_hint_refresh(
4690 reason_description,
4691 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4692 invalidate_cache,
4693 ignore_debounce,
4694 cx,
4695 ) {
4696 self.splice_inlays(&to_remove, to_insert, cx);
4697 }
4698 }
4699
4700 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4701 self.display_map
4702 .read(cx)
4703 .current_inlays()
4704 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4705 .cloned()
4706 .collect()
4707 }
4708
4709 pub fn excerpts_for_inlay_hints_query(
4710 &self,
4711 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4712 cx: &mut Context<Editor>,
4713 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4714 let Some(project) = self.project.as_ref() else {
4715 return HashMap::default();
4716 };
4717 let project = project.read(cx);
4718 let multi_buffer = self.buffer().read(cx);
4719 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4720 let multi_buffer_visible_start = self
4721 .scroll_manager
4722 .anchor()
4723 .anchor
4724 .to_point(&multi_buffer_snapshot);
4725 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4726 multi_buffer_visible_start
4727 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4728 Bias::Left,
4729 );
4730 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4731 multi_buffer_snapshot
4732 .range_to_buffer_ranges(multi_buffer_visible_range)
4733 .into_iter()
4734 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4735 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4736 let buffer_file = project::File::from_dyn(buffer.file())?;
4737 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4738 let worktree_entry = buffer_worktree
4739 .read(cx)
4740 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4741 if worktree_entry.is_ignored {
4742 return None;
4743 }
4744
4745 let language = buffer.language()?;
4746 if let Some(restrict_to_languages) = restrict_to_languages {
4747 if !restrict_to_languages.contains(language) {
4748 return None;
4749 }
4750 }
4751 Some((
4752 excerpt_id,
4753 (
4754 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4755 buffer.version().clone(),
4756 excerpt_visible_range,
4757 ),
4758 ))
4759 })
4760 .collect()
4761 }
4762
4763 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4764 TextLayoutDetails {
4765 text_system: window.text_system().clone(),
4766 editor_style: self.style.clone().unwrap(),
4767 rem_size: window.rem_size(),
4768 scroll_anchor: self.scroll_manager.anchor(),
4769 visible_rows: self.visible_line_count(),
4770 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4771 }
4772 }
4773
4774 pub fn splice_inlays(
4775 &self,
4776 to_remove: &[InlayId],
4777 to_insert: Vec<Inlay>,
4778 cx: &mut Context<Self>,
4779 ) {
4780 self.display_map.update(cx, |display_map, cx| {
4781 display_map.splice_inlays(to_remove, to_insert, cx)
4782 });
4783 cx.notify();
4784 }
4785
4786 fn trigger_on_type_formatting(
4787 &self,
4788 input: String,
4789 window: &mut Window,
4790 cx: &mut Context<Self>,
4791 ) -> Option<Task<Result<()>>> {
4792 if input.len() != 1 {
4793 return None;
4794 }
4795
4796 let project = self.project.as_ref()?;
4797 let position = self.selections.newest_anchor().head();
4798 let (buffer, buffer_position) = self
4799 .buffer
4800 .read(cx)
4801 .text_anchor_for_position(position, cx)?;
4802
4803 let settings = language_settings::language_settings(
4804 buffer
4805 .read(cx)
4806 .language_at(buffer_position)
4807 .map(|l| l.name()),
4808 buffer.read(cx).file(),
4809 cx,
4810 );
4811 if !settings.use_on_type_format {
4812 return None;
4813 }
4814
4815 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4816 // hence we do LSP request & edit on host side only — add formats to host's history.
4817 let push_to_lsp_host_history = true;
4818 // If this is not the host, append its history with new edits.
4819 let push_to_client_history = project.read(cx).is_via_collab();
4820
4821 let on_type_formatting = project.update(cx, |project, cx| {
4822 project.on_type_format(
4823 buffer.clone(),
4824 buffer_position,
4825 input,
4826 push_to_lsp_host_history,
4827 cx,
4828 )
4829 });
4830 Some(cx.spawn_in(window, async move |editor, cx| {
4831 if let Some(transaction) = on_type_formatting.await? {
4832 if push_to_client_history {
4833 buffer
4834 .update(cx, |buffer, _| {
4835 buffer.push_transaction(transaction, Instant::now());
4836 buffer.finalize_last_transaction();
4837 })
4838 .ok();
4839 }
4840 editor.update(cx, |editor, cx| {
4841 editor.refresh_document_highlights(cx);
4842 })?;
4843 }
4844 Ok(())
4845 }))
4846 }
4847
4848 pub fn show_word_completions(
4849 &mut self,
4850 _: &ShowWordCompletions,
4851 window: &mut Window,
4852 cx: &mut Context<Self>,
4853 ) {
4854 self.open_completions_menu(true, None, window, cx);
4855 }
4856
4857 pub fn show_completions(
4858 &mut self,
4859 options: &ShowCompletions,
4860 window: &mut Window,
4861 cx: &mut Context<Self>,
4862 ) {
4863 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4864 }
4865
4866 fn open_completions_menu(
4867 &mut self,
4868 ignore_completion_provider: bool,
4869 trigger: Option<&str>,
4870 window: &mut Window,
4871 cx: &mut Context<Self>,
4872 ) {
4873 if self.pending_rename.is_some() {
4874 return;
4875 }
4876 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4877 return;
4878 }
4879
4880 let position = self.selections.newest_anchor().head();
4881 if position.diff_base_anchor.is_some() {
4882 return;
4883 }
4884 let (buffer, buffer_position) =
4885 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4886 output
4887 } else {
4888 return;
4889 };
4890 let buffer_snapshot = buffer.read(cx).snapshot();
4891 let show_completion_documentation = buffer_snapshot
4892 .settings_at(buffer_position, cx)
4893 .show_completion_documentation;
4894
4895 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4896
4897 let trigger_kind = match trigger {
4898 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4899 CompletionTriggerKind::TRIGGER_CHARACTER
4900 }
4901 _ => CompletionTriggerKind::INVOKED,
4902 };
4903 let completion_context = CompletionContext {
4904 trigger_character: trigger.and_then(|trigger| {
4905 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4906 Some(String::from(trigger))
4907 } else {
4908 None
4909 }
4910 }),
4911 trigger_kind,
4912 };
4913
4914 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4915 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4916 let word_to_exclude = buffer_snapshot
4917 .text_for_range(old_range.clone())
4918 .collect::<String>();
4919 (
4920 buffer_snapshot.anchor_before(old_range.start)
4921 ..buffer_snapshot.anchor_after(old_range.end),
4922 Some(word_to_exclude),
4923 )
4924 } else {
4925 (buffer_position..buffer_position, None)
4926 };
4927
4928 let completion_settings = language_settings(
4929 buffer_snapshot
4930 .language_at(buffer_position)
4931 .map(|language| language.name()),
4932 buffer_snapshot.file(),
4933 cx,
4934 )
4935 .completions;
4936
4937 // The document can be large, so stay in reasonable bounds when searching for words,
4938 // otherwise completion pop-up might be slow to appear.
4939 const WORD_LOOKUP_ROWS: u32 = 5_000;
4940 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4941 let min_word_search = buffer_snapshot.clip_point(
4942 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4943 Bias::Left,
4944 );
4945 let max_word_search = buffer_snapshot.clip_point(
4946 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4947 Bias::Right,
4948 );
4949 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4950 ..buffer_snapshot.point_to_offset(max_word_search);
4951
4952 let provider = self
4953 .completion_provider
4954 .as_ref()
4955 .filter(|_| !ignore_completion_provider);
4956 let skip_digits = query
4957 .as_ref()
4958 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4959
4960 let (mut words, provided_completions) = match provider {
4961 Some(provider) => {
4962 let completions = provider.completions(
4963 position.excerpt_id,
4964 &buffer,
4965 buffer_position,
4966 completion_context,
4967 window,
4968 cx,
4969 );
4970
4971 let words = match completion_settings.words {
4972 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4973 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4974 .background_spawn(async move {
4975 buffer_snapshot.words_in_range(WordsQuery {
4976 fuzzy_contents: None,
4977 range: word_search_range,
4978 skip_digits,
4979 })
4980 }),
4981 };
4982
4983 (words, completions)
4984 }
4985 None => (
4986 cx.background_spawn(async move {
4987 buffer_snapshot.words_in_range(WordsQuery {
4988 fuzzy_contents: None,
4989 range: word_search_range,
4990 skip_digits,
4991 })
4992 }),
4993 Task::ready(Ok(None)),
4994 ),
4995 };
4996
4997 let sort_completions = provider
4998 .as_ref()
4999 .map_or(false, |provider| provider.sort_completions());
5000
5001 let filter_completions = provider
5002 .as_ref()
5003 .map_or(true, |provider| provider.filter_completions());
5004
5005 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5006
5007 let id = post_inc(&mut self.next_completion_id);
5008 let task = cx.spawn_in(window, async move |editor, cx| {
5009 async move {
5010 editor.update(cx, |this, _| {
5011 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5012 })?;
5013
5014 let mut completions = Vec::new();
5015 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5016 completions.extend(provided_completions);
5017 if completion_settings.words == WordsCompletionMode::Fallback {
5018 words = Task::ready(BTreeMap::default());
5019 }
5020 }
5021
5022 let mut words = words.await;
5023 if let Some(word_to_exclude) = &word_to_exclude {
5024 words.remove(word_to_exclude);
5025 }
5026 for lsp_completion in &completions {
5027 words.remove(&lsp_completion.new_text);
5028 }
5029 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5030 replace_range: old_range.clone(),
5031 new_text: word.clone(),
5032 label: CodeLabel::plain(word, None),
5033 icon_path: None,
5034 documentation: None,
5035 source: CompletionSource::BufferWord {
5036 word_range,
5037 resolved: false,
5038 },
5039 insert_text_mode: Some(InsertTextMode::AS_IS),
5040 confirm: None,
5041 }));
5042
5043 let menu = if completions.is_empty() {
5044 None
5045 } else {
5046 let mut menu = CompletionsMenu::new(
5047 id,
5048 sort_completions,
5049 show_completion_documentation,
5050 ignore_completion_provider,
5051 position,
5052 buffer.clone(),
5053 completions.into(),
5054 snippet_sort_order,
5055 );
5056
5057 menu.filter(
5058 if filter_completions {
5059 query.as_deref()
5060 } else {
5061 None
5062 },
5063 cx.background_executor().clone(),
5064 )
5065 .await;
5066
5067 menu.visible().then_some(menu)
5068 };
5069
5070 editor.update_in(cx, |editor, window, cx| {
5071 match editor.context_menu.borrow().as_ref() {
5072 None => {}
5073 Some(CodeContextMenu::Completions(prev_menu)) => {
5074 if prev_menu.id > id {
5075 return;
5076 }
5077 }
5078 _ => return,
5079 }
5080
5081 if editor.focus_handle.is_focused(window) && menu.is_some() {
5082 let mut menu = menu.unwrap();
5083 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5084 crate::hover_popover::hide_hover(editor, cx);
5085 *editor.context_menu.borrow_mut() =
5086 Some(CodeContextMenu::Completions(menu));
5087
5088 if editor.show_edit_predictions_in_menu() {
5089 editor.update_visible_inline_completion(window, cx);
5090 } else {
5091 editor.discard_inline_completion(false, cx);
5092 }
5093
5094 cx.notify();
5095 } else if editor.completion_tasks.len() <= 1 {
5096 // If there are no more completion tasks and the last menu was
5097 // empty, we should hide it.
5098 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5099 // If it was already hidden and we don't show inline
5100 // completions in the menu, we should also show the
5101 // inline-completion when available.
5102 if was_hidden && editor.show_edit_predictions_in_menu() {
5103 editor.update_visible_inline_completion(window, cx);
5104 }
5105 }
5106 })?;
5107
5108 anyhow::Ok(())
5109 }
5110 .log_err()
5111 .await
5112 });
5113
5114 self.completion_tasks.push((id, task));
5115 }
5116
5117 #[cfg(feature = "test-support")]
5118 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5119 let menu = self.context_menu.borrow();
5120 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5121 let completions = menu.completions.borrow();
5122 Some(completions.to_vec())
5123 } else {
5124 None
5125 }
5126 }
5127
5128 pub fn confirm_completion(
5129 &mut self,
5130 action: &ConfirmCompletion,
5131 window: &mut Window,
5132 cx: &mut Context<Self>,
5133 ) -> Option<Task<Result<()>>> {
5134 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5135 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5136 }
5137
5138 pub fn confirm_completion_insert(
5139 &mut self,
5140 _: &ConfirmCompletionInsert,
5141 window: &mut Window,
5142 cx: &mut Context<Self>,
5143 ) -> Option<Task<Result<()>>> {
5144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5145 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5146 }
5147
5148 pub fn confirm_completion_replace(
5149 &mut self,
5150 _: &ConfirmCompletionReplace,
5151 window: &mut Window,
5152 cx: &mut Context<Self>,
5153 ) -> Option<Task<Result<()>>> {
5154 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5155 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5156 }
5157
5158 pub fn compose_completion(
5159 &mut self,
5160 action: &ComposeCompletion,
5161 window: &mut Window,
5162 cx: &mut Context<Self>,
5163 ) -> Option<Task<Result<()>>> {
5164 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5165 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5166 }
5167
5168 fn do_completion(
5169 &mut self,
5170 item_ix: Option<usize>,
5171 intent: CompletionIntent,
5172 window: &mut Window,
5173 cx: &mut Context<Editor>,
5174 ) -> Option<Task<Result<()>>> {
5175 use language::ToOffset as _;
5176
5177 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5178 else {
5179 return None;
5180 };
5181
5182 let candidate_id = {
5183 let entries = completions_menu.entries.borrow();
5184 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5185 if self.show_edit_predictions_in_menu() {
5186 self.discard_inline_completion(true, cx);
5187 }
5188 mat.candidate_id
5189 };
5190
5191 let buffer_handle = completions_menu.buffer;
5192 let completion = completions_menu
5193 .completions
5194 .borrow()
5195 .get(candidate_id)?
5196 .clone();
5197 cx.stop_propagation();
5198
5199 let snapshot = self.buffer.read(cx).snapshot(cx);
5200 let newest_anchor = self.selections.newest_anchor();
5201
5202 let snippet;
5203 let new_text;
5204 if completion.is_snippet() {
5205 let mut snippet_source = completion.new_text.clone();
5206 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5207 if scope.prefers_label_for_snippet_in_completion() {
5208 if let Some(label) = completion.label() {
5209 if matches!(
5210 completion.kind(),
5211 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5212 ) {
5213 snippet_source = label;
5214 }
5215 }
5216 }
5217 }
5218 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5219 new_text = snippet.as_ref().unwrap().text.clone();
5220 } else {
5221 snippet = None;
5222 new_text = completion.new_text.clone();
5223 };
5224
5225 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5226 let buffer = buffer_handle.read(cx);
5227 let replace_range_multibuffer = {
5228 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5229 let multibuffer_anchor = snapshot
5230 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5231 .unwrap()
5232 ..snapshot
5233 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5234 .unwrap();
5235 multibuffer_anchor.start.to_offset(&snapshot)
5236 ..multibuffer_anchor.end.to_offset(&snapshot)
5237 };
5238 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5239 return None;
5240 }
5241
5242 let old_text = buffer
5243 .text_for_range(replace_range.clone())
5244 .collect::<String>();
5245 let lookbehind = newest_anchor
5246 .start
5247 .text_anchor
5248 .to_offset(buffer)
5249 .saturating_sub(replace_range.start);
5250 let lookahead = replace_range
5251 .end
5252 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5253 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5254 let suffix = &old_text[lookbehind.min(old_text.len())..];
5255
5256 let selections = self.selections.all::<usize>(cx);
5257 let mut ranges = Vec::new();
5258 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5259
5260 for selection in &selections {
5261 let range = if selection.id == newest_anchor.id {
5262 replace_range_multibuffer.clone()
5263 } else {
5264 let mut range = selection.range();
5265
5266 // if prefix is present, don't duplicate it
5267 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5268 range.start = range.start.saturating_sub(lookbehind);
5269
5270 // if suffix is also present, mimic the newest cursor and replace it
5271 if selection.id != newest_anchor.id
5272 && snapshot.contains_str_at(range.end, suffix)
5273 {
5274 range.end += lookahead;
5275 }
5276 }
5277 range
5278 };
5279
5280 ranges.push(range.clone());
5281
5282 if !self.linked_edit_ranges.is_empty() {
5283 let start_anchor = snapshot.anchor_before(range.start);
5284 let end_anchor = snapshot.anchor_after(range.end);
5285 if let Some(ranges) = self
5286 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5287 {
5288 for (buffer, edits) in ranges {
5289 linked_edits
5290 .entry(buffer.clone())
5291 .or_default()
5292 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5293 }
5294 }
5295 }
5296 }
5297
5298 cx.emit(EditorEvent::InputHandled {
5299 utf16_range_to_replace: None,
5300 text: new_text.clone().into(),
5301 });
5302
5303 self.transact(window, cx, |this, window, cx| {
5304 if let Some(mut snippet) = snippet {
5305 snippet.text = new_text.to_string();
5306 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5307 } else {
5308 this.buffer.update(cx, |buffer, cx| {
5309 let auto_indent = match completion.insert_text_mode {
5310 Some(InsertTextMode::AS_IS) => None,
5311 _ => this.autoindent_mode.clone(),
5312 };
5313 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5314 buffer.edit(edits, auto_indent, cx);
5315 });
5316 }
5317 for (buffer, edits) in linked_edits {
5318 buffer.update(cx, |buffer, cx| {
5319 let snapshot = buffer.snapshot();
5320 let edits = edits
5321 .into_iter()
5322 .map(|(range, text)| {
5323 use text::ToPoint as TP;
5324 let end_point = TP::to_point(&range.end, &snapshot);
5325 let start_point = TP::to_point(&range.start, &snapshot);
5326 (start_point..end_point, text)
5327 })
5328 .sorted_by_key(|(range, _)| range.start);
5329 buffer.edit(edits, None, cx);
5330 })
5331 }
5332
5333 this.refresh_inline_completion(true, false, window, cx);
5334 });
5335
5336 let show_new_completions_on_confirm = completion
5337 .confirm
5338 .as_ref()
5339 .map_or(false, |confirm| confirm(intent, window, cx));
5340 if show_new_completions_on_confirm {
5341 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5342 }
5343
5344 let provider = self.completion_provider.as_ref()?;
5345 drop(completion);
5346 let apply_edits = provider.apply_additional_edits_for_completion(
5347 buffer_handle,
5348 completions_menu.completions.clone(),
5349 candidate_id,
5350 true,
5351 cx,
5352 );
5353
5354 let editor_settings = EditorSettings::get_global(cx);
5355 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5356 // After the code completion is finished, users often want to know what signatures are needed.
5357 // so we should automatically call signature_help
5358 self.show_signature_help(&ShowSignatureHelp, window, cx);
5359 }
5360
5361 Some(cx.foreground_executor().spawn(async move {
5362 apply_edits.await?;
5363 Ok(())
5364 }))
5365 }
5366
5367 pub fn toggle_code_actions(
5368 &mut self,
5369 action: &ToggleCodeActions,
5370 window: &mut Window,
5371 cx: &mut Context<Self>,
5372 ) {
5373 let quick_launch = action.quick_launch;
5374 let mut context_menu = self.context_menu.borrow_mut();
5375 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5376 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5377 // Toggle if we're selecting the same one
5378 *context_menu = None;
5379 cx.notify();
5380 return;
5381 } else {
5382 // Otherwise, clear it and start a new one
5383 *context_menu = None;
5384 cx.notify();
5385 }
5386 }
5387 drop(context_menu);
5388 let snapshot = self.snapshot(window, cx);
5389 let deployed_from_indicator = action.deployed_from_indicator;
5390 let mut task = self.code_actions_task.take();
5391 let action = action.clone();
5392 cx.spawn_in(window, async move |editor, cx| {
5393 while let Some(prev_task) = task {
5394 prev_task.await.log_err();
5395 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5396 }
5397
5398 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5399 if editor.focus_handle.is_focused(window) {
5400 let multibuffer_point = action
5401 .deployed_from_indicator
5402 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5403 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5404 let (buffer, buffer_row) = snapshot
5405 .buffer_snapshot
5406 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5407 .and_then(|(buffer_snapshot, range)| {
5408 editor
5409 .buffer
5410 .read(cx)
5411 .buffer(buffer_snapshot.remote_id())
5412 .map(|buffer| (buffer, range.start.row))
5413 })?;
5414 let (_, code_actions) = editor
5415 .available_code_actions
5416 .clone()
5417 .and_then(|(location, code_actions)| {
5418 let snapshot = location.buffer.read(cx).snapshot();
5419 let point_range = location.range.to_point(&snapshot);
5420 let point_range = point_range.start.row..=point_range.end.row;
5421 if point_range.contains(&buffer_row) {
5422 Some((location, code_actions))
5423 } else {
5424 None
5425 }
5426 })
5427 .unzip();
5428 let buffer_id = buffer.read(cx).remote_id();
5429 let tasks = editor
5430 .tasks
5431 .get(&(buffer_id, buffer_row))
5432 .map(|t| Arc::new(t.to_owned()));
5433 if tasks.is_none() && code_actions.is_none() {
5434 return None;
5435 }
5436
5437 editor.completion_tasks.clear();
5438 editor.discard_inline_completion(false, cx);
5439 let task_context =
5440 tasks
5441 .as_ref()
5442 .zip(editor.project.clone())
5443 .map(|(tasks, project)| {
5444 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5445 });
5446
5447 Some(cx.spawn_in(window, async move |editor, cx| {
5448 let task_context = match task_context {
5449 Some(task_context) => task_context.await,
5450 None => None,
5451 };
5452 let resolved_tasks =
5453 tasks
5454 .zip(task_context.clone())
5455 .map(|(tasks, task_context)| ResolvedTasks {
5456 templates: tasks.resolve(&task_context).collect(),
5457 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5458 multibuffer_point.row,
5459 tasks.column,
5460 )),
5461 });
5462 let debug_scenarios = editor.update(cx, |editor, cx| {
5463 if cx.has_flag::<DebuggerFeatureFlag>() {
5464 maybe!({
5465 let project = editor.project.as_ref()?;
5466 let dap_store = project.read(cx).dap_store();
5467 let mut scenarios = vec![];
5468 let resolved_tasks = resolved_tasks.as_ref()?;
5469 let buffer = buffer.read(cx);
5470 let language = buffer.language()?;
5471 let file = buffer.file();
5472 let debug_adapter =
5473 language_settings(language.name().into(), file, cx)
5474 .debuggers
5475 .first()
5476 .map(SharedString::from)
5477 .or_else(|| {
5478 language
5479 .config()
5480 .debuggers
5481 .first()
5482 .map(SharedString::from)
5483 })?;
5484
5485 dap_store.update(cx, |dap_store, cx| {
5486 for (_, task) in &resolved_tasks.templates {
5487 if let Some(scenario) = dap_store
5488 .debug_scenario_for_build_task(
5489 task.original_task().clone(),
5490 debug_adapter.clone().into(),
5491 task.display_label().to_owned().into(),
5492 cx,
5493 )
5494 {
5495 scenarios.push(scenario);
5496 }
5497 }
5498 });
5499 Some(scenarios)
5500 })
5501 .unwrap_or_default()
5502 } else {
5503 vec![]
5504 }
5505 })?;
5506 let spawn_straight_away = quick_launch
5507 && resolved_tasks
5508 .as_ref()
5509 .map_or(false, |tasks| tasks.templates.len() == 1)
5510 && code_actions
5511 .as_ref()
5512 .map_or(true, |actions| actions.is_empty())
5513 && debug_scenarios.is_empty();
5514 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5515 crate::hover_popover::hide_hover(editor, cx);
5516 *editor.context_menu.borrow_mut() =
5517 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5518 buffer,
5519 actions: CodeActionContents::new(
5520 resolved_tasks,
5521 code_actions,
5522 debug_scenarios,
5523 task_context.unwrap_or_default(),
5524 ),
5525 selected_item: Default::default(),
5526 scroll_handle: UniformListScrollHandle::default(),
5527 deployed_from_indicator,
5528 }));
5529 if spawn_straight_away {
5530 if let Some(task) = editor.confirm_code_action(
5531 &ConfirmCodeAction { item_ix: Some(0) },
5532 window,
5533 cx,
5534 ) {
5535 cx.notify();
5536 return task;
5537 }
5538 }
5539 cx.notify();
5540 Task::ready(Ok(()))
5541 }) {
5542 task.await
5543 } else {
5544 Ok(())
5545 }
5546 }))
5547 } else {
5548 Some(Task::ready(Ok(())))
5549 }
5550 })?;
5551 if let Some(task) = spawned_test_task {
5552 task.await?;
5553 }
5554
5555 anyhow::Ok(())
5556 })
5557 .detach_and_log_err(cx);
5558 }
5559
5560 pub fn confirm_code_action(
5561 &mut self,
5562 action: &ConfirmCodeAction,
5563 window: &mut Window,
5564 cx: &mut Context<Self>,
5565 ) -> Option<Task<Result<()>>> {
5566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5567
5568 let actions_menu =
5569 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5570 menu
5571 } else {
5572 return None;
5573 };
5574
5575 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5576 let action = actions_menu.actions.get(action_ix)?;
5577 let title = action.label();
5578 let buffer = actions_menu.buffer;
5579 let workspace = self.workspace()?;
5580
5581 match action {
5582 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5583 workspace.update(cx, |workspace, cx| {
5584 workspace.schedule_resolved_task(
5585 task_source_kind,
5586 resolved_task,
5587 false,
5588 window,
5589 cx,
5590 );
5591
5592 Some(Task::ready(Ok(())))
5593 })
5594 }
5595 CodeActionsItem::CodeAction {
5596 excerpt_id,
5597 action,
5598 provider,
5599 } => {
5600 let apply_code_action =
5601 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5602 let workspace = workspace.downgrade();
5603 Some(cx.spawn_in(window, async move |editor, cx| {
5604 let project_transaction = apply_code_action.await?;
5605 Self::open_project_transaction(
5606 &editor,
5607 workspace,
5608 project_transaction,
5609 title,
5610 cx,
5611 )
5612 .await
5613 }))
5614 }
5615 CodeActionsItem::DebugScenario(scenario) => {
5616 let context = actions_menu.actions.context.clone();
5617
5618 workspace.update(cx, |workspace, cx| {
5619 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5620 });
5621 Some(Task::ready(Ok(())))
5622 }
5623 }
5624 }
5625
5626 pub async fn open_project_transaction(
5627 this: &WeakEntity<Editor>,
5628 workspace: WeakEntity<Workspace>,
5629 transaction: ProjectTransaction,
5630 title: String,
5631 cx: &mut AsyncWindowContext,
5632 ) -> Result<()> {
5633 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5634 cx.update(|_, cx| {
5635 entries.sort_unstable_by_key(|(buffer, _)| {
5636 buffer.read(cx).file().map(|f| f.path().clone())
5637 });
5638 })?;
5639
5640 // If the project transaction's edits are all contained within this editor, then
5641 // avoid opening a new editor to display them.
5642
5643 if let Some((buffer, transaction)) = entries.first() {
5644 if entries.len() == 1 {
5645 let excerpt = this.update(cx, |editor, cx| {
5646 editor
5647 .buffer()
5648 .read(cx)
5649 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5650 })?;
5651 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5652 if excerpted_buffer == *buffer {
5653 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5654 let excerpt_range = excerpt_range.to_offset(buffer);
5655 buffer
5656 .edited_ranges_for_transaction::<usize>(transaction)
5657 .all(|range| {
5658 excerpt_range.start <= range.start
5659 && excerpt_range.end >= range.end
5660 })
5661 })?;
5662
5663 if all_edits_within_excerpt {
5664 return Ok(());
5665 }
5666 }
5667 }
5668 }
5669 } else {
5670 return Ok(());
5671 }
5672
5673 let mut ranges_to_highlight = Vec::new();
5674 let excerpt_buffer = cx.new(|cx| {
5675 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5676 for (buffer_handle, transaction) in &entries {
5677 let edited_ranges = buffer_handle
5678 .read(cx)
5679 .edited_ranges_for_transaction::<Point>(transaction)
5680 .collect::<Vec<_>>();
5681 let (ranges, _) = multibuffer.set_excerpts_for_path(
5682 PathKey::for_buffer(buffer_handle, cx),
5683 buffer_handle.clone(),
5684 edited_ranges,
5685 DEFAULT_MULTIBUFFER_CONTEXT,
5686 cx,
5687 );
5688
5689 ranges_to_highlight.extend(ranges);
5690 }
5691 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5692 multibuffer
5693 })?;
5694
5695 workspace.update_in(cx, |workspace, window, cx| {
5696 let project = workspace.project().clone();
5697 let editor =
5698 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5699 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5700 editor.update(cx, |editor, cx| {
5701 editor.highlight_background::<Self>(
5702 &ranges_to_highlight,
5703 |theme| theme.editor_highlighted_line_background,
5704 cx,
5705 );
5706 });
5707 })?;
5708
5709 Ok(())
5710 }
5711
5712 pub fn clear_code_action_providers(&mut self) {
5713 self.code_action_providers.clear();
5714 self.available_code_actions.take();
5715 }
5716
5717 pub fn add_code_action_provider(
5718 &mut self,
5719 provider: Rc<dyn CodeActionProvider>,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) {
5723 if self
5724 .code_action_providers
5725 .iter()
5726 .any(|existing_provider| existing_provider.id() == provider.id())
5727 {
5728 return;
5729 }
5730
5731 self.code_action_providers.push(provider);
5732 self.refresh_code_actions(window, cx);
5733 }
5734
5735 pub fn remove_code_action_provider(
5736 &mut self,
5737 id: Arc<str>,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) {
5741 self.code_action_providers
5742 .retain(|provider| provider.id() != id);
5743 self.refresh_code_actions(window, cx);
5744 }
5745
5746 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5747 let newest_selection = self.selections.newest_anchor().clone();
5748 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5749 let buffer = self.buffer.read(cx);
5750 if newest_selection.head().diff_base_anchor.is_some() {
5751 return None;
5752 }
5753 let (start_buffer, start) =
5754 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5755 let (end_buffer, end) =
5756 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5757 if start_buffer != end_buffer {
5758 return None;
5759 }
5760
5761 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5762 cx.background_executor()
5763 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5764 .await;
5765
5766 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5767 let providers = this.code_action_providers.clone();
5768 let tasks = this
5769 .code_action_providers
5770 .iter()
5771 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5772 .collect::<Vec<_>>();
5773 (providers, tasks)
5774 })?;
5775
5776 let mut actions = Vec::new();
5777 for (provider, provider_actions) in
5778 providers.into_iter().zip(future::join_all(tasks).await)
5779 {
5780 if let Some(provider_actions) = provider_actions.log_err() {
5781 actions.extend(provider_actions.into_iter().map(|action| {
5782 AvailableCodeAction {
5783 excerpt_id: newest_selection.start.excerpt_id,
5784 action,
5785 provider: provider.clone(),
5786 }
5787 }));
5788 }
5789 }
5790
5791 this.update(cx, |this, cx| {
5792 this.available_code_actions = if actions.is_empty() {
5793 None
5794 } else {
5795 Some((
5796 Location {
5797 buffer: start_buffer,
5798 range: start..end,
5799 },
5800 actions.into(),
5801 ))
5802 };
5803 cx.notify();
5804 })
5805 }));
5806 None
5807 }
5808
5809 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5810 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5811 self.show_git_blame_inline = false;
5812
5813 self.show_git_blame_inline_delay_task =
5814 Some(cx.spawn_in(window, async move |this, cx| {
5815 cx.background_executor().timer(delay).await;
5816
5817 this.update(cx, |this, cx| {
5818 this.show_git_blame_inline = true;
5819 cx.notify();
5820 })
5821 .log_err();
5822 }));
5823 }
5824 }
5825
5826 fn show_blame_popover(
5827 &mut self,
5828 blame_entry: &BlameEntry,
5829 position: gpui::Point<Pixels>,
5830 cx: &mut Context<Self>,
5831 ) {
5832 if let Some(state) = &mut self.inline_blame_popover {
5833 state.hide_task.take();
5834 cx.notify();
5835 } else {
5836 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5837 let show_task = cx.spawn(async move |editor, cx| {
5838 cx.background_executor()
5839 .timer(std::time::Duration::from_millis(delay))
5840 .await;
5841 editor
5842 .update(cx, |editor, cx| {
5843 if let Some(state) = &mut editor.inline_blame_popover {
5844 state.show_task = None;
5845 cx.notify();
5846 }
5847 })
5848 .ok();
5849 });
5850 let Some(blame) = self.blame.as_ref() else {
5851 return;
5852 };
5853 let blame = blame.read(cx);
5854 let details = blame.details_for_entry(&blame_entry);
5855 let markdown = cx.new(|cx| {
5856 Markdown::new(
5857 details
5858 .as_ref()
5859 .map(|message| message.message.clone())
5860 .unwrap_or_default(),
5861 None,
5862 None,
5863 cx,
5864 )
5865 });
5866 self.inline_blame_popover = Some(InlineBlamePopover {
5867 position,
5868 show_task: Some(show_task),
5869 hide_task: None,
5870 popover_bounds: None,
5871 popover_state: InlineBlamePopoverState {
5872 scroll_handle: ScrollHandle::new(),
5873 commit_message: details,
5874 markdown,
5875 },
5876 });
5877 }
5878 }
5879
5880 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5881 if let Some(state) = &mut self.inline_blame_popover {
5882 if state.show_task.is_some() {
5883 self.inline_blame_popover.take();
5884 cx.notify();
5885 } else {
5886 let hide_task = cx.spawn(async move |editor, cx| {
5887 cx.background_executor()
5888 .timer(std::time::Duration::from_millis(100))
5889 .await;
5890 editor
5891 .update(cx, |editor, cx| {
5892 editor.inline_blame_popover.take();
5893 cx.notify();
5894 })
5895 .ok();
5896 });
5897 state.hide_task = Some(hide_task);
5898 }
5899 }
5900 }
5901
5902 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5903 if self.pending_rename.is_some() {
5904 return None;
5905 }
5906
5907 let provider = self.semantics_provider.clone()?;
5908 let buffer = self.buffer.read(cx);
5909 let newest_selection = self.selections.newest_anchor().clone();
5910 let cursor_position = newest_selection.head();
5911 let (cursor_buffer, cursor_buffer_position) =
5912 buffer.text_anchor_for_position(cursor_position, cx)?;
5913 let (tail_buffer, tail_buffer_position) =
5914 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5915 if cursor_buffer != tail_buffer {
5916 return None;
5917 }
5918
5919 let snapshot = cursor_buffer.read(cx).snapshot();
5920 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5921 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5922 if start_word_range != end_word_range {
5923 self.document_highlights_task.take();
5924 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5925 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5926 return None;
5927 }
5928
5929 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5930 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5931 cx.background_executor()
5932 .timer(Duration::from_millis(debounce))
5933 .await;
5934
5935 let highlights = if let Some(highlights) = cx
5936 .update(|cx| {
5937 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5938 })
5939 .ok()
5940 .flatten()
5941 {
5942 highlights.await.log_err()
5943 } else {
5944 None
5945 };
5946
5947 if let Some(highlights) = highlights {
5948 this.update(cx, |this, cx| {
5949 if this.pending_rename.is_some() {
5950 return;
5951 }
5952
5953 let buffer_id = cursor_position.buffer_id;
5954 let buffer = this.buffer.read(cx);
5955 if !buffer
5956 .text_anchor_for_position(cursor_position, cx)
5957 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5958 {
5959 return;
5960 }
5961
5962 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5963 let mut write_ranges = Vec::new();
5964 let mut read_ranges = Vec::new();
5965 for highlight in highlights {
5966 for (excerpt_id, excerpt_range) in
5967 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5968 {
5969 let start = highlight
5970 .range
5971 .start
5972 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5973 let end = highlight
5974 .range
5975 .end
5976 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5977 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5978 continue;
5979 }
5980
5981 let range = Anchor {
5982 buffer_id,
5983 excerpt_id,
5984 text_anchor: start,
5985 diff_base_anchor: None,
5986 }..Anchor {
5987 buffer_id,
5988 excerpt_id,
5989 text_anchor: end,
5990 diff_base_anchor: None,
5991 };
5992 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5993 write_ranges.push(range);
5994 } else {
5995 read_ranges.push(range);
5996 }
5997 }
5998 }
5999
6000 this.highlight_background::<DocumentHighlightRead>(
6001 &read_ranges,
6002 |theme| theme.editor_document_highlight_read_background,
6003 cx,
6004 );
6005 this.highlight_background::<DocumentHighlightWrite>(
6006 &write_ranges,
6007 |theme| theme.editor_document_highlight_write_background,
6008 cx,
6009 );
6010 cx.notify();
6011 })
6012 .log_err();
6013 }
6014 }));
6015 None
6016 }
6017
6018 fn prepare_highlight_query_from_selection(
6019 &mut self,
6020 cx: &mut Context<Editor>,
6021 ) -> Option<(String, Range<Anchor>)> {
6022 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6023 return None;
6024 }
6025 if !EditorSettings::get_global(cx).selection_highlight {
6026 return None;
6027 }
6028 if self.selections.count() != 1 || self.selections.line_mode {
6029 return None;
6030 }
6031 let selection = self.selections.newest::<Point>(cx);
6032 if selection.is_empty() || selection.start.row != selection.end.row {
6033 return None;
6034 }
6035 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6036 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6037 let query = multi_buffer_snapshot
6038 .text_for_range(selection_anchor_range.clone())
6039 .collect::<String>();
6040 if query.trim().is_empty() {
6041 return None;
6042 }
6043 Some((query, selection_anchor_range))
6044 }
6045
6046 fn update_selection_occurrence_highlights(
6047 &mut self,
6048 query_text: String,
6049 query_range: Range<Anchor>,
6050 multi_buffer_range_to_query: Range<Point>,
6051 use_debounce: bool,
6052 window: &mut Window,
6053 cx: &mut Context<Editor>,
6054 ) -> Task<()> {
6055 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6056 cx.spawn_in(window, async move |editor, cx| {
6057 if use_debounce {
6058 cx.background_executor()
6059 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6060 .await;
6061 }
6062 let match_task = cx.background_spawn(async move {
6063 let buffer_ranges = multi_buffer_snapshot
6064 .range_to_buffer_ranges(multi_buffer_range_to_query)
6065 .into_iter()
6066 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6067 let mut match_ranges = Vec::new();
6068 let Ok(regex) = project::search::SearchQuery::text(
6069 query_text.clone(),
6070 false,
6071 false,
6072 false,
6073 Default::default(),
6074 Default::default(),
6075 false,
6076 None,
6077 ) else {
6078 return Vec::default();
6079 };
6080 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6081 match_ranges.extend(
6082 regex
6083 .search(&buffer_snapshot, Some(search_range.clone()))
6084 .await
6085 .into_iter()
6086 .filter_map(|match_range| {
6087 let match_start = buffer_snapshot
6088 .anchor_after(search_range.start + match_range.start);
6089 let match_end = buffer_snapshot
6090 .anchor_before(search_range.start + match_range.end);
6091 let match_anchor_range = Anchor::range_in_buffer(
6092 excerpt_id,
6093 buffer_snapshot.remote_id(),
6094 match_start..match_end,
6095 );
6096 (match_anchor_range != query_range).then_some(match_anchor_range)
6097 }),
6098 );
6099 }
6100 match_ranges
6101 });
6102 let match_ranges = match_task.await;
6103 editor
6104 .update_in(cx, |editor, _, cx| {
6105 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6106 if !match_ranges.is_empty() {
6107 editor.highlight_background::<SelectedTextHighlight>(
6108 &match_ranges,
6109 |theme| theme.editor_document_highlight_bracket_background,
6110 cx,
6111 )
6112 }
6113 })
6114 .log_err();
6115 })
6116 }
6117
6118 fn refresh_selected_text_highlights(
6119 &mut self,
6120 on_buffer_edit: bool,
6121 window: &mut Window,
6122 cx: &mut Context<Editor>,
6123 ) {
6124 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6125 else {
6126 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6127 self.quick_selection_highlight_task.take();
6128 self.debounced_selection_highlight_task.take();
6129 return;
6130 };
6131 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6132 if on_buffer_edit
6133 || self
6134 .quick_selection_highlight_task
6135 .as_ref()
6136 .map_or(true, |(prev_anchor_range, _)| {
6137 prev_anchor_range != &query_range
6138 })
6139 {
6140 let multi_buffer_visible_start = self
6141 .scroll_manager
6142 .anchor()
6143 .anchor
6144 .to_point(&multi_buffer_snapshot);
6145 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6146 multi_buffer_visible_start
6147 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6148 Bias::Left,
6149 );
6150 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6151 self.quick_selection_highlight_task = Some((
6152 query_range.clone(),
6153 self.update_selection_occurrence_highlights(
6154 query_text.clone(),
6155 query_range.clone(),
6156 multi_buffer_visible_range,
6157 false,
6158 window,
6159 cx,
6160 ),
6161 ));
6162 }
6163 if on_buffer_edit
6164 || self
6165 .debounced_selection_highlight_task
6166 .as_ref()
6167 .map_or(true, |(prev_anchor_range, _)| {
6168 prev_anchor_range != &query_range
6169 })
6170 {
6171 let multi_buffer_start = multi_buffer_snapshot
6172 .anchor_before(0)
6173 .to_point(&multi_buffer_snapshot);
6174 let multi_buffer_end = multi_buffer_snapshot
6175 .anchor_after(multi_buffer_snapshot.len())
6176 .to_point(&multi_buffer_snapshot);
6177 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6178 self.debounced_selection_highlight_task = Some((
6179 query_range.clone(),
6180 self.update_selection_occurrence_highlights(
6181 query_text,
6182 query_range,
6183 multi_buffer_full_range,
6184 true,
6185 window,
6186 cx,
6187 ),
6188 ));
6189 }
6190 }
6191
6192 pub fn refresh_inline_completion(
6193 &mut self,
6194 debounce: bool,
6195 user_requested: bool,
6196 window: &mut Window,
6197 cx: &mut Context<Self>,
6198 ) -> Option<()> {
6199 let provider = self.edit_prediction_provider()?;
6200 let cursor = self.selections.newest_anchor().head();
6201 let (buffer, cursor_buffer_position) =
6202 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6203
6204 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6205 self.discard_inline_completion(false, cx);
6206 return None;
6207 }
6208
6209 if !user_requested
6210 && (!self.should_show_edit_predictions()
6211 || !self.is_focused(window)
6212 || buffer.read(cx).is_empty())
6213 {
6214 self.discard_inline_completion(false, cx);
6215 return None;
6216 }
6217
6218 self.update_visible_inline_completion(window, cx);
6219 provider.refresh(
6220 self.project.clone(),
6221 buffer,
6222 cursor_buffer_position,
6223 debounce,
6224 cx,
6225 );
6226 Some(())
6227 }
6228
6229 fn show_edit_predictions_in_menu(&self) -> bool {
6230 match self.edit_prediction_settings {
6231 EditPredictionSettings::Disabled => false,
6232 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6233 }
6234 }
6235
6236 pub fn edit_predictions_enabled(&self) -> bool {
6237 match self.edit_prediction_settings {
6238 EditPredictionSettings::Disabled => false,
6239 EditPredictionSettings::Enabled { .. } => true,
6240 }
6241 }
6242
6243 fn edit_prediction_requires_modifier(&self) -> bool {
6244 match self.edit_prediction_settings {
6245 EditPredictionSettings::Disabled => false,
6246 EditPredictionSettings::Enabled {
6247 preview_requires_modifier,
6248 ..
6249 } => preview_requires_modifier,
6250 }
6251 }
6252
6253 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6254 if self.edit_prediction_provider.is_none() {
6255 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6256 } else {
6257 let selection = self.selections.newest_anchor();
6258 let cursor = selection.head();
6259
6260 if let Some((buffer, cursor_buffer_position)) =
6261 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6262 {
6263 self.edit_prediction_settings =
6264 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6265 }
6266 }
6267 }
6268
6269 fn edit_prediction_settings_at_position(
6270 &self,
6271 buffer: &Entity<Buffer>,
6272 buffer_position: language::Anchor,
6273 cx: &App,
6274 ) -> EditPredictionSettings {
6275 if !self.mode.is_full()
6276 || !self.show_inline_completions_override.unwrap_or(true)
6277 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6278 {
6279 return EditPredictionSettings::Disabled;
6280 }
6281
6282 let buffer = buffer.read(cx);
6283
6284 let file = buffer.file();
6285
6286 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6287 return EditPredictionSettings::Disabled;
6288 };
6289
6290 let by_provider = matches!(
6291 self.menu_inline_completions_policy,
6292 MenuInlineCompletionsPolicy::ByProvider
6293 );
6294
6295 let show_in_menu = by_provider
6296 && self
6297 .edit_prediction_provider
6298 .as_ref()
6299 .map_or(false, |provider| {
6300 provider.provider.show_completions_in_menu()
6301 });
6302
6303 let preview_requires_modifier =
6304 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6305
6306 EditPredictionSettings::Enabled {
6307 show_in_menu,
6308 preview_requires_modifier,
6309 }
6310 }
6311
6312 fn should_show_edit_predictions(&self) -> bool {
6313 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6314 }
6315
6316 pub fn edit_prediction_preview_is_active(&self) -> bool {
6317 matches!(
6318 self.edit_prediction_preview,
6319 EditPredictionPreview::Active { .. }
6320 )
6321 }
6322
6323 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6324 let cursor = self.selections.newest_anchor().head();
6325 if let Some((buffer, cursor_position)) =
6326 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6327 {
6328 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6329 } else {
6330 false
6331 }
6332 }
6333
6334 pub fn supports_minimap(&self, cx: &App) -> bool {
6335 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6336 }
6337
6338 fn edit_predictions_enabled_in_buffer(
6339 &self,
6340 buffer: &Entity<Buffer>,
6341 buffer_position: language::Anchor,
6342 cx: &App,
6343 ) -> bool {
6344 maybe!({
6345 if self.read_only(cx) {
6346 return Some(false);
6347 }
6348 let provider = self.edit_prediction_provider()?;
6349 if !provider.is_enabled(&buffer, buffer_position, cx) {
6350 return Some(false);
6351 }
6352 let buffer = buffer.read(cx);
6353 let Some(file) = buffer.file() else {
6354 return Some(true);
6355 };
6356 let settings = all_language_settings(Some(file), cx);
6357 Some(settings.edit_predictions_enabled_for_file(file, cx))
6358 })
6359 .unwrap_or(false)
6360 }
6361
6362 fn cycle_inline_completion(
6363 &mut self,
6364 direction: Direction,
6365 window: &mut Window,
6366 cx: &mut Context<Self>,
6367 ) -> Option<()> {
6368 let provider = self.edit_prediction_provider()?;
6369 let cursor = self.selections.newest_anchor().head();
6370 let (buffer, cursor_buffer_position) =
6371 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6372 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6373 return None;
6374 }
6375
6376 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6377 self.update_visible_inline_completion(window, cx);
6378
6379 Some(())
6380 }
6381
6382 pub fn show_inline_completion(
6383 &mut self,
6384 _: &ShowEditPrediction,
6385 window: &mut Window,
6386 cx: &mut Context<Self>,
6387 ) {
6388 if !self.has_active_inline_completion() {
6389 self.refresh_inline_completion(false, true, window, cx);
6390 return;
6391 }
6392
6393 self.update_visible_inline_completion(window, cx);
6394 }
6395
6396 pub fn display_cursor_names(
6397 &mut self,
6398 _: &DisplayCursorNames,
6399 window: &mut Window,
6400 cx: &mut Context<Self>,
6401 ) {
6402 self.show_cursor_names(window, cx);
6403 }
6404
6405 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6406 self.show_cursor_names = true;
6407 cx.notify();
6408 cx.spawn_in(window, async move |this, cx| {
6409 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6410 this.update(cx, |this, cx| {
6411 this.show_cursor_names = false;
6412 cx.notify()
6413 })
6414 .ok()
6415 })
6416 .detach();
6417 }
6418
6419 pub fn next_edit_prediction(
6420 &mut self,
6421 _: &NextEditPrediction,
6422 window: &mut Window,
6423 cx: &mut Context<Self>,
6424 ) {
6425 if self.has_active_inline_completion() {
6426 self.cycle_inline_completion(Direction::Next, window, cx);
6427 } else {
6428 let is_copilot_disabled = self
6429 .refresh_inline_completion(false, true, window, cx)
6430 .is_none();
6431 if is_copilot_disabled {
6432 cx.propagate();
6433 }
6434 }
6435 }
6436
6437 pub fn previous_edit_prediction(
6438 &mut self,
6439 _: &PreviousEditPrediction,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) {
6443 if self.has_active_inline_completion() {
6444 self.cycle_inline_completion(Direction::Prev, window, cx);
6445 } else {
6446 let is_copilot_disabled = self
6447 .refresh_inline_completion(false, true, window, cx)
6448 .is_none();
6449 if is_copilot_disabled {
6450 cx.propagate();
6451 }
6452 }
6453 }
6454
6455 pub fn accept_edit_prediction(
6456 &mut self,
6457 _: &AcceptEditPrediction,
6458 window: &mut Window,
6459 cx: &mut Context<Self>,
6460 ) {
6461 if self.show_edit_predictions_in_menu() {
6462 self.hide_context_menu(window, cx);
6463 }
6464
6465 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6466 return;
6467 };
6468
6469 self.report_inline_completion_event(
6470 active_inline_completion.completion_id.clone(),
6471 true,
6472 cx,
6473 );
6474
6475 match &active_inline_completion.completion {
6476 InlineCompletion::Move { target, .. } => {
6477 let target = *target;
6478
6479 if let Some(position_map) = &self.last_position_map {
6480 if position_map
6481 .visible_row_range
6482 .contains(&target.to_display_point(&position_map.snapshot).row())
6483 || !self.edit_prediction_requires_modifier()
6484 {
6485 self.unfold_ranges(&[target..target], true, false, cx);
6486 // Note that this is also done in vim's handler of the Tab action.
6487 self.change_selections(
6488 Some(Autoscroll::newest()),
6489 window,
6490 cx,
6491 |selections| {
6492 selections.select_anchor_ranges([target..target]);
6493 },
6494 );
6495 self.clear_row_highlights::<EditPredictionPreview>();
6496
6497 self.edit_prediction_preview
6498 .set_previous_scroll_position(None);
6499 } else {
6500 self.edit_prediction_preview
6501 .set_previous_scroll_position(Some(
6502 position_map.snapshot.scroll_anchor,
6503 ));
6504
6505 self.highlight_rows::<EditPredictionPreview>(
6506 target..target,
6507 cx.theme().colors().editor_highlighted_line_background,
6508 RowHighlightOptions {
6509 autoscroll: true,
6510 ..Default::default()
6511 },
6512 cx,
6513 );
6514 self.request_autoscroll(Autoscroll::fit(), cx);
6515 }
6516 }
6517 }
6518 InlineCompletion::Edit { edits, .. } => {
6519 if let Some(provider) = self.edit_prediction_provider() {
6520 provider.accept(cx);
6521 }
6522
6523 let snapshot = self.buffer.read(cx).snapshot(cx);
6524 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6525
6526 self.buffer.update(cx, |buffer, cx| {
6527 buffer.edit(edits.iter().cloned(), None, cx)
6528 });
6529
6530 self.change_selections(None, window, cx, |s| {
6531 s.select_anchor_ranges([last_edit_end..last_edit_end])
6532 });
6533
6534 self.update_visible_inline_completion(window, cx);
6535 if self.active_inline_completion.is_none() {
6536 self.refresh_inline_completion(true, true, window, cx);
6537 }
6538
6539 cx.notify();
6540 }
6541 }
6542
6543 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6544 }
6545
6546 pub fn accept_partial_inline_completion(
6547 &mut self,
6548 _: &AcceptPartialEditPrediction,
6549 window: &mut Window,
6550 cx: &mut Context<Self>,
6551 ) {
6552 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6553 return;
6554 };
6555 if self.selections.count() != 1 {
6556 return;
6557 }
6558
6559 self.report_inline_completion_event(
6560 active_inline_completion.completion_id.clone(),
6561 true,
6562 cx,
6563 );
6564
6565 match &active_inline_completion.completion {
6566 InlineCompletion::Move { target, .. } => {
6567 let target = *target;
6568 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6569 selections.select_anchor_ranges([target..target]);
6570 });
6571 }
6572 InlineCompletion::Edit { edits, .. } => {
6573 // Find an insertion that starts at the cursor position.
6574 let snapshot = self.buffer.read(cx).snapshot(cx);
6575 let cursor_offset = self.selections.newest::<usize>(cx).head();
6576 let insertion = edits.iter().find_map(|(range, text)| {
6577 let range = range.to_offset(&snapshot);
6578 if range.is_empty() && range.start == cursor_offset {
6579 Some(text)
6580 } else {
6581 None
6582 }
6583 });
6584
6585 if let Some(text) = insertion {
6586 let mut partial_completion = text
6587 .chars()
6588 .by_ref()
6589 .take_while(|c| c.is_alphabetic())
6590 .collect::<String>();
6591 if partial_completion.is_empty() {
6592 partial_completion = text
6593 .chars()
6594 .by_ref()
6595 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6596 .collect::<String>();
6597 }
6598
6599 cx.emit(EditorEvent::InputHandled {
6600 utf16_range_to_replace: None,
6601 text: partial_completion.clone().into(),
6602 });
6603
6604 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6605
6606 self.refresh_inline_completion(true, true, window, cx);
6607 cx.notify();
6608 } else {
6609 self.accept_edit_prediction(&Default::default(), window, cx);
6610 }
6611 }
6612 }
6613 }
6614
6615 fn discard_inline_completion(
6616 &mut self,
6617 should_report_inline_completion_event: bool,
6618 cx: &mut Context<Self>,
6619 ) -> bool {
6620 if should_report_inline_completion_event {
6621 let completion_id = self
6622 .active_inline_completion
6623 .as_ref()
6624 .and_then(|active_completion| active_completion.completion_id.clone());
6625
6626 self.report_inline_completion_event(completion_id, false, cx);
6627 }
6628
6629 if let Some(provider) = self.edit_prediction_provider() {
6630 provider.discard(cx);
6631 }
6632
6633 self.take_active_inline_completion(cx)
6634 }
6635
6636 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6637 let Some(provider) = self.edit_prediction_provider() else {
6638 return;
6639 };
6640
6641 let Some((_, buffer, _)) = self
6642 .buffer
6643 .read(cx)
6644 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6645 else {
6646 return;
6647 };
6648
6649 let extension = buffer
6650 .read(cx)
6651 .file()
6652 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6653
6654 let event_type = match accepted {
6655 true => "Edit Prediction Accepted",
6656 false => "Edit Prediction Discarded",
6657 };
6658 telemetry::event!(
6659 event_type,
6660 provider = provider.name(),
6661 prediction_id = id,
6662 suggestion_accepted = accepted,
6663 file_extension = extension,
6664 );
6665 }
6666
6667 pub fn has_active_inline_completion(&self) -> bool {
6668 self.active_inline_completion.is_some()
6669 }
6670
6671 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6672 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6673 return false;
6674 };
6675
6676 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6677 self.clear_highlights::<InlineCompletionHighlight>(cx);
6678 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6679 true
6680 }
6681
6682 /// Returns true when we're displaying the edit prediction popover below the cursor
6683 /// like we are not previewing and the LSP autocomplete menu is visible
6684 /// or we are in `when_holding_modifier` mode.
6685 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6686 if self.edit_prediction_preview_is_active()
6687 || !self.show_edit_predictions_in_menu()
6688 || !self.edit_predictions_enabled()
6689 {
6690 return false;
6691 }
6692
6693 if self.has_visible_completions_menu() {
6694 return true;
6695 }
6696
6697 has_completion && self.edit_prediction_requires_modifier()
6698 }
6699
6700 fn handle_modifiers_changed(
6701 &mut self,
6702 modifiers: Modifiers,
6703 position_map: &PositionMap,
6704 window: &mut Window,
6705 cx: &mut Context<Self>,
6706 ) {
6707 if self.show_edit_predictions_in_menu() {
6708 self.update_edit_prediction_preview(&modifiers, window, cx);
6709 }
6710
6711 self.update_selection_mode(&modifiers, position_map, window, cx);
6712
6713 let mouse_position = window.mouse_position();
6714 if !position_map.text_hitbox.is_hovered(window) {
6715 return;
6716 }
6717
6718 self.update_hovered_link(
6719 position_map.point_for_position(mouse_position),
6720 &position_map.snapshot,
6721 modifiers,
6722 window,
6723 cx,
6724 )
6725 }
6726
6727 fn update_selection_mode(
6728 &mut self,
6729 modifiers: &Modifiers,
6730 position_map: &PositionMap,
6731 window: &mut Window,
6732 cx: &mut Context<Self>,
6733 ) {
6734 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6735 return;
6736 }
6737
6738 let mouse_position = window.mouse_position();
6739 let point_for_position = position_map.point_for_position(mouse_position);
6740 let position = point_for_position.previous_valid;
6741
6742 self.select(
6743 SelectPhase::BeginColumnar {
6744 position,
6745 reset: false,
6746 goal_column: point_for_position.exact_unclipped.column(),
6747 },
6748 window,
6749 cx,
6750 );
6751 }
6752
6753 fn update_edit_prediction_preview(
6754 &mut self,
6755 modifiers: &Modifiers,
6756 window: &mut Window,
6757 cx: &mut Context<Self>,
6758 ) {
6759 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6760 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6761 return;
6762 };
6763
6764 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6765 if matches!(
6766 self.edit_prediction_preview,
6767 EditPredictionPreview::Inactive { .. }
6768 ) {
6769 self.edit_prediction_preview = EditPredictionPreview::Active {
6770 previous_scroll_position: None,
6771 since: Instant::now(),
6772 };
6773
6774 self.update_visible_inline_completion(window, cx);
6775 cx.notify();
6776 }
6777 } else if let EditPredictionPreview::Active {
6778 previous_scroll_position,
6779 since,
6780 } = self.edit_prediction_preview
6781 {
6782 if let (Some(previous_scroll_position), Some(position_map)) =
6783 (previous_scroll_position, self.last_position_map.as_ref())
6784 {
6785 self.set_scroll_position(
6786 previous_scroll_position
6787 .scroll_position(&position_map.snapshot.display_snapshot),
6788 window,
6789 cx,
6790 );
6791 }
6792
6793 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6794 released_too_fast: since.elapsed() < Duration::from_millis(200),
6795 };
6796 self.clear_row_highlights::<EditPredictionPreview>();
6797 self.update_visible_inline_completion(window, cx);
6798 cx.notify();
6799 }
6800 }
6801
6802 fn update_visible_inline_completion(
6803 &mut self,
6804 _window: &mut Window,
6805 cx: &mut Context<Self>,
6806 ) -> Option<()> {
6807 let selection = self.selections.newest_anchor();
6808 let cursor = selection.head();
6809 let multibuffer = self.buffer.read(cx).snapshot(cx);
6810 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6811 let excerpt_id = cursor.excerpt_id;
6812
6813 let show_in_menu = self.show_edit_predictions_in_menu();
6814 let completions_menu_has_precedence = !show_in_menu
6815 && (self.context_menu.borrow().is_some()
6816 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6817
6818 if completions_menu_has_precedence
6819 || !offset_selection.is_empty()
6820 || self
6821 .active_inline_completion
6822 .as_ref()
6823 .map_or(false, |completion| {
6824 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6825 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6826 !invalidation_range.contains(&offset_selection.head())
6827 })
6828 {
6829 self.discard_inline_completion(false, cx);
6830 return None;
6831 }
6832
6833 self.take_active_inline_completion(cx);
6834 let Some(provider) = self.edit_prediction_provider() else {
6835 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6836 return None;
6837 };
6838
6839 let (buffer, cursor_buffer_position) =
6840 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6841
6842 self.edit_prediction_settings =
6843 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6844
6845 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6846
6847 if self.edit_prediction_indent_conflict {
6848 let cursor_point = cursor.to_point(&multibuffer);
6849
6850 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6851
6852 if let Some((_, indent)) = indents.iter().next() {
6853 if indent.len == cursor_point.column {
6854 self.edit_prediction_indent_conflict = false;
6855 }
6856 }
6857 }
6858
6859 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6860 let edits = inline_completion
6861 .edits
6862 .into_iter()
6863 .flat_map(|(range, new_text)| {
6864 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6865 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6866 Some((start..end, new_text))
6867 })
6868 .collect::<Vec<_>>();
6869 if edits.is_empty() {
6870 return None;
6871 }
6872
6873 let first_edit_start = edits.first().unwrap().0.start;
6874 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6875 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6876
6877 let last_edit_end = edits.last().unwrap().0.end;
6878 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6879 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6880
6881 let cursor_row = cursor.to_point(&multibuffer).row;
6882
6883 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6884
6885 let mut inlay_ids = Vec::new();
6886 let invalidation_row_range;
6887 let move_invalidation_row_range = if cursor_row < edit_start_row {
6888 Some(cursor_row..edit_end_row)
6889 } else if cursor_row > edit_end_row {
6890 Some(edit_start_row..cursor_row)
6891 } else {
6892 None
6893 };
6894 let is_move =
6895 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6896 let completion = if is_move {
6897 invalidation_row_range =
6898 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6899 let target = first_edit_start;
6900 InlineCompletion::Move { target, snapshot }
6901 } else {
6902 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6903 && !self.inline_completions_hidden_for_vim_mode;
6904
6905 if show_completions_in_buffer {
6906 if edits
6907 .iter()
6908 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6909 {
6910 let mut inlays = Vec::new();
6911 for (range, new_text) in &edits {
6912 let inlay = Inlay::inline_completion(
6913 post_inc(&mut self.next_inlay_id),
6914 range.start,
6915 new_text.as_str(),
6916 );
6917 inlay_ids.push(inlay.id);
6918 inlays.push(inlay);
6919 }
6920
6921 self.splice_inlays(&[], inlays, cx);
6922 } else {
6923 let background_color = cx.theme().status().deleted_background;
6924 self.highlight_text::<InlineCompletionHighlight>(
6925 edits.iter().map(|(range, _)| range.clone()).collect(),
6926 HighlightStyle {
6927 background_color: Some(background_color),
6928 ..Default::default()
6929 },
6930 cx,
6931 );
6932 }
6933 }
6934
6935 invalidation_row_range = edit_start_row..edit_end_row;
6936
6937 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6938 if provider.show_tab_accept_marker() {
6939 EditDisplayMode::TabAccept
6940 } else {
6941 EditDisplayMode::Inline
6942 }
6943 } else {
6944 EditDisplayMode::DiffPopover
6945 };
6946
6947 InlineCompletion::Edit {
6948 edits,
6949 edit_preview: inline_completion.edit_preview,
6950 display_mode,
6951 snapshot,
6952 }
6953 };
6954
6955 let invalidation_range = multibuffer
6956 .anchor_before(Point::new(invalidation_row_range.start, 0))
6957 ..multibuffer.anchor_after(Point::new(
6958 invalidation_row_range.end,
6959 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6960 ));
6961
6962 self.stale_inline_completion_in_menu = None;
6963 self.active_inline_completion = Some(InlineCompletionState {
6964 inlay_ids,
6965 completion,
6966 completion_id: inline_completion.id,
6967 invalidation_range,
6968 });
6969
6970 cx.notify();
6971
6972 Some(())
6973 }
6974
6975 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6976 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6977 }
6978
6979 fn clear_tasks(&mut self) {
6980 self.tasks.clear()
6981 }
6982
6983 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6984 if self.tasks.insert(key, value).is_some() {
6985 // This case should hopefully be rare, but just in case...
6986 log::error!(
6987 "multiple different run targets found on a single line, only the last target will be rendered"
6988 )
6989 }
6990 }
6991
6992 /// Get all display points of breakpoints that will be rendered within editor
6993 ///
6994 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6995 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6996 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6997 fn active_breakpoints(
6998 &self,
6999 range: Range<DisplayRow>,
7000 window: &mut Window,
7001 cx: &mut Context<Self>,
7002 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7003 let mut breakpoint_display_points = HashMap::default();
7004
7005 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7006 return breakpoint_display_points;
7007 };
7008
7009 let snapshot = self.snapshot(window, cx);
7010
7011 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7012 let Some(project) = self.project.as_ref() else {
7013 return breakpoint_display_points;
7014 };
7015
7016 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7017 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7018
7019 for (buffer_snapshot, range, excerpt_id) in
7020 multi_buffer_snapshot.range_to_buffer_ranges(range)
7021 {
7022 let Some(buffer) = project.read_with(cx, |this, cx| {
7023 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7024 }) else {
7025 continue;
7026 };
7027 let breakpoints = breakpoint_store.read(cx).breakpoints(
7028 &buffer,
7029 Some(
7030 buffer_snapshot.anchor_before(range.start)
7031 ..buffer_snapshot.anchor_after(range.end),
7032 ),
7033 buffer_snapshot,
7034 cx,
7035 );
7036 for (breakpoint, state) in breakpoints {
7037 let multi_buffer_anchor =
7038 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7039 let position = multi_buffer_anchor
7040 .to_point(&multi_buffer_snapshot)
7041 .to_display_point(&snapshot);
7042
7043 breakpoint_display_points.insert(
7044 position.row(),
7045 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7046 );
7047 }
7048 }
7049
7050 breakpoint_display_points
7051 }
7052
7053 fn breakpoint_context_menu(
7054 &self,
7055 anchor: Anchor,
7056 window: &mut Window,
7057 cx: &mut Context<Self>,
7058 ) -> Entity<ui::ContextMenu> {
7059 let weak_editor = cx.weak_entity();
7060 let focus_handle = self.focus_handle(cx);
7061
7062 let row = self
7063 .buffer
7064 .read(cx)
7065 .snapshot(cx)
7066 .summary_for_anchor::<Point>(&anchor)
7067 .row;
7068
7069 let breakpoint = self
7070 .breakpoint_at_row(row, window, cx)
7071 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7072
7073 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7074 "Edit Log Breakpoint"
7075 } else {
7076 "Set Log Breakpoint"
7077 };
7078
7079 let condition_breakpoint_msg = if breakpoint
7080 .as_ref()
7081 .is_some_and(|bp| bp.1.condition.is_some())
7082 {
7083 "Edit Condition Breakpoint"
7084 } else {
7085 "Set Condition Breakpoint"
7086 };
7087
7088 let hit_condition_breakpoint_msg = if breakpoint
7089 .as_ref()
7090 .is_some_and(|bp| bp.1.hit_condition.is_some())
7091 {
7092 "Edit Hit Condition Breakpoint"
7093 } else {
7094 "Set Hit Condition Breakpoint"
7095 };
7096
7097 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7098 "Unset Breakpoint"
7099 } else {
7100 "Set Breakpoint"
7101 };
7102
7103 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7104 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7105
7106 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7107 BreakpointState::Enabled => Some("Disable"),
7108 BreakpointState::Disabled => Some("Enable"),
7109 });
7110
7111 let (anchor, breakpoint) =
7112 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7113
7114 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7115 menu.on_blur_subscription(Subscription::new(|| {}))
7116 .context(focus_handle)
7117 .when(run_to_cursor, |this| {
7118 let weak_editor = weak_editor.clone();
7119 this.entry("Run to cursor", None, move |window, cx| {
7120 weak_editor
7121 .update(cx, |editor, cx| {
7122 editor.change_selections(None, window, cx, |s| {
7123 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7124 });
7125 })
7126 .ok();
7127
7128 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7129 })
7130 .separator()
7131 })
7132 .when_some(toggle_state_msg, |this, msg| {
7133 this.entry(msg, None, {
7134 let weak_editor = weak_editor.clone();
7135 let breakpoint = breakpoint.clone();
7136 move |_window, cx| {
7137 weak_editor
7138 .update(cx, |this, cx| {
7139 this.edit_breakpoint_at_anchor(
7140 anchor,
7141 breakpoint.as_ref().clone(),
7142 BreakpointEditAction::InvertState,
7143 cx,
7144 );
7145 })
7146 .log_err();
7147 }
7148 })
7149 })
7150 .entry(set_breakpoint_msg, None, {
7151 let weak_editor = weak_editor.clone();
7152 let breakpoint = breakpoint.clone();
7153 move |_window, cx| {
7154 weak_editor
7155 .update(cx, |this, cx| {
7156 this.edit_breakpoint_at_anchor(
7157 anchor,
7158 breakpoint.as_ref().clone(),
7159 BreakpointEditAction::Toggle,
7160 cx,
7161 );
7162 })
7163 .log_err();
7164 }
7165 })
7166 .entry(log_breakpoint_msg, None, {
7167 let breakpoint = breakpoint.clone();
7168 let weak_editor = weak_editor.clone();
7169 move |window, cx| {
7170 weak_editor
7171 .update(cx, |this, cx| {
7172 this.add_edit_breakpoint_block(
7173 anchor,
7174 breakpoint.as_ref(),
7175 BreakpointPromptEditAction::Log,
7176 window,
7177 cx,
7178 );
7179 })
7180 .log_err();
7181 }
7182 })
7183 .entry(condition_breakpoint_msg, None, {
7184 let breakpoint = breakpoint.clone();
7185 let weak_editor = weak_editor.clone();
7186 move |window, cx| {
7187 weak_editor
7188 .update(cx, |this, cx| {
7189 this.add_edit_breakpoint_block(
7190 anchor,
7191 breakpoint.as_ref(),
7192 BreakpointPromptEditAction::Condition,
7193 window,
7194 cx,
7195 );
7196 })
7197 .log_err();
7198 }
7199 })
7200 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7201 weak_editor
7202 .update(cx, |this, cx| {
7203 this.add_edit_breakpoint_block(
7204 anchor,
7205 breakpoint.as_ref(),
7206 BreakpointPromptEditAction::HitCondition,
7207 window,
7208 cx,
7209 );
7210 })
7211 .log_err();
7212 })
7213 })
7214 }
7215
7216 fn render_breakpoint(
7217 &self,
7218 position: Anchor,
7219 row: DisplayRow,
7220 breakpoint: &Breakpoint,
7221 state: Option<BreakpointSessionState>,
7222 cx: &mut Context<Self>,
7223 ) -> IconButton {
7224 let is_rejected = state.is_some_and(|s| !s.verified);
7225 // Is it a breakpoint that shows up when hovering over gutter?
7226 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7227 (false, false),
7228 |PhantomBreakpointIndicator {
7229 is_active,
7230 display_row,
7231 collides_with_existing_breakpoint,
7232 }| {
7233 (
7234 is_active && display_row == row,
7235 collides_with_existing_breakpoint,
7236 )
7237 },
7238 );
7239
7240 let (color, icon) = {
7241 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7242 (false, false) => ui::IconName::DebugBreakpoint,
7243 (true, false) => ui::IconName::DebugLogBreakpoint,
7244 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7245 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7246 };
7247
7248 let color = if is_phantom {
7249 Color::Hint
7250 } else if is_rejected {
7251 Color::Disabled
7252 } else {
7253 Color::Debugger
7254 };
7255
7256 (color, icon)
7257 };
7258
7259 let breakpoint = Arc::from(breakpoint.clone());
7260
7261 let alt_as_text = gpui::Keystroke {
7262 modifiers: Modifiers::secondary_key(),
7263 ..Default::default()
7264 };
7265 let primary_action_text = if breakpoint.is_disabled() {
7266 "Enable breakpoint"
7267 } else if is_phantom && !collides_with_existing {
7268 "Set breakpoint"
7269 } else {
7270 "Unset breakpoint"
7271 };
7272 let focus_handle = self.focus_handle.clone();
7273
7274 let meta = if is_rejected {
7275 SharedString::from("No executable code is associated with this line.")
7276 } else if collides_with_existing && !breakpoint.is_disabled() {
7277 SharedString::from(format!(
7278 "{alt_as_text}-click to disable,\nright-click for more options."
7279 ))
7280 } else {
7281 SharedString::from("Right-click for more options.")
7282 };
7283 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7284 .icon_size(IconSize::XSmall)
7285 .size(ui::ButtonSize::None)
7286 .when(is_rejected, |this| {
7287 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7288 })
7289 .icon_color(color)
7290 .style(ButtonStyle::Transparent)
7291 .on_click(cx.listener({
7292 let breakpoint = breakpoint.clone();
7293
7294 move |editor, event: &ClickEvent, window, cx| {
7295 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7296 BreakpointEditAction::InvertState
7297 } else {
7298 BreakpointEditAction::Toggle
7299 };
7300
7301 window.focus(&editor.focus_handle(cx));
7302 editor.edit_breakpoint_at_anchor(
7303 position,
7304 breakpoint.as_ref().clone(),
7305 edit_action,
7306 cx,
7307 );
7308 }
7309 }))
7310 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7311 editor.set_breakpoint_context_menu(
7312 row,
7313 Some(position),
7314 event.down.position,
7315 window,
7316 cx,
7317 );
7318 }))
7319 .tooltip(move |window, cx| {
7320 Tooltip::with_meta_in(
7321 primary_action_text,
7322 Some(&ToggleBreakpoint),
7323 meta.clone(),
7324 &focus_handle,
7325 window,
7326 cx,
7327 )
7328 })
7329 }
7330
7331 fn build_tasks_context(
7332 project: &Entity<Project>,
7333 buffer: &Entity<Buffer>,
7334 buffer_row: u32,
7335 tasks: &Arc<RunnableTasks>,
7336 cx: &mut Context<Self>,
7337 ) -> Task<Option<task::TaskContext>> {
7338 let position = Point::new(buffer_row, tasks.column);
7339 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7340 let location = Location {
7341 buffer: buffer.clone(),
7342 range: range_start..range_start,
7343 };
7344 // Fill in the environmental variables from the tree-sitter captures
7345 let mut captured_task_variables = TaskVariables::default();
7346 for (capture_name, value) in tasks.extra_variables.clone() {
7347 captured_task_variables.insert(
7348 task::VariableName::Custom(capture_name.into()),
7349 value.clone(),
7350 );
7351 }
7352 project.update(cx, |project, cx| {
7353 project.task_store().update(cx, |task_store, cx| {
7354 task_store.task_context_for_location(captured_task_variables, location, cx)
7355 })
7356 })
7357 }
7358
7359 pub fn spawn_nearest_task(
7360 &mut self,
7361 action: &SpawnNearestTask,
7362 window: &mut Window,
7363 cx: &mut Context<Self>,
7364 ) {
7365 let Some((workspace, _)) = self.workspace.clone() else {
7366 return;
7367 };
7368 let Some(project) = self.project.clone() else {
7369 return;
7370 };
7371
7372 // Try to find a closest, enclosing node using tree-sitter that has a
7373 // task
7374 let Some((buffer, buffer_row, tasks)) = self
7375 .find_enclosing_node_task(cx)
7376 // Or find the task that's closest in row-distance.
7377 .or_else(|| self.find_closest_task(cx))
7378 else {
7379 return;
7380 };
7381
7382 let reveal_strategy = action.reveal;
7383 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7384 cx.spawn_in(window, async move |_, cx| {
7385 let context = task_context.await?;
7386 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7387
7388 let resolved = &mut resolved_task.resolved;
7389 resolved.reveal = reveal_strategy;
7390
7391 workspace
7392 .update_in(cx, |workspace, window, cx| {
7393 workspace.schedule_resolved_task(
7394 task_source_kind,
7395 resolved_task,
7396 false,
7397 window,
7398 cx,
7399 );
7400 })
7401 .ok()
7402 })
7403 .detach();
7404 }
7405
7406 fn find_closest_task(
7407 &mut self,
7408 cx: &mut Context<Self>,
7409 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7410 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7411
7412 let ((buffer_id, row), tasks) = self
7413 .tasks
7414 .iter()
7415 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7416
7417 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7418 let tasks = Arc::new(tasks.to_owned());
7419 Some((buffer, *row, tasks))
7420 }
7421
7422 fn find_enclosing_node_task(
7423 &mut self,
7424 cx: &mut Context<Self>,
7425 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7426 let snapshot = self.buffer.read(cx).snapshot(cx);
7427 let offset = self.selections.newest::<usize>(cx).head();
7428 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7429 let buffer_id = excerpt.buffer().remote_id();
7430
7431 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7432 let mut cursor = layer.node().walk();
7433
7434 while cursor.goto_first_child_for_byte(offset).is_some() {
7435 if cursor.node().end_byte() == offset {
7436 cursor.goto_next_sibling();
7437 }
7438 }
7439
7440 // Ascend to the smallest ancestor that contains the range and has a task.
7441 loop {
7442 let node = cursor.node();
7443 let node_range = node.byte_range();
7444 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7445
7446 // Check if this node contains our offset
7447 if node_range.start <= offset && node_range.end >= offset {
7448 // If it contains offset, check for task
7449 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7450 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7451 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7452 }
7453 }
7454
7455 if !cursor.goto_parent() {
7456 break;
7457 }
7458 }
7459 None
7460 }
7461
7462 fn render_run_indicator(
7463 &self,
7464 _style: &EditorStyle,
7465 is_active: bool,
7466 row: DisplayRow,
7467 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7468 cx: &mut Context<Self>,
7469 ) -> IconButton {
7470 let color = Color::Muted;
7471 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7472
7473 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7474 .shape(ui::IconButtonShape::Square)
7475 .icon_size(IconSize::XSmall)
7476 .icon_color(color)
7477 .toggle_state(is_active)
7478 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7479 let quick_launch = e.down.button == MouseButton::Left;
7480 window.focus(&editor.focus_handle(cx));
7481 editor.toggle_code_actions(
7482 &ToggleCodeActions {
7483 deployed_from_indicator: Some(row),
7484 quick_launch,
7485 },
7486 window,
7487 cx,
7488 );
7489 }))
7490 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7491 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7492 }))
7493 }
7494
7495 pub fn context_menu_visible(&self) -> bool {
7496 !self.edit_prediction_preview_is_active()
7497 && self
7498 .context_menu
7499 .borrow()
7500 .as_ref()
7501 .map_or(false, |menu| menu.visible())
7502 }
7503
7504 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7505 self.context_menu
7506 .borrow()
7507 .as_ref()
7508 .map(|menu| menu.origin())
7509 }
7510
7511 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7512 self.context_menu_options = Some(options);
7513 }
7514
7515 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7516 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7517
7518 fn render_edit_prediction_popover(
7519 &mut self,
7520 text_bounds: &Bounds<Pixels>,
7521 content_origin: gpui::Point<Pixels>,
7522 right_margin: Pixels,
7523 editor_snapshot: &EditorSnapshot,
7524 visible_row_range: Range<DisplayRow>,
7525 scroll_top: f32,
7526 scroll_bottom: f32,
7527 line_layouts: &[LineWithInvisibles],
7528 line_height: Pixels,
7529 scroll_pixel_position: gpui::Point<Pixels>,
7530 newest_selection_head: Option<DisplayPoint>,
7531 editor_width: Pixels,
7532 style: &EditorStyle,
7533 window: &mut Window,
7534 cx: &mut App,
7535 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7536 if self.mode().is_minimap() {
7537 return None;
7538 }
7539 let active_inline_completion = self.active_inline_completion.as_ref()?;
7540
7541 if self.edit_prediction_visible_in_cursor_popover(true) {
7542 return None;
7543 }
7544
7545 match &active_inline_completion.completion {
7546 InlineCompletion::Move { target, .. } => {
7547 let target_display_point = target.to_display_point(editor_snapshot);
7548
7549 if self.edit_prediction_requires_modifier() {
7550 if !self.edit_prediction_preview_is_active() {
7551 return None;
7552 }
7553
7554 self.render_edit_prediction_modifier_jump_popover(
7555 text_bounds,
7556 content_origin,
7557 visible_row_range,
7558 line_layouts,
7559 line_height,
7560 scroll_pixel_position,
7561 newest_selection_head,
7562 target_display_point,
7563 window,
7564 cx,
7565 )
7566 } else {
7567 self.render_edit_prediction_eager_jump_popover(
7568 text_bounds,
7569 content_origin,
7570 editor_snapshot,
7571 visible_row_range,
7572 scroll_top,
7573 scroll_bottom,
7574 line_height,
7575 scroll_pixel_position,
7576 target_display_point,
7577 editor_width,
7578 window,
7579 cx,
7580 )
7581 }
7582 }
7583 InlineCompletion::Edit {
7584 display_mode: EditDisplayMode::Inline,
7585 ..
7586 } => None,
7587 InlineCompletion::Edit {
7588 display_mode: EditDisplayMode::TabAccept,
7589 edits,
7590 ..
7591 } => {
7592 let range = &edits.first()?.0;
7593 let target_display_point = range.end.to_display_point(editor_snapshot);
7594
7595 self.render_edit_prediction_end_of_line_popover(
7596 "Accept",
7597 editor_snapshot,
7598 visible_row_range,
7599 target_display_point,
7600 line_height,
7601 scroll_pixel_position,
7602 content_origin,
7603 editor_width,
7604 window,
7605 cx,
7606 )
7607 }
7608 InlineCompletion::Edit {
7609 edits,
7610 edit_preview,
7611 display_mode: EditDisplayMode::DiffPopover,
7612 snapshot,
7613 } => self.render_edit_prediction_diff_popover(
7614 text_bounds,
7615 content_origin,
7616 right_margin,
7617 editor_snapshot,
7618 visible_row_range,
7619 line_layouts,
7620 line_height,
7621 scroll_pixel_position,
7622 newest_selection_head,
7623 editor_width,
7624 style,
7625 edits,
7626 edit_preview,
7627 snapshot,
7628 window,
7629 cx,
7630 ),
7631 }
7632 }
7633
7634 fn render_edit_prediction_modifier_jump_popover(
7635 &mut self,
7636 text_bounds: &Bounds<Pixels>,
7637 content_origin: gpui::Point<Pixels>,
7638 visible_row_range: Range<DisplayRow>,
7639 line_layouts: &[LineWithInvisibles],
7640 line_height: Pixels,
7641 scroll_pixel_position: gpui::Point<Pixels>,
7642 newest_selection_head: Option<DisplayPoint>,
7643 target_display_point: DisplayPoint,
7644 window: &mut Window,
7645 cx: &mut App,
7646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7647 let scrolled_content_origin =
7648 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7649
7650 const SCROLL_PADDING_Y: Pixels = px(12.);
7651
7652 if target_display_point.row() < visible_row_range.start {
7653 return self.render_edit_prediction_scroll_popover(
7654 |_| SCROLL_PADDING_Y,
7655 IconName::ArrowUp,
7656 visible_row_range,
7657 line_layouts,
7658 newest_selection_head,
7659 scrolled_content_origin,
7660 window,
7661 cx,
7662 );
7663 } else if target_display_point.row() >= visible_row_range.end {
7664 return self.render_edit_prediction_scroll_popover(
7665 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7666 IconName::ArrowDown,
7667 visible_row_range,
7668 line_layouts,
7669 newest_selection_head,
7670 scrolled_content_origin,
7671 window,
7672 cx,
7673 );
7674 }
7675
7676 const POLE_WIDTH: Pixels = px(2.);
7677
7678 let line_layout =
7679 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7680 let target_column = target_display_point.column() as usize;
7681
7682 let target_x = line_layout.x_for_index(target_column);
7683 let target_y =
7684 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7685
7686 let flag_on_right = target_x < text_bounds.size.width / 2.;
7687
7688 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7689 border_color.l += 0.001;
7690
7691 let mut element = v_flex()
7692 .items_end()
7693 .when(flag_on_right, |el| el.items_start())
7694 .child(if flag_on_right {
7695 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7696 .rounded_bl(px(0.))
7697 .rounded_tl(px(0.))
7698 .border_l_2()
7699 .border_color(border_color)
7700 } else {
7701 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7702 .rounded_br(px(0.))
7703 .rounded_tr(px(0.))
7704 .border_r_2()
7705 .border_color(border_color)
7706 })
7707 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7708 .into_any();
7709
7710 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7711
7712 let mut origin = scrolled_content_origin + point(target_x, target_y)
7713 - point(
7714 if flag_on_right {
7715 POLE_WIDTH
7716 } else {
7717 size.width - POLE_WIDTH
7718 },
7719 size.height - line_height,
7720 );
7721
7722 origin.x = origin.x.max(content_origin.x);
7723
7724 element.prepaint_at(origin, window, cx);
7725
7726 Some((element, origin))
7727 }
7728
7729 fn render_edit_prediction_scroll_popover(
7730 &mut self,
7731 to_y: impl Fn(Size<Pixels>) -> Pixels,
7732 scroll_icon: IconName,
7733 visible_row_range: Range<DisplayRow>,
7734 line_layouts: &[LineWithInvisibles],
7735 newest_selection_head: Option<DisplayPoint>,
7736 scrolled_content_origin: gpui::Point<Pixels>,
7737 window: &mut Window,
7738 cx: &mut App,
7739 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7740 let mut element = self
7741 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7742 .into_any();
7743
7744 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7745
7746 let cursor = newest_selection_head?;
7747 let cursor_row_layout =
7748 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7749 let cursor_column = cursor.column() as usize;
7750
7751 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7752
7753 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7754
7755 element.prepaint_at(origin, window, cx);
7756 Some((element, origin))
7757 }
7758
7759 fn render_edit_prediction_eager_jump_popover(
7760 &mut self,
7761 text_bounds: &Bounds<Pixels>,
7762 content_origin: gpui::Point<Pixels>,
7763 editor_snapshot: &EditorSnapshot,
7764 visible_row_range: Range<DisplayRow>,
7765 scroll_top: f32,
7766 scroll_bottom: f32,
7767 line_height: Pixels,
7768 scroll_pixel_position: gpui::Point<Pixels>,
7769 target_display_point: DisplayPoint,
7770 editor_width: Pixels,
7771 window: &mut Window,
7772 cx: &mut App,
7773 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7774 if target_display_point.row().as_f32() < scroll_top {
7775 let mut element = self
7776 .render_edit_prediction_line_popover(
7777 "Jump to Edit",
7778 Some(IconName::ArrowUp),
7779 window,
7780 cx,
7781 )?
7782 .into_any();
7783
7784 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7785 let offset = point(
7786 (text_bounds.size.width - size.width) / 2.,
7787 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7788 );
7789
7790 let origin = text_bounds.origin + offset;
7791 element.prepaint_at(origin, window, cx);
7792 Some((element, origin))
7793 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7794 let mut element = self
7795 .render_edit_prediction_line_popover(
7796 "Jump to Edit",
7797 Some(IconName::ArrowDown),
7798 window,
7799 cx,
7800 )?
7801 .into_any();
7802
7803 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7804 let offset = point(
7805 (text_bounds.size.width - size.width) / 2.,
7806 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7807 );
7808
7809 let origin = text_bounds.origin + offset;
7810 element.prepaint_at(origin, window, cx);
7811 Some((element, origin))
7812 } else {
7813 self.render_edit_prediction_end_of_line_popover(
7814 "Jump to Edit",
7815 editor_snapshot,
7816 visible_row_range,
7817 target_display_point,
7818 line_height,
7819 scroll_pixel_position,
7820 content_origin,
7821 editor_width,
7822 window,
7823 cx,
7824 )
7825 }
7826 }
7827
7828 fn render_edit_prediction_end_of_line_popover(
7829 self: &mut Editor,
7830 label: &'static str,
7831 editor_snapshot: &EditorSnapshot,
7832 visible_row_range: Range<DisplayRow>,
7833 target_display_point: DisplayPoint,
7834 line_height: Pixels,
7835 scroll_pixel_position: gpui::Point<Pixels>,
7836 content_origin: gpui::Point<Pixels>,
7837 editor_width: Pixels,
7838 window: &mut Window,
7839 cx: &mut App,
7840 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7841 let target_line_end = DisplayPoint::new(
7842 target_display_point.row(),
7843 editor_snapshot.line_len(target_display_point.row()),
7844 );
7845
7846 let mut element = self
7847 .render_edit_prediction_line_popover(label, None, window, cx)?
7848 .into_any();
7849
7850 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7851
7852 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7853
7854 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7855 let mut origin = start_point
7856 + line_origin
7857 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7858 origin.x = origin.x.max(content_origin.x);
7859
7860 let max_x = content_origin.x + editor_width - size.width;
7861
7862 if origin.x > max_x {
7863 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7864
7865 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7866 origin.y += offset;
7867 IconName::ArrowUp
7868 } else {
7869 origin.y -= offset;
7870 IconName::ArrowDown
7871 };
7872
7873 element = self
7874 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7875 .into_any();
7876
7877 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7878
7879 origin.x = content_origin.x + editor_width - size.width - px(2.);
7880 }
7881
7882 element.prepaint_at(origin, window, cx);
7883 Some((element, origin))
7884 }
7885
7886 fn render_edit_prediction_diff_popover(
7887 self: &Editor,
7888 text_bounds: &Bounds<Pixels>,
7889 content_origin: gpui::Point<Pixels>,
7890 right_margin: Pixels,
7891 editor_snapshot: &EditorSnapshot,
7892 visible_row_range: Range<DisplayRow>,
7893 line_layouts: &[LineWithInvisibles],
7894 line_height: Pixels,
7895 scroll_pixel_position: gpui::Point<Pixels>,
7896 newest_selection_head: Option<DisplayPoint>,
7897 editor_width: Pixels,
7898 style: &EditorStyle,
7899 edits: &Vec<(Range<Anchor>, String)>,
7900 edit_preview: &Option<language::EditPreview>,
7901 snapshot: &language::BufferSnapshot,
7902 window: &mut Window,
7903 cx: &mut App,
7904 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7905 let edit_start = edits
7906 .first()
7907 .unwrap()
7908 .0
7909 .start
7910 .to_display_point(editor_snapshot);
7911 let edit_end = edits
7912 .last()
7913 .unwrap()
7914 .0
7915 .end
7916 .to_display_point(editor_snapshot);
7917
7918 let is_visible = visible_row_range.contains(&edit_start.row())
7919 || visible_row_range.contains(&edit_end.row());
7920 if !is_visible {
7921 return None;
7922 }
7923
7924 let highlighted_edits =
7925 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7926
7927 let styled_text = highlighted_edits.to_styled_text(&style.text);
7928 let line_count = highlighted_edits.text.lines().count();
7929
7930 const BORDER_WIDTH: Pixels = px(1.);
7931
7932 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7933 let has_keybind = keybind.is_some();
7934
7935 let mut element = h_flex()
7936 .items_start()
7937 .child(
7938 h_flex()
7939 .bg(cx.theme().colors().editor_background)
7940 .border(BORDER_WIDTH)
7941 .shadow_sm()
7942 .border_color(cx.theme().colors().border)
7943 .rounded_l_lg()
7944 .when(line_count > 1, |el| el.rounded_br_lg())
7945 .pr_1()
7946 .child(styled_text),
7947 )
7948 .child(
7949 h_flex()
7950 .h(line_height + BORDER_WIDTH * 2.)
7951 .px_1p5()
7952 .gap_1()
7953 // Workaround: For some reason, there's a gap if we don't do this
7954 .ml(-BORDER_WIDTH)
7955 .shadow(smallvec![gpui::BoxShadow {
7956 color: gpui::black().opacity(0.05),
7957 offset: point(px(1.), px(1.)),
7958 blur_radius: px(2.),
7959 spread_radius: px(0.),
7960 }])
7961 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7962 .border(BORDER_WIDTH)
7963 .border_color(cx.theme().colors().border)
7964 .rounded_r_lg()
7965 .id("edit_prediction_diff_popover_keybind")
7966 .when(!has_keybind, |el| {
7967 let status_colors = cx.theme().status();
7968
7969 el.bg(status_colors.error_background)
7970 .border_color(status_colors.error.opacity(0.6))
7971 .child(Icon::new(IconName::Info).color(Color::Error))
7972 .cursor_default()
7973 .hoverable_tooltip(move |_window, cx| {
7974 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7975 })
7976 })
7977 .children(keybind),
7978 )
7979 .into_any();
7980
7981 let longest_row =
7982 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7983 let longest_line_width = if visible_row_range.contains(&longest_row) {
7984 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7985 } else {
7986 layout_line(
7987 longest_row,
7988 editor_snapshot,
7989 style,
7990 editor_width,
7991 |_| false,
7992 window,
7993 cx,
7994 )
7995 .width
7996 };
7997
7998 let viewport_bounds =
7999 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8000 right: -right_margin,
8001 ..Default::default()
8002 });
8003
8004 let x_after_longest =
8005 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8006 - scroll_pixel_position.x;
8007
8008 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8009
8010 // Fully visible if it can be displayed within the window (allow overlapping other
8011 // panes). However, this is only allowed if the popover starts within text_bounds.
8012 let can_position_to_the_right = x_after_longest < text_bounds.right()
8013 && x_after_longest + element_bounds.width < viewport_bounds.right();
8014
8015 let mut origin = if can_position_to_the_right {
8016 point(
8017 x_after_longest,
8018 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8019 - scroll_pixel_position.y,
8020 )
8021 } else {
8022 let cursor_row = newest_selection_head.map(|head| head.row());
8023 let above_edit = edit_start
8024 .row()
8025 .0
8026 .checked_sub(line_count as u32)
8027 .map(DisplayRow);
8028 let below_edit = Some(edit_end.row() + 1);
8029 let above_cursor =
8030 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8031 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8032
8033 // Place the edit popover adjacent to the edit if there is a location
8034 // available that is onscreen and does not obscure the cursor. Otherwise,
8035 // place it adjacent to the cursor.
8036 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8037 .into_iter()
8038 .flatten()
8039 .find(|&start_row| {
8040 let end_row = start_row + line_count as u32;
8041 visible_row_range.contains(&start_row)
8042 && visible_row_range.contains(&end_row)
8043 && cursor_row.map_or(true, |cursor_row| {
8044 !((start_row..end_row).contains(&cursor_row))
8045 })
8046 })?;
8047
8048 content_origin
8049 + point(
8050 -scroll_pixel_position.x,
8051 row_target.as_f32() * line_height - scroll_pixel_position.y,
8052 )
8053 };
8054
8055 origin.x -= BORDER_WIDTH;
8056
8057 window.defer_draw(element, origin, 1);
8058
8059 // Do not return an element, since it will already be drawn due to defer_draw.
8060 None
8061 }
8062
8063 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8064 px(30.)
8065 }
8066
8067 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8068 if self.read_only(cx) {
8069 cx.theme().players().read_only()
8070 } else {
8071 self.style.as_ref().unwrap().local_player
8072 }
8073 }
8074
8075 fn render_edit_prediction_accept_keybind(
8076 &self,
8077 window: &mut Window,
8078 cx: &App,
8079 ) -> Option<AnyElement> {
8080 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8081 let accept_keystroke = accept_binding.keystroke()?;
8082
8083 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8084
8085 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8086 Color::Accent
8087 } else {
8088 Color::Muted
8089 };
8090
8091 h_flex()
8092 .px_0p5()
8093 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8094 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8095 .text_size(TextSize::XSmall.rems(cx))
8096 .child(h_flex().children(ui::render_modifiers(
8097 &accept_keystroke.modifiers,
8098 PlatformStyle::platform(),
8099 Some(modifiers_color),
8100 Some(IconSize::XSmall.rems().into()),
8101 true,
8102 )))
8103 .when(is_platform_style_mac, |parent| {
8104 parent.child(accept_keystroke.key.clone())
8105 })
8106 .when(!is_platform_style_mac, |parent| {
8107 parent.child(
8108 Key::new(
8109 util::capitalize(&accept_keystroke.key),
8110 Some(Color::Default),
8111 )
8112 .size(Some(IconSize::XSmall.rems().into())),
8113 )
8114 })
8115 .into_any()
8116 .into()
8117 }
8118
8119 fn render_edit_prediction_line_popover(
8120 &self,
8121 label: impl Into<SharedString>,
8122 icon: Option<IconName>,
8123 window: &mut Window,
8124 cx: &App,
8125 ) -> Option<Stateful<Div>> {
8126 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8127
8128 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8129 let has_keybind = keybind.is_some();
8130
8131 let result = h_flex()
8132 .id("ep-line-popover")
8133 .py_0p5()
8134 .pl_1()
8135 .pr(padding_right)
8136 .gap_1()
8137 .rounded_md()
8138 .border_1()
8139 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8140 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8141 .shadow_sm()
8142 .when(!has_keybind, |el| {
8143 let status_colors = cx.theme().status();
8144
8145 el.bg(status_colors.error_background)
8146 .border_color(status_colors.error.opacity(0.6))
8147 .pl_2()
8148 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8149 .cursor_default()
8150 .hoverable_tooltip(move |_window, cx| {
8151 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8152 })
8153 })
8154 .children(keybind)
8155 .child(
8156 Label::new(label)
8157 .size(LabelSize::Small)
8158 .when(!has_keybind, |el| {
8159 el.color(cx.theme().status().error.into()).strikethrough()
8160 }),
8161 )
8162 .when(!has_keybind, |el| {
8163 el.child(
8164 h_flex().ml_1().child(
8165 Icon::new(IconName::Info)
8166 .size(IconSize::Small)
8167 .color(cx.theme().status().error.into()),
8168 ),
8169 )
8170 })
8171 .when_some(icon, |element, icon| {
8172 element.child(
8173 div()
8174 .mt(px(1.5))
8175 .child(Icon::new(icon).size(IconSize::Small)),
8176 )
8177 });
8178
8179 Some(result)
8180 }
8181
8182 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8183 let accent_color = cx.theme().colors().text_accent;
8184 let editor_bg_color = cx.theme().colors().editor_background;
8185 editor_bg_color.blend(accent_color.opacity(0.1))
8186 }
8187
8188 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8189 let accent_color = cx.theme().colors().text_accent;
8190 let editor_bg_color = cx.theme().colors().editor_background;
8191 editor_bg_color.blend(accent_color.opacity(0.6))
8192 }
8193
8194 fn render_edit_prediction_cursor_popover(
8195 &self,
8196 min_width: Pixels,
8197 max_width: Pixels,
8198 cursor_point: Point,
8199 style: &EditorStyle,
8200 accept_keystroke: Option<&gpui::Keystroke>,
8201 _window: &Window,
8202 cx: &mut Context<Editor>,
8203 ) -> Option<AnyElement> {
8204 let provider = self.edit_prediction_provider.as_ref()?;
8205
8206 if provider.provider.needs_terms_acceptance(cx) {
8207 return Some(
8208 h_flex()
8209 .min_w(min_width)
8210 .flex_1()
8211 .px_2()
8212 .py_1()
8213 .gap_3()
8214 .elevation_2(cx)
8215 .hover(|style| style.bg(cx.theme().colors().element_hover))
8216 .id("accept-terms")
8217 .cursor_pointer()
8218 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8219 .on_click(cx.listener(|this, _event, window, cx| {
8220 cx.stop_propagation();
8221 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8222 window.dispatch_action(
8223 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8224 cx,
8225 );
8226 }))
8227 .child(
8228 h_flex()
8229 .flex_1()
8230 .gap_2()
8231 .child(Icon::new(IconName::ZedPredict))
8232 .child(Label::new("Accept Terms of Service"))
8233 .child(div().w_full())
8234 .child(
8235 Icon::new(IconName::ArrowUpRight)
8236 .color(Color::Muted)
8237 .size(IconSize::Small),
8238 )
8239 .into_any_element(),
8240 )
8241 .into_any(),
8242 );
8243 }
8244
8245 let is_refreshing = provider.provider.is_refreshing(cx);
8246
8247 fn pending_completion_container() -> Div {
8248 h_flex()
8249 .h_full()
8250 .flex_1()
8251 .gap_2()
8252 .child(Icon::new(IconName::ZedPredict))
8253 }
8254
8255 let completion = match &self.active_inline_completion {
8256 Some(prediction) => {
8257 if !self.has_visible_completions_menu() {
8258 const RADIUS: Pixels = px(6.);
8259 const BORDER_WIDTH: Pixels = px(1.);
8260
8261 return Some(
8262 h_flex()
8263 .elevation_2(cx)
8264 .border(BORDER_WIDTH)
8265 .border_color(cx.theme().colors().border)
8266 .when(accept_keystroke.is_none(), |el| {
8267 el.border_color(cx.theme().status().error)
8268 })
8269 .rounded(RADIUS)
8270 .rounded_tl(px(0.))
8271 .overflow_hidden()
8272 .child(div().px_1p5().child(match &prediction.completion {
8273 InlineCompletion::Move { target, snapshot } => {
8274 use text::ToPoint as _;
8275 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8276 {
8277 Icon::new(IconName::ZedPredictDown)
8278 } else {
8279 Icon::new(IconName::ZedPredictUp)
8280 }
8281 }
8282 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8283 }))
8284 .child(
8285 h_flex()
8286 .gap_1()
8287 .py_1()
8288 .px_2()
8289 .rounded_r(RADIUS - BORDER_WIDTH)
8290 .border_l_1()
8291 .border_color(cx.theme().colors().border)
8292 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8293 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8294 el.child(
8295 Label::new("Hold")
8296 .size(LabelSize::Small)
8297 .when(accept_keystroke.is_none(), |el| {
8298 el.strikethrough()
8299 })
8300 .line_height_style(LineHeightStyle::UiLabel),
8301 )
8302 })
8303 .id("edit_prediction_cursor_popover_keybind")
8304 .when(accept_keystroke.is_none(), |el| {
8305 let status_colors = cx.theme().status();
8306
8307 el.bg(status_colors.error_background)
8308 .border_color(status_colors.error.opacity(0.6))
8309 .child(Icon::new(IconName::Info).color(Color::Error))
8310 .cursor_default()
8311 .hoverable_tooltip(move |_window, cx| {
8312 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8313 .into()
8314 })
8315 })
8316 .when_some(
8317 accept_keystroke.as_ref(),
8318 |el, accept_keystroke| {
8319 el.child(h_flex().children(ui::render_modifiers(
8320 &accept_keystroke.modifiers,
8321 PlatformStyle::platform(),
8322 Some(Color::Default),
8323 Some(IconSize::XSmall.rems().into()),
8324 false,
8325 )))
8326 },
8327 ),
8328 )
8329 .into_any(),
8330 );
8331 }
8332
8333 self.render_edit_prediction_cursor_popover_preview(
8334 prediction,
8335 cursor_point,
8336 style,
8337 cx,
8338 )?
8339 }
8340
8341 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8342 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8343 stale_completion,
8344 cursor_point,
8345 style,
8346 cx,
8347 )?,
8348
8349 None => {
8350 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8351 }
8352 },
8353
8354 None => pending_completion_container().child(Label::new("No Prediction")),
8355 };
8356
8357 let completion = if is_refreshing {
8358 completion
8359 .with_animation(
8360 "loading-completion",
8361 Animation::new(Duration::from_secs(2))
8362 .repeat()
8363 .with_easing(pulsating_between(0.4, 0.8)),
8364 |label, delta| label.opacity(delta),
8365 )
8366 .into_any_element()
8367 } else {
8368 completion.into_any_element()
8369 };
8370
8371 let has_completion = self.active_inline_completion.is_some();
8372
8373 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8374 Some(
8375 h_flex()
8376 .min_w(min_width)
8377 .max_w(max_width)
8378 .flex_1()
8379 .elevation_2(cx)
8380 .border_color(cx.theme().colors().border)
8381 .child(
8382 div()
8383 .flex_1()
8384 .py_1()
8385 .px_2()
8386 .overflow_hidden()
8387 .child(completion),
8388 )
8389 .when_some(accept_keystroke, |el, accept_keystroke| {
8390 if !accept_keystroke.modifiers.modified() {
8391 return el;
8392 }
8393
8394 el.child(
8395 h_flex()
8396 .h_full()
8397 .border_l_1()
8398 .rounded_r_lg()
8399 .border_color(cx.theme().colors().border)
8400 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8401 .gap_1()
8402 .py_1()
8403 .px_2()
8404 .child(
8405 h_flex()
8406 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8407 .when(is_platform_style_mac, |parent| parent.gap_1())
8408 .child(h_flex().children(ui::render_modifiers(
8409 &accept_keystroke.modifiers,
8410 PlatformStyle::platform(),
8411 Some(if !has_completion {
8412 Color::Muted
8413 } else {
8414 Color::Default
8415 }),
8416 None,
8417 false,
8418 ))),
8419 )
8420 .child(Label::new("Preview").into_any_element())
8421 .opacity(if has_completion { 1.0 } else { 0.4 }),
8422 )
8423 })
8424 .into_any(),
8425 )
8426 }
8427
8428 fn render_edit_prediction_cursor_popover_preview(
8429 &self,
8430 completion: &InlineCompletionState,
8431 cursor_point: Point,
8432 style: &EditorStyle,
8433 cx: &mut Context<Editor>,
8434 ) -> Option<Div> {
8435 use text::ToPoint as _;
8436
8437 fn render_relative_row_jump(
8438 prefix: impl Into<String>,
8439 current_row: u32,
8440 target_row: u32,
8441 ) -> Div {
8442 let (row_diff, arrow) = if target_row < current_row {
8443 (current_row - target_row, IconName::ArrowUp)
8444 } else {
8445 (target_row - current_row, IconName::ArrowDown)
8446 };
8447
8448 h_flex()
8449 .child(
8450 Label::new(format!("{}{}", prefix.into(), row_diff))
8451 .color(Color::Muted)
8452 .size(LabelSize::Small),
8453 )
8454 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8455 }
8456
8457 match &completion.completion {
8458 InlineCompletion::Move {
8459 target, snapshot, ..
8460 } => Some(
8461 h_flex()
8462 .px_2()
8463 .gap_2()
8464 .flex_1()
8465 .child(
8466 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8467 Icon::new(IconName::ZedPredictDown)
8468 } else {
8469 Icon::new(IconName::ZedPredictUp)
8470 },
8471 )
8472 .child(Label::new("Jump to Edit")),
8473 ),
8474
8475 InlineCompletion::Edit {
8476 edits,
8477 edit_preview,
8478 snapshot,
8479 display_mode: _,
8480 } => {
8481 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8482
8483 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8484 &snapshot,
8485 &edits,
8486 edit_preview.as_ref()?,
8487 true,
8488 cx,
8489 )
8490 .first_line_preview();
8491
8492 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8493 .with_default_highlights(&style.text, highlighted_edits.highlights);
8494
8495 let preview = h_flex()
8496 .gap_1()
8497 .min_w_16()
8498 .child(styled_text)
8499 .when(has_more_lines, |parent| parent.child("…"));
8500
8501 let left = if first_edit_row != cursor_point.row {
8502 render_relative_row_jump("", cursor_point.row, first_edit_row)
8503 .into_any_element()
8504 } else {
8505 Icon::new(IconName::ZedPredict).into_any_element()
8506 };
8507
8508 Some(
8509 h_flex()
8510 .h_full()
8511 .flex_1()
8512 .gap_2()
8513 .pr_1()
8514 .overflow_x_hidden()
8515 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8516 .child(left)
8517 .child(preview),
8518 )
8519 }
8520 }
8521 }
8522
8523 fn render_context_menu(
8524 &self,
8525 style: &EditorStyle,
8526 max_height_in_lines: u32,
8527 window: &mut Window,
8528 cx: &mut Context<Editor>,
8529 ) -> Option<AnyElement> {
8530 let menu = self.context_menu.borrow();
8531 let menu = menu.as_ref()?;
8532 if !menu.visible() {
8533 return None;
8534 };
8535 Some(menu.render(style, max_height_in_lines, window, cx))
8536 }
8537
8538 fn render_context_menu_aside(
8539 &mut self,
8540 max_size: Size<Pixels>,
8541 window: &mut Window,
8542 cx: &mut Context<Editor>,
8543 ) -> Option<AnyElement> {
8544 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8545 if menu.visible() {
8546 menu.render_aside(self, max_size, window, cx)
8547 } else {
8548 None
8549 }
8550 })
8551 }
8552
8553 fn hide_context_menu(
8554 &mut self,
8555 window: &mut Window,
8556 cx: &mut Context<Self>,
8557 ) -> Option<CodeContextMenu> {
8558 cx.notify();
8559 self.completion_tasks.clear();
8560 let context_menu = self.context_menu.borrow_mut().take();
8561 self.stale_inline_completion_in_menu.take();
8562 self.update_visible_inline_completion(window, cx);
8563 context_menu
8564 }
8565
8566 fn show_snippet_choices(
8567 &mut self,
8568 choices: &Vec<String>,
8569 selection: Range<Anchor>,
8570 cx: &mut Context<Self>,
8571 ) {
8572 if selection.start.buffer_id.is_none() {
8573 return;
8574 }
8575 let buffer_id = selection.start.buffer_id.unwrap();
8576 let buffer = self.buffer().read(cx).buffer(buffer_id);
8577 let id = post_inc(&mut self.next_completion_id);
8578 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8579
8580 if let Some(buffer) = buffer {
8581 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8582 CompletionsMenu::new_snippet_choices(
8583 id,
8584 true,
8585 choices,
8586 selection,
8587 buffer,
8588 snippet_sort_order,
8589 ),
8590 ));
8591 }
8592 }
8593
8594 pub fn insert_snippet(
8595 &mut self,
8596 insertion_ranges: &[Range<usize>],
8597 snippet: Snippet,
8598 window: &mut Window,
8599 cx: &mut Context<Self>,
8600 ) -> Result<()> {
8601 struct Tabstop<T> {
8602 is_end_tabstop: bool,
8603 ranges: Vec<Range<T>>,
8604 choices: Option<Vec<String>>,
8605 }
8606
8607 let tabstops = self.buffer.update(cx, |buffer, cx| {
8608 let snippet_text: Arc<str> = snippet.text.clone().into();
8609 let edits = insertion_ranges
8610 .iter()
8611 .cloned()
8612 .map(|range| (range, snippet_text.clone()));
8613 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8614
8615 let snapshot = &*buffer.read(cx);
8616 let snippet = &snippet;
8617 snippet
8618 .tabstops
8619 .iter()
8620 .map(|tabstop| {
8621 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8622 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8623 });
8624 let mut tabstop_ranges = tabstop
8625 .ranges
8626 .iter()
8627 .flat_map(|tabstop_range| {
8628 let mut delta = 0_isize;
8629 insertion_ranges.iter().map(move |insertion_range| {
8630 let insertion_start = insertion_range.start as isize + delta;
8631 delta +=
8632 snippet.text.len() as isize - insertion_range.len() as isize;
8633
8634 let start = ((insertion_start + tabstop_range.start) as usize)
8635 .min(snapshot.len());
8636 let end = ((insertion_start + tabstop_range.end) as usize)
8637 .min(snapshot.len());
8638 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8639 })
8640 })
8641 .collect::<Vec<_>>();
8642 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8643
8644 Tabstop {
8645 is_end_tabstop,
8646 ranges: tabstop_ranges,
8647 choices: tabstop.choices.clone(),
8648 }
8649 })
8650 .collect::<Vec<_>>()
8651 });
8652 if let Some(tabstop) = tabstops.first() {
8653 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8654 s.select_ranges(tabstop.ranges.iter().cloned());
8655 });
8656
8657 if let Some(choices) = &tabstop.choices {
8658 if let Some(selection) = tabstop.ranges.first() {
8659 self.show_snippet_choices(choices, selection.clone(), cx)
8660 }
8661 }
8662
8663 // If we're already at the last tabstop and it's at the end of the snippet,
8664 // we're done, we don't need to keep the state around.
8665 if !tabstop.is_end_tabstop {
8666 let choices = tabstops
8667 .iter()
8668 .map(|tabstop| tabstop.choices.clone())
8669 .collect();
8670
8671 let ranges = tabstops
8672 .into_iter()
8673 .map(|tabstop| tabstop.ranges)
8674 .collect::<Vec<_>>();
8675
8676 self.snippet_stack.push(SnippetState {
8677 active_index: 0,
8678 ranges,
8679 choices,
8680 });
8681 }
8682
8683 // Check whether the just-entered snippet ends with an auto-closable bracket.
8684 if self.autoclose_regions.is_empty() {
8685 let snapshot = self.buffer.read(cx).snapshot(cx);
8686 for selection in &mut self.selections.all::<Point>(cx) {
8687 let selection_head = selection.head();
8688 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8689 continue;
8690 };
8691
8692 let mut bracket_pair = None;
8693 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8694 let prev_chars = snapshot
8695 .reversed_chars_at(selection_head)
8696 .collect::<String>();
8697 for (pair, enabled) in scope.brackets() {
8698 if enabled
8699 && pair.close
8700 && prev_chars.starts_with(pair.start.as_str())
8701 && next_chars.starts_with(pair.end.as_str())
8702 {
8703 bracket_pair = Some(pair.clone());
8704 break;
8705 }
8706 }
8707 if let Some(pair) = bracket_pair {
8708 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8709 let autoclose_enabled =
8710 self.use_autoclose && snapshot_settings.use_autoclose;
8711 if autoclose_enabled {
8712 let start = snapshot.anchor_after(selection_head);
8713 let end = snapshot.anchor_after(selection_head);
8714 self.autoclose_regions.push(AutocloseRegion {
8715 selection_id: selection.id,
8716 range: start..end,
8717 pair,
8718 });
8719 }
8720 }
8721 }
8722 }
8723 }
8724 Ok(())
8725 }
8726
8727 pub fn move_to_next_snippet_tabstop(
8728 &mut self,
8729 window: &mut Window,
8730 cx: &mut Context<Self>,
8731 ) -> bool {
8732 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8733 }
8734
8735 pub fn move_to_prev_snippet_tabstop(
8736 &mut self,
8737 window: &mut Window,
8738 cx: &mut Context<Self>,
8739 ) -> bool {
8740 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8741 }
8742
8743 pub fn move_to_snippet_tabstop(
8744 &mut self,
8745 bias: Bias,
8746 window: &mut Window,
8747 cx: &mut Context<Self>,
8748 ) -> bool {
8749 if let Some(mut snippet) = self.snippet_stack.pop() {
8750 match bias {
8751 Bias::Left => {
8752 if snippet.active_index > 0 {
8753 snippet.active_index -= 1;
8754 } else {
8755 self.snippet_stack.push(snippet);
8756 return false;
8757 }
8758 }
8759 Bias::Right => {
8760 if snippet.active_index + 1 < snippet.ranges.len() {
8761 snippet.active_index += 1;
8762 } else {
8763 self.snippet_stack.push(snippet);
8764 return false;
8765 }
8766 }
8767 }
8768 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8769 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8770 s.select_anchor_ranges(current_ranges.iter().cloned())
8771 });
8772
8773 if let Some(choices) = &snippet.choices[snippet.active_index] {
8774 if let Some(selection) = current_ranges.first() {
8775 self.show_snippet_choices(&choices, selection.clone(), cx);
8776 }
8777 }
8778
8779 // If snippet state is not at the last tabstop, push it back on the stack
8780 if snippet.active_index + 1 < snippet.ranges.len() {
8781 self.snippet_stack.push(snippet);
8782 }
8783 return true;
8784 }
8785 }
8786
8787 false
8788 }
8789
8790 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8791 self.transact(window, cx, |this, window, cx| {
8792 this.select_all(&SelectAll, window, cx);
8793 this.insert("", window, cx);
8794 });
8795 }
8796
8797 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8798 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8799 self.transact(window, cx, |this, window, cx| {
8800 this.select_autoclose_pair(window, cx);
8801 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8802 if !this.linked_edit_ranges.is_empty() {
8803 let selections = this.selections.all::<MultiBufferPoint>(cx);
8804 let snapshot = this.buffer.read(cx).snapshot(cx);
8805
8806 for selection in selections.iter() {
8807 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8808 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8809 if selection_start.buffer_id != selection_end.buffer_id {
8810 continue;
8811 }
8812 if let Some(ranges) =
8813 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8814 {
8815 for (buffer, entries) in ranges {
8816 linked_ranges.entry(buffer).or_default().extend(entries);
8817 }
8818 }
8819 }
8820 }
8821
8822 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8823 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8824 for selection in &mut selections {
8825 if selection.is_empty() {
8826 let old_head = selection.head();
8827 let mut new_head =
8828 movement::left(&display_map, old_head.to_display_point(&display_map))
8829 .to_point(&display_map);
8830 if let Some((buffer, line_buffer_range)) = display_map
8831 .buffer_snapshot
8832 .buffer_line_for_row(MultiBufferRow(old_head.row))
8833 {
8834 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8835 let indent_len = match indent_size.kind {
8836 IndentKind::Space => {
8837 buffer.settings_at(line_buffer_range.start, cx).tab_size
8838 }
8839 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8840 };
8841 if old_head.column <= indent_size.len && old_head.column > 0 {
8842 let indent_len = indent_len.get();
8843 new_head = cmp::min(
8844 new_head,
8845 MultiBufferPoint::new(
8846 old_head.row,
8847 ((old_head.column - 1) / indent_len) * indent_len,
8848 ),
8849 );
8850 }
8851 }
8852
8853 selection.set_head(new_head, SelectionGoal::None);
8854 }
8855 }
8856
8857 this.signature_help_state.set_backspace_pressed(true);
8858 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8859 s.select(selections)
8860 });
8861 this.insert("", window, cx);
8862 let empty_str: Arc<str> = Arc::from("");
8863 for (buffer, edits) in linked_ranges {
8864 let snapshot = buffer.read(cx).snapshot();
8865 use text::ToPoint as TP;
8866
8867 let edits = edits
8868 .into_iter()
8869 .map(|range| {
8870 let end_point = TP::to_point(&range.end, &snapshot);
8871 let mut start_point = TP::to_point(&range.start, &snapshot);
8872
8873 if end_point == start_point {
8874 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8875 .saturating_sub(1);
8876 start_point =
8877 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8878 };
8879
8880 (start_point..end_point, empty_str.clone())
8881 })
8882 .sorted_by_key(|(range, _)| range.start)
8883 .collect::<Vec<_>>();
8884 buffer.update(cx, |this, cx| {
8885 this.edit(edits, None, cx);
8886 })
8887 }
8888 this.refresh_inline_completion(true, false, window, cx);
8889 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8890 });
8891 }
8892
8893 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8894 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8895 self.transact(window, cx, |this, window, cx| {
8896 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8897 s.move_with(|map, selection| {
8898 if selection.is_empty() {
8899 let cursor = movement::right(map, selection.head());
8900 selection.end = cursor;
8901 selection.reversed = true;
8902 selection.goal = SelectionGoal::None;
8903 }
8904 })
8905 });
8906 this.insert("", window, cx);
8907 this.refresh_inline_completion(true, false, window, cx);
8908 });
8909 }
8910
8911 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8912 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8913 if self.move_to_prev_snippet_tabstop(window, cx) {
8914 return;
8915 }
8916 self.outdent(&Outdent, window, cx);
8917 }
8918
8919 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8920 if self.move_to_next_snippet_tabstop(window, cx) {
8921 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8922 return;
8923 }
8924 if self.read_only(cx) {
8925 return;
8926 }
8927 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8928 let mut selections = self.selections.all_adjusted(cx);
8929 let buffer = self.buffer.read(cx);
8930 let snapshot = buffer.snapshot(cx);
8931 let rows_iter = selections.iter().map(|s| s.head().row);
8932 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8933
8934 let has_some_cursor_in_whitespace = selections
8935 .iter()
8936 .filter(|selection| selection.is_empty())
8937 .any(|selection| {
8938 let cursor = selection.head();
8939 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8940 cursor.column < current_indent.len
8941 });
8942
8943 let mut edits = Vec::new();
8944 let mut prev_edited_row = 0;
8945 let mut row_delta = 0;
8946 for selection in &mut selections {
8947 if selection.start.row != prev_edited_row {
8948 row_delta = 0;
8949 }
8950 prev_edited_row = selection.end.row;
8951
8952 // If the selection is non-empty, then increase the indentation of the selected lines.
8953 if !selection.is_empty() {
8954 row_delta =
8955 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8956 continue;
8957 }
8958
8959 let cursor = selection.head();
8960 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8961 if let Some(suggested_indent) =
8962 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8963 {
8964 // Don't do anything if already at suggested indent
8965 // and there is any other cursor which is not
8966 if has_some_cursor_in_whitespace
8967 && cursor.column == current_indent.len
8968 && current_indent.len == suggested_indent.len
8969 {
8970 continue;
8971 }
8972
8973 // Adjust line and move cursor to suggested indent
8974 // if cursor is not at suggested indent
8975 if cursor.column < suggested_indent.len
8976 && cursor.column <= current_indent.len
8977 && current_indent.len <= suggested_indent.len
8978 {
8979 selection.start = Point::new(cursor.row, suggested_indent.len);
8980 selection.end = selection.start;
8981 if row_delta == 0 {
8982 edits.extend(Buffer::edit_for_indent_size_adjustment(
8983 cursor.row,
8984 current_indent,
8985 suggested_indent,
8986 ));
8987 row_delta = suggested_indent.len - current_indent.len;
8988 }
8989 continue;
8990 }
8991
8992 // If current indent is more than suggested indent
8993 // only move cursor to current indent and skip indent
8994 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8995 selection.start = Point::new(cursor.row, current_indent.len);
8996 selection.end = selection.start;
8997 continue;
8998 }
8999 }
9000
9001 // Otherwise, insert a hard or soft tab.
9002 let settings = buffer.language_settings_at(cursor, cx);
9003 let tab_size = if settings.hard_tabs {
9004 IndentSize::tab()
9005 } else {
9006 let tab_size = settings.tab_size.get();
9007 let indent_remainder = snapshot
9008 .text_for_range(Point::new(cursor.row, 0)..cursor)
9009 .flat_map(str::chars)
9010 .fold(row_delta % tab_size, |counter: u32, c| {
9011 if c == '\t' {
9012 0
9013 } else {
9014 (counter + 1) % tab_size
9015 }
9016 });
9017
9018 let chars_to_next_tab_stop = tab_size - indent_remainder;
9019 IndentSize::spaces(chars_to_next_tab_stop)
9020 };
9021 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9022 selection.end = selection.start;
9023 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9024 row_delta += tab_size.len;
9025 }
9026
9027 self.transact(window, cx, |this, window, cx| {
9028 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9029 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9030 s.select(selections)
9031 });
9032 this.refresh_inline_completion(true, false, window, cx);
9033 });
9034 }
9035
9036 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9037 if self.read_only(cx) {
9038 return;
9039 }
9040 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9041 let mut selections = self.selections.all::<Point>(cx);
9042 let mut prev_edited_row = 0;
9043 let mut row_delta = 0;
9044 let mut edits = Vec::new();
9045 let buffer = self.buffer.read(cx);
9046 let snapshot = buffer.snapshot(cx);
9047 for selection in &mut selections {
9048 if selection.start.row != prev_edited_row {
9049 row_delta = 0;
9050 }
9051 prev_edited_row = selection.end.row;
9052
9053 row_delta =
9054 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9055 }
9056
9057 self.transact(window, cx, |this, window, cx| {
9058 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9059 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9060 s.select(selections)
9061 });
9062 });
9063 }
9064
9065 fn indent_selection(
9066 buffer: &MultiBuffer,
9067 snapshot: &MultiBufferSnapshot,
9068 selection: &mut Selection<Point>,
9069 edits: &mut Vec<(Range<Point>, String)>,
9070 delta_for_start_row: u32,
9071 cx: &App,
9072 ) -> u32 {
9073 let settings = buffer.language_settings_at(selection.start, cx);
9074 let tab_size = settings.tab_size.get();
9075 let indent_kind = if settings.hard_tabs {
9076 IndentKind::Tab
9077 } else {
9078 IndentKind::Space
9079 };
9080 let mut start_row = selection.start.row;
9081 let mut end_row = selection.end.row + 1;
9082
9083 // If a selection ends at the beginning of a line, don't indent
9084 // that last line.
9085 if selection.end.column == 0 && selection.end.row > selection.start.row {
9086 end_row -= 1;
9087 }
9088
9089 // Avoid re-indenting a row that has already been indented by a
9090 // previous selection, but still update this selection's column
9091 // to reflect that indentation.
9092 if delta_for_start_row > 0 {
9093 start_row += 1;
9094 selection.start.column += delta_for_start_row;
9095 if selection.end.row == selection.start.row {
9096 selection.end.column += delta_for_start_row;
9097 }
9098 }
9099
9100 let mut delta_for_end_row = 0;
9101 let has_multiple_rows = start_row + 1 != end_row;
9102 for row in start_row..end_row {
9103 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9104 let indent_delta = match (current_indent.kind, indent_kind) {
9105 (IndentKind::Space, IndentKind::Space) => {
9106 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9107 IndentSize::spaces(columns_to_next_tab_stop)
9108 }
9109 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9110 (_, IndentKind::Tab) => IndentSize::tab(),
9111 };
9112
9113 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9114 0
9115 } else {
9116 selection.start.column
9117 };
9118 let row_start = Point::new(row, start);
9119 edits.push((
9120 row_start..row_start,
9121 indent_delta.chars().collect::<String>(),
9122 ));
9123
9124 // Update this selection's endpoints to reflect the indentation.
9125 if row == selection.start.row {
9126 selection.start.column += indent_delta.len;
9127 }
9128 if row == selection.end.row {
9129 selection.end.column += indent_delta.len;
9130 delta_for_end_row = indent_delta.len;
9131 }
9132 }
9133
9134 if selection.start.row == selection.end.row {
9135 delta_for_start_row + delta_for_end_row
9136 } else {
9137 delta_for_end_row
9138 }
9139 }
9140
9141 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9142 if self.read_only(cx) {
9143 return;
9144 }
9145 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9147 let selections = self.selections.all::<Point>(cx);
9148 let mut deletion_ranges = Vec::new();
9149 let mut last_outdent = None;
9150 {
9151 let buffer = self.buffer.read(cx);
9152 let snapshot = buffer.snapshot(cx);
9153 for selection in &selections {
9154 let settings = buffer.language_settings_at(selection.start, cx);
9155 let tab_size = settings.tab_size.get();
9156 let mut rows = selection.spanned_rows(false, &display_map);
9157
9158 // Avoid re-outdenting a row that has already been outdented by a
9159 // previous selection.
9160 if let Some(last_row) = last_outdent {
9161 if last_row == rows.start {
9162 rows.start = rows.start.next_row();
9163 }
9164 }
9165 let has_multiple_rows = rows.len() > 1;
9166 for row in rows.iter_rows() {
9167 let indent_size = snapshot.indent_size_for_line(row);
9168 if indent_size.len > 0 {
9169 let deletion_len = match indent_size.kind {
9170 IndentKind::Space => {
9171 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9172 if columns_to_prev_tab_stop == 0 {
9173 tab_size
9174 } else {
9175 columns_to_prev_tab_stop
9176 }
9177 }
9178 IndentKind::Tab => 1,
9179 };
9180 let start = if has_multiple_rows
9181 || deletion_len > selection.start.column
9182 || indent_size.len < selection.start.column
9183 {
9184 0
9185 } else {
9186 selection.start.column - deletion_len
9187 };
9188 deletion_ranges.push(
9189 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9190 );
9191 last_outdent = Some(row);
9192 }
9193 }
9194 }
9195 }
9196
9197 self.transact(window, cx, |this, window, cx| {
9198 this.buffer.update(cx, |buffer, cx| {
9199 let empty_str: Arc<str> = Arc::default();
9200 buffer.edit(
9201 deletion_ranges
9202 .into_iter()
9203 .map(|range| (range, empty_str.clone())),
9204 None,
9205 cx,
9206 );
9207 });
9208 let selections = this.selections.all::<usize>(cx);
9209 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9210 s.select(selections)
9211 });
9212 });
9213 }
9214
9215 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9216 if self.read_only(cx) {
9217 return;
9218 }
9219 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9220 let selections = self
9221 .selections
9222 .all::<usize>(cx)
9223 .into_iter()
9224 .map(|s| s.range());
9225
9226 self.transact(window, cx, |this, window, cx| {
9227 this.buffer.update(cx, |buffer, cx| {
9228 buffer.autoindent_ranges(selections, cx);
9229 });
9230 let selections = this.selections.all::<usize>(cx);
9231 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9232 s.select(selections)
9233 });
9234 });
9235 }
9236
9237 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9238 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9240 let selections = self.selections.all::<Point>(cx);
9241
9242 let mut new_cursors = Vec::new();
9243 let mut edit_ranges = Vec::new();
9244 let mut selections = selections.iter().peekable();
9245 while let Some(selection) = selections.next() {
9246 let mut rows = selection.spanned_rows(false, &display_map);
9247 let goal_display_column = selection.head().to_display_point(&display_map).column();
9248
9249 // Accumulate contiguous regions of rows that we want to delete.
9250 while let Some(next_selection) = selections.peek() {
9251 let next_rows = next_selection.spanned_rows(false, &display_map);
9252 if next_rows.start <= rows.end {
9253 rows.end = next_rows.end;
9254 selections.next().unwrap();
9255 } else {
9256 break;
9257 }
9258 }
9259
9260 let buffer = &display_map.buffer_snapshot;
9261 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9262 let edit_end;
9263 let cursor_buffer_row;
9264 if buffer.max_point().row >= rows.end.0 {
9265 // If there's a line after the range, delete the \n from the end of the row range
9266 // and position the cursor on the next line.
9267 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9268 cursor_buffer_row = rows.end;
9269 } else {
9270 // If there isn't a line after the range, delete the \n from the line before the
9271 // start of the row range and position the cursor there.
9272 edit_start = edit_start.saturating_sub(1);
9273 edit_end = buffer.len();
9274 cursor_buffer_row = rows.start.previous_row();
9275 }
9276
9277 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9278 *cursor.column_mut() =
9279 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9280
9281 new_cursors.push((
9282 selection.id,
9283 buffer.anchor_after(cursor.to_point(&display_map)),
9284 ));
9285 edit_ranges.push(edit_start..edit_end);
9286 }
9287
9288 self.transact(window, cx, |this, window, cx| {
9289 let buffer = this.buffer.update(cx, |buffer, cx| {
9290 let empty_str: Arc<str> = Arc::default();
9291 buffer.edit(
9292 edit_ranges
9293 .into_iter()
9294 .map(|range| (range, empty_str.clone())),
9295 None,
9296 cx,
9297 );
9298 buffer.snapshot(cx)
9299 });
9300 let new_selections = new_cursors
9301 .into_iter()
9302 .map(|(id, cursor)| {
9303 let cursor = cursor.to_point(&buffer);
9304 Selection {
9305 id,
9306 start: cursor,
9307 end: cursor,
9308 reversed: false,
9309 goal: SelectionGoal::None,
9310 }
9311 })
9312 .collect();
9313
9314 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9315 s.select(new_selections);
9316 });
9317 });
9318 }
9319
9320 pub fn join_lines_impl(
9321 &mut self,
9322 insert_whitespace: bool,
9323 window: &mut Window,
9324 cx: &mut Context<Self>,
9325 ) {
9326 if self.read_only(cx) {
9327 return;
9328 }
9329 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9330 for selection in self.selections.all::<Point>(cx) {
9331 let start = MultiBufferRow(selection.start.row);
9332 // Treat single line selections as if they include the next line. Otherwise this action
9333 // would do nothing for single line selections individual cursors.
9334 let end = if selection.start.row == selection.end.row {
9335 MultiBufferRow(selection.start.row + 1)
9336 } else {
9337 MultiBufferRow(selection.end.row)
9338 };
9339
9340 if let Some(last_row_range) = row_ranges.last_mut() {
9341 if start <= last_row_range.end {
9342 last_row_range.end = end;
9343 continue;
9344 }
9345 }
9346 row_ranges.push(start..end);
9347 }
9348
9349 let snapshot = self.buffer.read(cx).snapshot(cx);
9350 let mut cursor_positions = Vec::new();
9351 for row_range in &row_ranges {
9352 let anchor = snapshot.anchor_before(Point::new(
9353 row_range.end.previous_row().0,
9354 snapshot.line_len(row_range.end.previous_row()),
9355 ));
9356 cursor_positions.push(anchor..anchor);
9357 }
9358
9359 self.transact(window, cx, |this, window, cx| {
9360 for row_range in row_ranges.into_iter().rev() {
9361 for row in row_range.iter_rows().rev() {
9362 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9363 let next_line_row = row.next_row();
9364 let indent = snapshot.indent_size_for_line(next_line_row);
9365 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9366
9367 let replace =
9368 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9369 " "
9370 } else {
9371 ""
9372 };
9373
9374 this.buffer.update(cx, |buffer, cx| {
9375 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9376 });
9377 }
9378 }
9379
9380 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9381 s.select_anchor_ranges(cursor_positions)
9382 });
9383 });
9384 }
9385
9386 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9387 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9388 self.join_lines_impl(true, window, cx);
9389 }
9390
9391 pub fn sort_lines_case_sensitive(
9392 &mut self,
9393 _: &SortLinesCaseSensitive,
9394 window: &mut Window,
9395 cx: &mut Context<Self>,
9396 ) {
9397 self.manipulate_lines(window, cx, |lines| lines.sort())
9398 }
9399
9400 pub fn sort_lines_case_insensitive(
9401 &mut self,
9402 _: &SortLinesCaseInsensitive,
9403 window: &mut Window,
9404 cx: &mut Context<Self>,
9405 ) {
9406 self.manipulate_lines(window, cx, |lines| {
9407 lines.sort_by_key(|line| line.to_lowercase())
9408 })
9409 }
9410
9411 pub fn unique_lines_case_insensitive(
9412 &mut self,
9413 _: &UniqueLinesCaseInsensitive,
9414 window: &mut Window,
9415 cx: &mut Context<Self>,
9416 ) {
9417 self.manipulate_lines(window, cx, |lines| {
9418 let mut seen = HashSet::default();
9419 lines.retain(|line| seen.insert(line.to_lowercase()));
9420 })
9421 }
9422
9423 pub fn unique_lines_case_sensitive(
9424 &mut self,
9425 _: &UniqueLinesCaseSensitive,
9426 window: &mut Window,
9427 cx: &mut Context<Self>,
9428 ) {
9429 self.manipulate_lines(window, cx, |lines| {
9430 let mut seen = HashSet::default();
9431 lines.retain(|line| seen.insert(*line));
9432 })
9433 }
9434
9435 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9436 let Some(project) = self.project.clone() else {
9437 return;
9438 };
9439 self.reload(project, window, cx)
9440 .detach_and_notify_err(window, cx);
9441 }
9442
9443 pub fn restore_file(
9444 &mut self,
9445 _: &::git::RestoreFile,
9446 window: &mut Window,
9447 cx: &mut Context<Self>,
9448 ) {
9449 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9450 let mut buffer_ids = HashSet::default();
9451 let snapshot = self.buffer().read(cx).snapshot(cx);
9452 for selection in self.selections.all::<usize>(cx) {
9453 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9454 }
9455
9456 let buffer = self.buffer().read(cx);
9457 let ranges = buffer_ids
9458 .into_iter()
9459 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9460 .collect::<Vec<_>>();
9461
9462 self.restore_hunks_in_ranges(ranges, window, cx);
9463 }
9464
9465 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9466 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9467 let selections = self
9468 .selections
9469 .all(cx)
9470 .into_iter()
9471 .map(|s| s.range())
9472 .collect();
9473 self.restore_hunks_in_ranges(selections, window, cx);
9474 }
9475
9476 pub fn restore_hunks_in_ranges(
9477 &mut self,
9478 ranges: Vec<Range<Point>>,
9479 window: &mut Window,
9480 cx: &mut Context<Editor>,
9481 ) {
9482 let mut revert_changes = HashMap::default();
9483 let chunk_by = self
9484 .snapshot(window, cx)
9485 .hunks_for_ranges(ranges)
9486 .into_iter()
9487 .chunk_by(|hunk| hunk.buffer_id);
9488 for (buffer_id, hunks) in &chunk_by {
9489 let hunks = hunks.collect::<Vec<_>>();
9490 for hunk in &hunks {
9491 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9492 }
9493 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9494 }
9495 drop(chunk_by);
9496 if !revert_changes.is_empty() {
9497 self.transact(window, cx, |editor, window, cx| {
9498 editor.restore(revert_changes, window, cx);
9499 });
9500 }
9501 }
9502
9503 pub fn open_active_item_in_terminal(
9504 &mut self,
9505 _: &OpenInTerminal,
9506 window: &mut Window,
9507 cx: &mut Context<Self>,
9508 ) {
9509 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9510 let project_path = buffer.read(cx).project_path(cx)?;
9511 let project = self.project.as_ref()?.read(cx);
9512 let entry = project.entry_for_path(&project_path, cx)?;
9513 let parent = match &entry.canonical_path {
9514 Some(canonical_path) => canonical_path.to_path_buf(),
9515 None => project.absolute_path(&project_path, cx)?,
9516 }
9517 .parent()?
9518 .to_path_buf();
9519 Some(parent)
9520 }) {
9521 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9522 }
9523 }
9524
9525 fn set_breakpoint_context_menu(
9526 &mut self,
9527 display_row: DisplayRow,
9528 position: Option<Anchor>,
9529 clicked_point: gpui::Point<Pixels>,
9530 window: &mut Window,
9531 cx: &mut Context<Self>,
9532 ) {
9533 if !cx.has_flag::<DebuggerFeatureFlag>() {
9534 return;
9535 }
9536 let source = self
9537 .buffer
9538 .read(cx)
9539 .snapshot(cx)
9540 .anchor_before(Point::new(display_row.0, 0u32));
9541
9542 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9543
9544 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9545 self,
9546 source,
9547 clicked_point,
9548 context_menu,
9549 window,
9550 cx,
9551 );
9552 }
9553
9554 fn add_edit_breakpoint_block(
9555 &mut self,
9556 anchor: Anchor,
9557 breakpoint: &Breakpoint,
9558 edit_action: BreakpointPromptEditAction,
9559 window: &mut Window,
9560 cx: &mut Context<Self>,
9561 ) {
9562 let weak_editor = cx.weak_entity();
9563 let bp_prompt = cx.new(|cx| {
9564 BreakpointPromptEditor::new(
9565 weak_editor,
9566 anchor,
9567 breakpoint.clone(),
9568 edit_action,
9569 window,
9570 cx,
9571 )
9572 });
9573
9574 let height = bp_prompt.update(cx, |this, cx| {
9575 this.prompt
9576 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9577 });
9578 let cloned_prompt = bp_prompt.clone();
9579 let blocks = vec![BlockProperties {
9580 style: BlockStyle::Sticky,
9581 placement: BlockPlacement::Above(anchor),
9582 height: Some(height),
9583 render: Arc::new(move |cx| {
9584 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9585 cloned_prompt.clone().into_any_element()
9586 }),
9587 priority: 0,
9588 render_in_minimap: true,
9589 }];
9590
9591 let focus_handle = bp_prompt.focus_handle(cx);
9592 window.focus(&focus_handle);
9593
9594 let block_ids = self.insert_blocks(blocks, None, cx);
9595 bp_prompt.update(cx, |prompt, _| {
9596 prompt.add_block_ids(block_ids);
9597 });
9598 }
9599
9600 pub(crate) fn breakpoint_at_row(
9601 &self,
9602 row: u32,
9603 window: &mut Window,
9604 cx: &mut Context<Self>,
9605 ) -> Option<(Anchor, Breakpoint)> {
9606 let snapshot = self.snapshot(window, cx);
9607 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9608
9609 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9610 }
9611
9612 pub(crate) fn breakpoint_at_anchor(
9613 &self,
9614 breakpoint_position: Anchor,
9615 snapshot: &EditorSnapshot,
9616 cx: &mut Context<Self>,
9617 ) -> Option<(Anchor, Breakpoint)> {
9618 let project = self.project.clone()?;
9619
9620 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9621 snapshot
9622 .buffer_snapshot
9623 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9624 })?;
9625
9626 let enclosing_excerpt = breakpoint_position.excerpt_id;
9627 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9628 let buffer_snapshot = buffer.read(cx).snapshot();
9629
9630 let row = buffer_snapshot
9631 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9632 .row;
9633
9634 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9635 let anchor_end = snapshot
9636 .buffer_snapshot
9637 .anchor_after(Point::new(row, line_len));
9638
9639 let bp = self
9640 .breakpoint_store
9641 .as_ref()?
9642 .read_with(cx, |breakpoint_store, cx| {
9643 breakpoint_store
9644 .breakpoints(
9645 &buffer,
9646 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9647 &buffer_snapshot,
9648 cx,
9649 )
9650 .next()
9651 .and_then(|(bp, _)| {
9652 let breakpoint_row = buffer_snapshot
9653 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9654 .row;
9655
9656 if breakpoint_row == row {
9657 snapshot
9658 .buffer_snapshot
9659 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9660 .map(|position| (position, bp.bp.clone()))
9661 } else {
9662 None
9663 }
9664 })
9665 });
9666 bp
9667 }
9668
9669 pub fn edit_log_breakpoint(
9670 &mut self,
9671 _: &EditLogBreakpoint,
9672 window: &mut Window,
9673 cx: &mut Context<Self>,
9674 ) {
9675 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9676 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9677 message: None,
9678 state: BreakpointState::Enabled,
9679 condition: None,
9680 hit_condition: None,
9681 });
9682
9683 self.add_edit_breakpoint_block(
9684 anchor,
9685 &breakpoint,
9686 BreakpointPromptEditAction::Log,
9687 window,
9688 cx,
9689 );
9690 }
9691 }
9692
9693 fn breakpoints_at_cursors(
9694 &self,
9695 window: &mut Window,
9696 cx: &mut Context<Self>,
9697 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9698 let snapshot = self.snapshot(window, cx);
9699 let cursors = self
9700 .selections
9701 .disjoint_anchors()
9702 .into_iter()
9703 .map(|selection| {
9704 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9705
9706 let breakpoint_position = self
9707 .breakpoint_at_row(cursor_position.row, window, cx)
9708 .map(|bp| bp.0)
9709 .unwrap_or_else(|| {
9710 snapshot
9711 .display_snapshot
9712 .buffer_snapshot
9713 .anchor_after(Point::new(cursor_position.row, 0))
9714 });
9715
9716 let breakpoint = self
9717 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9718 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9719
9720 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9721 })
9722 // 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.
9723 .collect::<HashMap<Anchor, _>>();
9724
9725 cursors.into_iter().collect()
9726 }
9727
9728 pub fn enable_breakpoint(
9729 &mut self,
9730 _: &crate::actions::EnableBreakpoint,
9731 window: &mut Window,
9732 cx: &mut Context<Self>,
9733 ) {
9734 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9735 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9736 continue;
9737 };
9738 self.edit_breakpoint_at_anchor(
9739 anchor,
9740 breakpoint,
9741 BreakpointEditAction::InvertState,
9742 cx,
9743 );
9744 }
9745 }
9746
9747 pub fn disable_breakpoint(
9748 &mut self,
9749 _: &crate::actions::DisableBreakpoint,
9750 window: &mut Window,
9751 cx: &mut Context<Self>,
9752 ) {
9753 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9754 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9755 continue;
9756 };
9757 self.edit_breakpoint_at_anchor(
9758 anchor,
9759 breakpoint,
9760 BreakpointEditAction::InvertState,
9761 cx,
9762 );
9763 }
9764 }
9765
9766 pub fn toggle_breakpoint(
9767 &mut self,
9768 _: &crate::actions::ToggleBreakpoint,
9769 window: &mut Window,
9770 cx: &mut Context<Self>,
9771 ) {
9772 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9773 if let Some(breakpoint) = breakpoint {
9774 self.edit_breakpoint_at_anchor(
9775 anchor,
9776 breakpoint,
9777 BreakpointEditAction::Toggle,
9778 cx,
9779 );
9780 } else {
9781 self.edit_breakpoint_at_anchor(
9782 anchor,
9783 Breakpoint::new_standard(),
9784 BreakpointEditAction::Toggle,
9785 cx,
9786 );
9787 }
9788 }
9789 }
9790
9791 pub fn edit_breakpoint_at_anchor(
9792 &mut self,
9793 breakpoint_position: Anchor,
9794 breakpoint: Breakpoint,
9795 edit_action: BreakpointEditAction,
9796 cx: &mut Context<Self>,
9797 ) {
9798 let Some(breakpoint_store) = &self.breakpoint_store else {
9799 return;
9800 };
9801
9802 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9803 if breakpoint_position == Anchor::min() {
9804 self.buffer()
9805 .read(cx)
9806 .excerpt_buffer_ids()
9807 .into_iter()
9808 .next()
9809 } else {
9810 None
9811 }
9812 }) else {
9813 return;
9814 };
9815
9816 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9817 return;
9818 };
9819
9820 breakpoint_store.update(cx, |breakpoint_store, cx| {
9821 breakpoint_store.toggle_breakpoint(
9822 buffer,
9823 BreakpointWithPosition {
9824 position: breakpoint_position.text_anchor,
9825 bp: breakpoint,
9826 },
9827 edit_action,
9828 cx,
9829 );
9830 });
9831
9832 cx.notify();
9833 }
9834
9835 #[cfg(any(test, feature = "test-support"))]
9836 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9837 self.breakpoint_store.clone()
9838 }
9839
9840 pub fn prepare_restore_change(
9841 &self,
9842 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9843 hunk: &MultiBufferDiffHunk,
9844 cx: &mut App,
9845 ) -> Option<()> {
9846 if hunk.is_created_file() {
9847 return None;
9848 }
9849 let buffer = self.buffer.read(cx);
9850 let diff = buffer.diff_for(hunk.buffer_id)?;
9851 let buffer = buffer.buffer(hunk.buffer_id)?;
9852 let buffer = buffer.read(cx);
9853 let original_text = diff
9854 .read(cx)
9855 .base_text()
9856 .as_rope()
9857 .slice(hunk.diff_base_byte_range.clone());
9858 let buffer_snapshot = buffer.snapshot();
9859 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9860 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9861 probe
9862 .0
9863 .start
9864 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9865 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9866 }) {
9867 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9868 Some(())
9869 } else {
9870 None
9871 }
9872 }
9873
9874 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9875 self.manipulate_lines(window, cx, |lines| lines.reverse())
9876 }
9877
9878 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9879 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9880 }
9881
9882 fn manipulate_lines<Fn>(
9883 &mut self,
9884 window: &mut Window,
9885 cx: &mut Context<Self>,
9886 mut callback: Fn,
9887 ) where
9888 Fn: FnMut(&mut Vec<&str>),
9889 {
9890 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9891
9892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9893 let buffer = self.buffer.read(cx).snapshot(cx);
9894
9895 let mut edits = Vec::new();
9896
9897 let selections = self.selections.all::<Point>(cx);
9898 let mut selections = selections.iter().peekable();
9899 let mut contiguous_row_selections = Vec::new();
9900 let mut new_selections = Vec::new();
9901 let mut added_lines = 0;
9902 let mut removed_lines = 0;
9903
9904 while let Some(selection) = selections.next() {
9905 let (start_row, end_row) = consume_contiguous_rows(
9906 &mut contiguous_row_selections,
9907 selection,
9908 &display_map,
9909 &mut selections,
9910 );
9911
9912 let start_point = Point::new(start_row.0, 0);
9913 let end_point = Point::new(
9914 end_row.previous_row().0,
9915 buffer.line_len(end_row.previous_row()),
9916 );
9917 let text = buffer
9918 .text_for_range(start_point..end_point)
9919 .collect::<String>();
9920
9921 let mut lines = text.split('\n').collect_vec();
9922
9923 let lines_before = lines.len();
9924 callback(&mut lines);
9925 let lines_after = lines.len();
9926
9927 edits.push((start_point..end_point, lines.join("\n")));
9928
9929 // Selections must change based on added and removed line count
9930 let start_row =
9931 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9932 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9933 new_selections.push(Selection {
9934 id: selection.id,
9935 start: start_row,
9936 end: end_row,
9937 goal: SelectionGoal::None,
9938 reversed: selection.reversed,
9939 });
9940
9941 if lines_after > lines_before {
9942 added_lines += lines_after - lines_before;
9943 } else if lines_before > lines_after {
9944 removed_lines += lines_before - lines_after;
9945 }
9946 }
9947
9948 self.transact(window, cx, |this, window, cx| {
9949 let buffer = this.buffer.update(cx, |buffer, cx| {
9950 buffer.edit(edits, None, cx);
9951 buffer.snapshot(cx)
9952 });
9953
9954 // Recalculate offsets on newly edited buffer
9955 let new_selections = new_selections
9956 .iter()
9957 .map(|s| {
9958 let start_point = Point::new(s.start.0, 0);
9959 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9960 Selection {
9961 id: s.id,
9962 start: buffer.point_to_offset(start_point),
9963 end: buffer.point_to_offset(end_point),
9964 goal: s.goal,
9965 reversed: s.reversed,
9966 }
9967 })
9968 .collect();
9969
9970 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9971 s.select(new_selections);
9972 });
9973
9974 this.request_autoscroll(Autoscroll::fit(), cx);
9975 });
9976 }
9977
9978 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9979 self.manipulate_text(window, cx, |text| {
9980 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9981 if has_upper_case_characters {
9982 text.to_lowercase()
9983 } else {
9984 text.to_uppercase()
9985 }
9986 })
9987 }
9988
9989 pub fn convert_to_upper_case(
9990 &mut self,
9991 _: &ConvertToUpperCase,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) {
9995 self.manipulate_text(window, cx, |text| text.to_uppercase())
9996 }
9997
9998 pub fn convert_to_lower_case(
9999 &mut self,
10000 _: &ConvertToLowerCase,
10001 window: &mut Window,
10002 cx: &mut Context<Self>,
10003 ) {
10004 self.manipulate_text(window, cx, |text| text.to_lowercase())
10005 }
10006
10007 pub fn convert_to_title_case(
10008 &mut self,
10009 _: &ConvertToTitleCase,
10010 window: &mut Window,
10011 cx: &mut Context<Self>,
10012 ) {
10013 self.manipulate_text(window, cx, |text| {
10014 text.split('\n')
10015 .map(|line| line.to_case(Case::Title))
10016 .join("\n")
10017 })
10018 }
10019
10020 pub fn convert_to_snake_case(
10021 &mut self,
10022 _: &ConvertToSnakeCase,
10023 window: &mut Window,
10024 cx: &mut Context<Self>,
10025 ) {
10026 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10027 }
10028
10029 pub fn convert_to_kebab_case(
10030 &mut self,
10031 _: &ConvertToKebabCase,
10032 window: &mut Window,
10033 cx: &mut Context<Self>,
10034 ) {
10035 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10036 }
10037
10038 pub fn convert_to_upper_camel_case(
10039 &mut self,
10040 _: &ConvertToUpperCamelCase,
10041 window: &mut Window,
10042 cx: &mut Context<Self>,
10043 ) {
10044 self.manipulate_text(window, cx, |text| {
10045 text.split('\n')
10046 .map(|line| line.to_case(Case::UpperCamel))
10047 .join("\n")
10048 })
10049 }
10050
10051 pub fn convert_to_lower_camel_case(
10052 &mut self,
10053 _: &ConvertToLowerCamelCase,
10054 window: &mut Window,
10055 cx: &mut Context<Self>,
10056 ) {
10057 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10058 }
10059
10060 pub fn convert_to_opposite_case(
10061 &mut self,
10062 _: &ConvertToOppositeCase,
10063 window: &mut Window,
10064 cx: &mut Context<Self>,
10065 ) {
10066 self.manipulate_text(window, cx, |text| {
10067 text.chars()
10068 .fold(String::with_capacity(text.len()), |mut t, c| {
10069 if c.is_uppercase() {
10070 t.extend(c.to_lowercase());
10071 } else {
10072 t.extend(c.to_uppercase());
10073 }
10074 t
10075 })
10076 })
10077 }
10078
10079 pub fn convert_to_rot13(
10080 &mut self,
10081 _: &ConvertToRot13,
10082 window: &mut Window,
10083 cx: &mut Context<Self>,
10084 ) {
10085 self.manipulate_text(window, cx, |text| {
10086 text.chars()
10087 .map(|c| match c {
10088 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10089 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10090 _ => c,
10091 })
10092 .collect()
10093 })
10094 }
10095
10096 pub fn convert_to_rot47(
10097 &mut self,
10098 _: &ConvertToRot47,
10099 window: &mut Window,
10100 cx: &mut Context<Self>,
10101 ) {
10102 self.manipulate_text(window, cx, |text| {
10103 text.chars()
10104 .map(|c| {
10105 let code_point = c as u32;
10106 if code_point >= 33 && code_point <= 126 {
10107 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10108 }
10109 c
10110 })
10111 .collect()
10112 })
10113 }
10114
10115 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10116 where
10117 Fn: FnMut(&str) -> String,
10118 {
10119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10120 let buffer = self.buffer.read(cx).snapshot(cx);
10121
10122 let mut new_selections = Vec::new();
10123 let mut edits = Vec::new();
10124 let mut selection_adjustment = 0i32;
10125
10126 for selection in self.selections.all::<usize>(cx) {
10127 let selection_is_empty = selection.is_empty();
10128
10129 let (start, end) = if selection_is_empty {
10130 let word_range = movement::surrounding_word(
10131 &display_map,
10132 selection.start.to_display_point(&display_map),
10133 );
10134 let start = word_range.start.to_offset(&display_map, Bias::Left);
10135 let end = word_range.end.to_offset(&display_map, Bias::Left);
10136 (start, end)
10137 } else {
10138 (selection.start, selection.end)
10139 };
10140
10141 let text = buffer.text_for_range(start..end).collect::<String>();
10142 let old_length = text.len() as i32;
10143 let text = callback(&text);
10144
10145 new_selections.push(Selection {
10146 start: (start as i32 - selection_adjustment) as usize,
10147 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10148 goal: SelectionGoal::None,
10149 ..selection
10150 });
10151
10152 selection_adjustment += old_length - text.len() as i32;
10153
10154 edits.push((start..end, text));
10155 }
10156
10157 self.transact(window, cx, |this, window, cx| {
10158 this.buffer.update(cx, |buffer, cx| {
10159 buffer.edit(edits, None, cx);
10160 });
10161
10162 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10163 s.select(new_selections);
10164 });
10165
10166 this.request_autoscroll(Autoscroll::fit(), cx);
10167 });
10168 }
10169
10170 pub fn duplicate(
10171 &mut self,
10172 upwards: bool,
10173 whole_lines: bool,
10174 window: &mut Window,
10175 cx: &mut Context<Self>,
10176 ) {
10177 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10178
10179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10180 let buffer = &display_map.buffer_snapshot;
10181 let selections = self.selections.all::<Point>(cx);
10182
10183 let mut edits = Vec::new();
10184 let mut selections_iter = selections.iter().peekable();
10185 while let Some(selection) = selections_iter.next() {
10186 let mut rows = selection.spanned_rows(false, &display_map);
10187 // duplicate line-wise
10188 if whole_lines || selection.start == selection.end {
10189 // Avoid duplicating the same lines twice.
10190 while let Some(next_selection) = selections_iter.peek() {
10191 let next_rows = next_selection.spanned_rows(false, &display_map);
10192 if next_rows.start < rows.end {
10193 rows.end = next_rows.end;
10194 selections_iter.next().unwrap();
10195 } else {
10196 break;
10197 }
10198 }
10199
10200 // Copy the text from the selected row region and splice it either at the start
10201 // or end of the region.
10202 let start = Point::new(rows.start.0, 0);
10203 let end = Point::new(
10204 rows.end.previous_row().0,
10205 buffer.line_len(rows.end.previous_row()),
10206 );
10207 let text = buffer
10208 .text_for_range(start..end)
10209 .chain(Some("\n"))
10210 .collect::<String>();
10211 let insert_location = if upwards {
10212 Point::new(rows.end.0, 0)
10213 } else {
10214 start
10215 };
10216 edits.push((insert_location..insert_location, text));
10217 } else {
10218 // duplicate character-wise
10219 let start = selection.start;
10220 let end = selection.end;
10221 let text = buffer.text_for_range(start..end).collect::<String>();
10222 edits.push((selection.end..selection.end, text));
10223 }
10224 }
10225
10226 self.transact(window, cx, |this, _, cx| {
10227 this.buffer.update(cx, |buffer, cx| {
10228 buffer.edit(edits, None, cx);
10229 });
10230
10231 this.request_autoscroll(Autoscroll::fit(), cx);
10232 });
10233 }
10234
10235 pub fn duplicate_line_up(
10236 &mut self,
10237 _: &DuplicateLineUp,
10238 window: &mut Window,
10239 cx: &mut Context<Self>,
10240 ) {
10241 self.duplicate(true, true, window, cx);
10242 }
10243
10244 pub fn duplicate_line_down(
10245 &mut self,
10246 _: &DuplicateLineDown,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 self.duplicate(false, true, window, cx);
10251 }
10252
10253 pub fn duplicate_selection(
10254 &mut self,
10255 _: &DuplicateSelection,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) {
10259 self.duplicate(false, false, window, cx);
10260 }
10261
10262 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10263 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10264
10265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10266 let buffer = self.buffer.read(cx).snapshot(cx);
10267
10268 let mut edits = Vec::new();
10269 let mut unfold_ranges = Vec::new();
10270 let mut refold_creases = Vec::new();
10271
10272 let selections = self.selections.all::<Point>(cx);
10273 let mut selections = selections.iter().peekable();
10274 let mut contiguous_row_selections = Vec::new();
10275 let mut new_selections = Vec::new();
10276
10277 while let Some(selection) = selections.next() {
10278 // Find all the selections that span a contiguous row range
10279 let (start_row, end_row) = consume_contiguous_rows(
10280 &mut contiguous_row_selections,
10281 selection,
10282 &display_map,
10283 &mut selections,
10284 );
10285
10286 // Move the text spanned by the row range to be before the line preceding the row range
10287 if start_row.0 > 0 {
10288 let range_to_move = Point::new(
10289 start_row.previous_row().0,
10290 buffer.line_len(start_row.previous_row()),
10291 )
10292 ..Point::new(
10293 end_row.previous_row().0,
10294 buffer.line_len(end_row.previous_row()),
10295 );
10296 let insertion_point = display_map
10297 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10298 .0;
10299
10300 // Don't move lines across excerpts
10301 if buffer
10302 .excerpt_containing(insertion_point..range_to_move.end)
10303 .is_some()
10304 {
10305 let text = buffer
10306 .text_for_range(range_to_move.clone())
10307 .flat_map(|s| s.chars())
10308 .skip(1)
10309 .chain(['\n'])
10310 .collect::<String>();
10311
10312 edits.push((
10313 buffer.anchor_after(range_to_move.start)
10314 ..buffer.anchor_before(range_to_move.end),
10315 String::new(),
10316 ));
10317 let insertion_anchor = buffer.anchor_after(insertion_point);
10318 edits.push((insertion_anchor..insertion_anchor, text));
10319
10320 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10321
10322 // Move selections up
10323 new_selections.extend(contiguous_row_selections.drain(..).map(
10324 |mut selection| {
10325 selection.start.row -= row_delta;
10326 selection.end.row -= row_delta;
10327 selection
10328 },
10329 ));
10330
10331 // Move folds up
10332 unfold_ranges.push(range_to_move.clone());
10333 for fold in display_map.folds_in_range(
10334 buffer.anchor_before(range_to_move.start)
10335 ..buffer.anchor_after(range_to_move.end),
10336 ) {
10337 let mut start = fold.range.start.to_point(&buffer);
10338 let mut end = fold.range.end.to_point(&buffer);
10339 start.row -= row_delta;
10340 end.row -= row_delta;
10341 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10342 }
10343 }
10344 }
10345
10346 // If we didn't move line(s), preserve the existing selections
10347 new_selections.append(&mut contiguous_row_selections);
10348 }
10349
10350 self.transact(window, cx, |this, window, cx| {
10351 this.unfold_ranges(&unfold_ranges, true, true, cx);
10352 this.buffer.update(cx, |buffer, cx| {
10353 for (range, text) in edits {
10354 buffer.edit([(range, text)], None, cx);
10355 }
10356 });
10357 this.fold_creases(refold_creases, true, window, cx);
10358 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10359 s.select(new_selections);
10360 })
10361 });
10362 }
10363
10364 pub fn move_line_down(
10365 &mut self,
10366 _: &MoveLineDown,
10367 window: &mut Window,
10368 cx: &mut Context<Self>,
10369 ) {
10370 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10371
10372 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10373 let buffer = self.buffer.read(cx).snapshot(cx);
10374
10375 let mut edits = Vec::new();
10376 let mut unfold_ranges = Vec::new();
10377 let mut refold_creases = Vec::new();
10378
10379 let selections = self.selections.all::<Point>(cx);
10380 let mut selections = selections.iter().peekable();
10381 let mut contiguous_row_selections = Vec::new();
10382 let mut new_selections = Vec::new();
10383
10384 while let Some(selection) = selections.next() {
10385 // Find all the selections that span a contiguous row range
10386 let (start_row, end_row) = consume_contiguous_rows(
10387 &mut contiguous_row_selections,
10388 selection,
10389 &display_map,
10390 &mut selections,
10391 );
10392
10393 // Move the text spanned by the row range to be after the last line of the row range
10394 if end_row.0 <= buffer.max_point().row {
10395 let range_to_move =
10396 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10397 let insertion_point = display_map
10398 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10399 .0;
10400
10401 // Don't move lines across excerpt boundaries
10402 if buffer
10403 .excerpt_containing(range_to_move.start..insertion_point)
10404 .is_some()
10405 {
10406 let mut text = String::from("\n");
10407 text.extend(buffer.text_for_range(range_to_move.clone()));
10408 text.pop(); // Drop trailing newline
10409 edits.push((
10410 buffer.anchor_after(range_to_move.start)
10411 ..buffer.anchor_before(range_to_move.end),
10412 String::new(),
10413 ));
10414 let insertion_anchor = buffer.anchor_after(insertion_point);
10415 edits.push((insertion_anchor..insertion_anchor, text));
10416
10417 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10418
10419 // Move selections down
10420 new_selections.extend(contiguous_row_selections.drain(..).map(
10421 |mut selection| {
10422 selection.start.row += row_delta;
10423 selection.end.row += row_delta;
10424 selection
10425 },
10426 ));
10427
10428 // Move folds down
10429 unfold_ranges.push(range_to_move.clone());
10430 for fold in display_map.folds_in_range(
10431 buffer.anchor_before(range_to_move.start)
10432 ..buffer.anchor_after(range_to_move.end),
10433 ) {
10434 let mut start = fold.range.start.to_point(&buffer);
10435 let mut end = fold.range.end.to_point(&buffer);
10436 start.row += row_delta;
10437 end.row += row_delta;
10438 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10439 }
10440 }
10441 }
10442
10443 // If we didn't move line(s), preserve the existing selections
10444 new_selections.append(&mut contiguous_row_selections);
10445 }
10446
10447 self.transact(window, cx, |this, window, cx| {
10448 this.unfold_ranges(&unfold_ranges, true, true, cx);
10449 this.buffer.update(cx, |buffer, cx| {
10450 for (range, text) in edits {
10451 buffer.edit([(range, text)], None, cx);
10452 }
10453 });
10454 this.fold_creases(refold_creases, true, window, cx);
10455 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10456 s.select(new_selections)
10457 });
10458 });
10459 }
10460
10461 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10462 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10463 let text_layout_details = &self.text_layout_details(window);
10464 self.transact(window, cx, |this, window, cx| {
10465 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10466 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10467 s.move_with(|display_map, selection| {
10468 if !selection.is_empty() {
10469 return;
10470 }
10471
10472 let mut head = selection.head();
10473 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10474 if head.column() == display_map.line_len(head.row()) {
10475 transpose_offset = display_map
10476 .buffer_snapshot
10477 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10478 }
10479
10480 if transpose_offset == 0 {
10481 return;
10482 }
10483
10484 *head.column_mut() += 1;
10485 head = display_map.clip_point(head, Bias::Right);
10486 let goal = SelectionGoal::HorizontalPosition(
10487 display_map
10488 .x_for_display_point(head, text_layout_details)
10489 .into(),
10490 );
10491 selection.collapse_to(head, goal);
10492
10493 let transpose_start = display_map
10494 .buffer_snapshot
10495 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10496 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10497 let transpose_end = display_map
10498 .buffer_snapshot
10499 .clip_offset(transpose_offset + 1, Bias::Right);
10500 if let Some(ch) =
10501 display_map.buffer_snapshot.chars_at(transpose_start).next()
10502 {
10503 edits.push((transpose_start..transpose_offset, String::new()));
10504 edits.push((transpose_end..transpose_end, ch.to_string()));
10505 }
10506 }
10507 });
10508 edits
10509 });
10510 this.buffer
10511 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10512 let selections = this.selections.all::<usize>(cx);
10513 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10514 s.select(selections);
10515 });
10516 });
10517 }
10518
10519 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10520 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10521 self.rewrap_impl(RewrapOptions::default(), cx)
10522 }
10523
10524 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10525 let buffer = self.buffer.read(cx).snapshot(cx);
10526 let selections = self.selections.all::<Point>(cx);
10527 let mut selections = selections.iter().peekable();
10528
10529 let mut edits = Vec::new();
10530 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10531
10532 while let Some(selection) = selections.next() {
10533 let mut start_row = selection.start.row;
10534 let mut end_row = selection.end.row;
10535
10536 // Skip selections that overlap with a range that has already been rewrapped.
10537 let selection_range = start_row..end_row;
10538 if rewrapped_row_ranges
10539 .iter()
10540 .any(|range| range.overlaps(&selection_range))
10541 {
10542 continue;
10543 }
10544
10545 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10546
10547 // Since not all lines in the selection may be at the same indent
10548 // level, choose the indent size that is the most common between all
10549 // of the lines.
10550 //
10551 // If there is a tie, we use the deepest indent.
10552 let (indent_size, indent_end) = {
10553 let mut indent_size_occurrences = HashMap::default();
10554 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10555
10556 for row in start_row..=end_row {
10557 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10558 rows_by_indent_size.entry(indent).or_default().push(row);
10559 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10560 }
10561
10562 let indent_size = indent_size_occurrences
10563 .into_iter()
10564 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10565 .map(|(indent, _)| indent)
10566 .unwrap_or_default();
10567 let row = rows_by_indent_size[&indent_size][0];
10568 let indent_end = Point::new(row, indent_size.len);
10569
10570 (indent_size, indent_end)
10571 };
10572
10573 let mut line_prefix = indent_size.chars().collect::<String>();
10574
10575 let mut inside_comment = false;
10576 if let Some(comment_prefix) =
10577 buffer
10578 .language_scope_at(selection.head())
10579 .and_then(|language| {
10580 language
10581 .line_comment_prefixes()
10582 .iter()
10583 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10584 .cloned()
10585 })
10586 {
10587 line_prefix.push_str(&comment_prefix);
10588 inside_comment = true;
10589 }
10590
10591 let language_settings = buffer.language_settings_at(selection.head(), cx);
10592 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10593 RewrapBehavior::InComments => inside_comment,
10594 RewrapBehavior::InSelections => !selection.is_empty(),
10595 RewrapBehavior::Anywhere => true,
10596 };
10597
10598 let should_rewrap = options.override_language_settings
10599 || allow_rewrap_based_on_language
10600 || self.hard_wrap.is_some();
10601 if !should_rewrap {
10602 continue;
10603 }
10604
10605 if selection.is_empty() {
10606 'expand_upwards: while start_row > 0 {
10607 let prev_row = start_row - 1;
10608 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10609 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10610 {
10611 start_row = prev_row;
10612 } else {
10613 break 'expand_upwards;
10614 }
10615 }
10616
10617 'expand_downwards: while end_row < buffer.max_point().row {
10618 let next_row = end_row + 1;
10619 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10620 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10621 {
10622 end_row = next_row;
10623 } else {
10624 break 'expand_downwards;
10625 }
10626 }
10627 }
10628
10629 let start = Point::new(start_row, 0);
10630 let start_offset = start.to_offset(&buffer);
10631 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10632 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10633 let Some(lines_without_prefixes) = selection_text
10634 .lines()
10635 .map(|line| {
10636 line.strip_prefix(&line_prefix)
10637 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10638 .with_context(|| {
10639 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10640 })
10641 })
10642 .collect::<Result<Vec<_>, _>>()
10643 .log_err()
10644 else {
10645 continue;
10646 };
10647
10648 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10649 buffer
10650 .language_settings_at(Point::new(start_row, 0), cx)
10651 .preferred_line_length as usize
10652 });
10653 let wrapped_text = wrap_with_prefix(
10654 line_prefix,
10655 lines_without_prefixes.join("\n"),
10656 wrap_column,
10657 tab_size,
10658 options.preserve_existing_whitespace,
10659 );
10660
10661 // TODO: should always use char-based diff while still supporting cursor behavior that
10662 // matches vim.
10663 let mut diff_options = DiffOptions::default();
10664 if options.override_language_settings {
10665 diff_options.max_word_diff_len = 0;
10666 diff_options.max_word_diff_line_count = 0;
10667 } else {
10668 diff_options.max_word_diff_len = usize::MAX;
10669 diff_options.max_word_diff_line_count = usize::MAX;
10670 }
10671
10672 for (old_range, new_text) in
10673 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10674 {
10675 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10676 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10677 edits.push((edit_start..edit_end, new_text));
10678 }
10679
10680 rewrapped_row_ranges.push(start_row..=end_row);
10681 }
10682
10683 self.buffer
10684 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10685 }
10686
10687 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10688 let mut text = String::new();
10689 let buffer = self.buffer.read(cx).snapshot(cx);
10690 let mut selections = self.selections.all::<Point>(cx);
10691 let mut clipboard_selections = Vec::with_capacity(selections.len());
10692 {
10693 let max_point = buffer.max_point();
10694 let mut is_first = true;
10695 for selection in &mut selections {
10696 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10697 if is_entire_line {
10698 selection.start = Point::new(selection.start.row, 0);
10699 if !selection.is_empty() && selection.end.column == 0 {
10700 selection.end = cmp::min(max_point, selection.end);
10701 } else {
10702 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10703 }
10704 selection.goal = SelectionGoal::None;
10705 }
10706 if is_first {
10707 is_first = false;
10708 } else {
10709 text += "\n";
10710 }
10711 let mut len = 0;
10712 for chunk in buffer.text_for_range(selection.start..selection.end) {
10713 text.push_str(chunk);
10714 len += chunk.len();
10715 }
10716 clipboard_selections.push(ClipboardSelection {
10717 len,
10718 is_entire_line,
10719 first_line_indent: buffer
10720 .indent_size_for_line(MultiBufferRow(selection.start.row))
10721 .len,
10722 });
10723 }
10724 }
10725
10726 self.transact(window, cx, |this, window, cx| {
10727 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10728 s.select(selections);
10729 });
10730 this.insert("", window, cx);
10731 });
10732 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10733 }
10734
10735 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10737 let item = self.cut_common(window, cx);
10738 cx.write_to_clipboard(item);
10739 }
10740
10741 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10742 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10743 self.change_selections(None, window, cx, |s| {
10744 s.move_with(|snapshot, sel| {
10745 if sel.is_empty() {
10746 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10747 }
10748 });
10749 });
10750 let item = self.cut_common(window, cx);
10751 cx.set_global(KillRing(item))
10752 }
10753
10754 pub fn kill_ring_yank(
10755 &mut self,
10756 _: &KillRingYank,
10757 window: &mut Window,
10758 cx: &mut Context<Self>,
10759 ) {
10760 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10761 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10762 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10763 (kill_ring.text().to_string(), kill_ring.metadata_json())
10764 } else {
10765 return;
10766 }
10767 } else {
10768 return;
10769 };
10770 self.do_paste(&text, metadata, false, window, cx);
10771 }
10772
10773 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10774 self.do_copy(true, cx);
10775 }
10776
10777 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10778 self.do_copy(false, cx);
10779 }
10780
10781 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10782 let selections = self.selections.all::<Point>(cx);
10783 let buffer = self.buffer.read(cx).read(cx);
10784 let mut text = String::new();
10785
10786 let mut clipboard_selections = Vec::with_capacity(selections.len());
10787 {
10788 let max_point = buffer.max_point();
10789 let mut is_first = true;
10790 for selection in &selections {
10791 let mut start = selection.start;
10792 let mut end = selection.end;
10793 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10794 if is_entire_line {
10795 start = Point::new(start.row, 0);
10796 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10797 }
10798
10799 let mut trimmed_selections = Vec::new();
10800 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10801 let row = MultiBufferRow(start.row);
10802 let first_indent = buffer.indent_size_for_line(row);
10803 if first_indent.len == 0 || start.column > first_indent.len {
10804 trimmed_selections.push(start..end);
10805 } else {
10806 trimmed_selections.push(
10807 Point::new(row.0, first_indent.len)
10808 ..Point::new(row.0, buffer.line_len(row)),
10809 );
10810 for row in start.row + 1..=end.row {
10811 let mut line_len = buffer.line_len(MultiBufferRow(row));
10812 if row == end.row {
10813 line_len = end.column;
10814 }
10815 if line_len == 0 {
10816 trimmed_selections
10817 .push(Point::new(row, 0)..Point::new(row, line_len));
10818 continue;
10819 }
10820 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10821 if row_indent_size.len >= first_indent.len {
10822 trimmed_selections.push(
10823 Point::new(row, first_indent.len)..Point::new(row, line_len),
10824 );
10825 } else {
10826 trimmed_selections.clear();
10827 trimmed_selections.push(start..end);
10828 break;
10829 }
10830 }
10831 }
10832 } else {
10833 trimmed_selections.push(start..end);
10834 }
10835
10836 for trimmed_range in trimmed_selections {
10837 if is_first {
10838 is_first = false;
10839 } else {
10840 text += "\n";
10841 }
10842 let mut len = 0;
10843 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10844 text.push_str(chunk);
10845 len += chunk.len();
10846 }
10847 clipboard_selections.push(ClipboardSelection {
10848 len,
10849 is_entire_line,
10850 first_line_indent: buffer
10851 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10852 .len,
10853 });
10854 }
10855 }
10856 }
10857
10858 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10859 text,
10860 clipboard_selections,
10861 ));
10862 }
10863
10864 pub fn do_paste(
10865 &mut self,
10866 text: &String,
10867 clipboard_selections: Option<Vec<ClipboardSelection>>,
10868 handle_entire_lines: bool,
10869 window: &mut Window,
10870 cx: &mut Context<Self>,
10871 ) {
10872 if self.read_only(cx) {
10873 return;
10874 }
10875
10876 let clipboard_text = Cow::Borrowed(text);
10877
10878 self.transact(window, cx, |this, window, cx| {
10879 if let Some(mut clipboard_selections) = clipboard_selections {
10880 let old_selections = this.selections.all::<usize>(cx);
10881 let all_selections_were_entire_line =
10882 clipboard_selections.iter().all(|s| s.is_entire_line);
10883 let first_selection_indent_column =
10884 clipboard_selections.first().map(|s| s.first_line_indent);
10885 if clipboard_selections.len() != old_selections.len() {
10886 clipboard_selections.drain(..);
10887 }
10888 let cursor_offset = this.selections.last::<usize>(cx).head();
10889 let mut auto_indent_on_paste = true;
10890
10891 this.buffer.update(cx, |buffer, cx| {
10892 let snapshot = buffer.read(cx);
10893 auto_indent_on_paste = snapshot
10894 .language_settings_at(cursor_offset, cx)
10895 .auto_indent_on_paste;
10896
10897 let mut start_offset = 0;
10898 let mut edits = Vec::new();
10899 let mut original_indent_columns = Vec::new();
10900 for (ix, selection) in old_selections.iter().enumerate() {
10901 let to_insert;
10902 let entire_line;
10903 let original_indent_column;
10904 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10905 let end_offset = start_offset + clipboard_selection.len;
10906 to_insert = &clipboard_text[start_offset..end_offset];
10907 entire_line = clipboard_selection.is_entire_line;
10908 start_offset = end_offset + 1;
10909 original_indent_column = Some(clipboard_selection.first_line_indent);
10910 } else {
10911 to_insert = clipboard_text.as_str();
10912 entire_line = all_selections_were_entire_line;
10913 original_indent_column = first_selection_indent_column
10914 }
10915
10916 // If the corresponding selection was empty when this slice of the
10917 // clipboard text was written, then the entire line containing the
10918 // selection was copied. If this selection is also currently empty,
10919 // then paste the line before the current line of the buffer.
10920 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10921 let column = selection.start.to_point(&snapshot).column as usize;
10922 let line_start = selection.start - column;
10923 line_start..line_start
10924 } else {
10925 selection.range()
10926 };
10927
10928 edits.push((range, to_insert));
10929 original_indent_columns.push(original_indent_column);
10930 }
10931 drop(snapshot);
10932
10933 buffer.edit(
10934 edits,
10935 if auto_indent_on_paste {
10936 Some(AutoindentMode::Block {
10937 original_indent_columns,
10938 })
10939 } else {
10940 None
10941 },
10942 cx,
10943 );
10944 });
10945
10946 let selections = this.selections.all::<usize>(cx);
10947 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10948 s.select(selections)
10949 });
10950 } else {
10951 this.insert(&clipboard_text, window, cx);
10952 }
10953 });
10954 }
10955
10956 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10957 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10958 if let Some(item) = cx.read_from_clipboard() {
10959 let entries = item.entries();
10960
10961 match entries.first() {
10962 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10963 // of all the pasted entries.
10964 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10965 .do_paste(
10966 clipboard_string.text(),
10967 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10968 true,
10969 window,
10970 cx,
10971 ),
10972 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10973 }
10974 }
10975 }
10976
10977 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10978 if self.read_only(cx) {
10979 return;
10980 }
10981
10982 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10983
10984 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10985 if let Some((selections, _)) =
10986 self.selection_history.transaction(transaction_id).cloned()
10987 {
10988 self.change_selections(None, window, cx, |s| {
10989 s.select_anchors(selections.to_vec());
10990 });
10991 } else {
10992 log::error!(
10993 "No entry in selection_history found for undo. \
10994 This may correspond to a bug where undo does not update the selection. \
10995 If this is occurring, please add details to \
10996 https://github.com/zed-industries/zed/issues/22692"
10997 );
10998 }
10999 self.request_autoscroll(Autoscroll::fit(), cx);
11000 self.unmark_text(window, cx);
11001 self.refresh_inline_completion(true, false, window, cx);
11002 cx.emit(EditorEvent::Edited { transaction_id });
11003 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11004 }
11005 }
11006
11007 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11008 if self.read_only(cx) {
11009 return;
11010 }
11011
11012 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11013
11014 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11015 if let Some((_, Some(selections))) =
11016 self.selection_history.transaction(transaction_id).cloned()
11017 {
11018 self.change_selections(None, window, cx, |s| {
11019 s.select_anchors(selections.to_vec());
11020 });
11021 } else {
11022 log::error!(
11023 "No entry in selection_history found for redo. \
11024 This may correspond to a bug where undo does not update the selection. \
11025 If this is occurring, please add details to \
11026 https://github.com/zed-industries/zed/issues/22692"
11027 );
11028 }
11029 self.request_autoscroll(Autoscroll::fit(), cx);
11030 self.unmark_text(window, cx);
11031 self.refresh_inline_completion(true, false, window, cx);
11032 cx.emit(EditorEvent::Edited { transaction_id });
11033 }
11034 }
11035
11036 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11037 self.buffer
11038 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11039 }
11040
11041 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11042 self.buffer
11043 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11044 }
11045
11046 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11047 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11048 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11049 s.move_with(|map, selection| {
11050 let cursor = if selection.is_empty() {
11051 movement::left(map, selection.start)
11052 } else {
11053 selection.start
11054 };
11055 selection.collapse_to(cursor, SelectionGoal::None);
11056 });
11057 })
11058 }
11059
11060 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11063 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11064 })
11065 }
11066
11067 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11068 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11069 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11070 s.move_with(|map, selection| {
11071 let cursor = if selection.is_empty() {
11072 movement::right(map, selection.end)
11073 } else {
11074 selection.end
11075 };
11076 selection.collapse_to(cursor, SelectionGoal::None)
11077 });
11078 })
11079 }
11080
11081 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11082 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11083 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11084 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11085 })
11086 }
11087
11088 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11089 if self.take_rename(true, window, cx).is_some() {
11090 return;
11091 }
11092
11093 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11094 cx.propagate();
11095 return;
11096 }
11097
11098 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11099
11100 let text_layout_details = &self.text_layout_details(window);
11101 let selection_count = self.selections.count();
11102 let first_selection = self.selections.first_anchor();
11103
11104 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11105 s.move_with(|map, selection| {
11106 if !selection.is_empty() {
11107 selection.goal = SelectionGoal::None;
11108 }
11109 let (cursor, goal) = movement::up(
11110 map,
11111 selection.start,
11112 selection.goal,
11113 false,
11114 text_layout_details,
11115 );
11116 selection.collapse_to(cursor, goal);
11117 });
11118 });
11119
11120 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11121 {
11122 cx.propagate();
11123 }
11124 }
11125
11126 pub fn move_up_by_lines(
11127 &mut self,
11128 action: &MoveUpByLines,
11129 window: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 if self.take_rename(true, window, cx).is_some() {
11133 return;
11134 }
11135
11136 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11137 cx.propagate();
11138 return;
11139 }
11140
11141 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11142
11143 let text_layout_details = &self.text_layout_details(window);
11144
11145 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11146 s.move_with(|map, selection| {
11147 if !selection.is_empty() {
11148 selection.goal = SelectionGoal::None;
11149 }
11150 let (cursor, goal) = movement::up_by_rows(
11151 map,
11152 selection.start,
11153 action.lines,
11154 selection.goal,
11155 false,
11156 text_layout_details,
11157 );
11158 selection.collapse_to(cursor, goal);
11159 });
11160 })
11161 }
11162
11163 pub fn move_down_by_lines(
11164 &mut self,
11165 action: &MoveDownByLines,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 if self.take_rename(true, window, cx).is_some() {
11170 return;
11171 }
11172
11173 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11174 cx.propagate();
11175 return;
11176 }
11177
11178 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11179
11180 let text_layout_details = &self.text_layout_details(window);
11181
11182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11183 s.move_with(|map, selection| {
11184 if !selection.is_empty() {
11185 selection.goal = SelectionGoal::None;
11186 }
11187 let (cursor, goal) = movement::down_by_rows(
11188 map,
11189 selection.start,
11190 action.lines,
11191 selection.goal,
11192 false,
11193 text_layout_details,
11194 );
11195 selection.collapse_to(cursor, goal);
11196 });
11197 })
11198 }
11199
11200 pub fn select_down_by_lines(
11201 &mut self,
11202 action: &SelectDownByLines,
11203 window: &mut Window,
11204 cx: &mut Context<Self>,
11205 ) {
11206 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11207 let text_layout_details = &self.text_layout_details(window);
11208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11209 s.move_heads_with(|map, head, goal| {
11210 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11211 })
11212 })
11213 }
11214
11215 pub fn select_up_by_lines(
11216 &mut self,
11217 action: &SelectUpByLines,
11218 window: &mut Window,
11219 cx: &mut Context<Self>,
11220 ) {
11221 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11222 let text_layout_details = &self.text_layout_details(window);
11223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11224 s.move_heads_with(|map, head, goal| {
11225 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11226 })
11227 })
11228 }
11229
11230 pub fn select_page_up(
11231 &mut self,
11232 _: &SelectPageUp,
11233 window: &mut Window,
11234 cx: &mut Context<Self>,
11235 ) {
11236 let Some(row_count) = self.visible_row_count() else {
11237 return;
11238 };
11239
11240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11241
11242 let text_layout_details = &self.text_layout_details(window);
11243
11244 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11245 s.move_heads_with(|map, head, goal| {
11246 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11247 })
11248 })
11249 }
11250
11251 pub fn move_page_up(
11252 &mut self,
11253 action: &MovePageUp,
11254 window: &mut Window,
11255 cx: &mut Context<Self>,
11256 ) {
11257 if self.take_rename(true, window, cx).is_some() {
11258 return;
11259 }
11260
11261 if self
11262 .context_menu
11263 .borrow_mut()
11264 .as_mut()
11265 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11266 .unwrap_or(false)
11267 {
11268 return;
11269 }
11270
11271 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11272 cx.propagate();
11273 return;
11274 }
11275
11276 let Some(row_count) = self.visible_row_count() else {
11277 return;
11278 };
11279
11280 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11281
11282 let autoscroll = if action.center_cursor {
11283 Autoscroll::center()
11284 } else {
11285 Autoscroll::fit()
11286 };
11287
11288 let text_layout_details = &self.text_layout_details(window);
11289
11290 self.change_selections(Some(autoscroll), window, cx, |s| {
11291 s.move_with(|map, selection| {
11292 if !selection.is_empty() {
11293 selection.goal = SelectionGoal::None;
11294 }
11295 let (cursor, goal) = movement::up_by_rows(
11296 map,
11297 selection.end,
11298 row_count,
11299 selection.goal,
11300 false,
11301 text_layout_details,
11302 );
11303 selection.collapse_to(cursor, goal);
11304 });
11305 });
11306 }
11307
11308 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11309 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11310 let text_layout_details = &self.text_layout_details(window);
11311 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11312 s.move_heads_with(|map, head, goal| {
11313 movement::up(map, head, goal, false, text_layout_details)
11314 })
11315 })
11316 }
11317
11318 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11319 self.take_rename(true, window, cx);
11320
11321 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11322 cx.propagate();
11323 return;
11324 }
11325
11326 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11327
11328 let text_layout_details = &self.text_layout_details(window);
11329 let selection_count = self.selections.count();
11330 let first_selection = self.selections.first_anchor();
11331
11332 self.change_selections(Some(Autoscroll::fit()), 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::down(
11338 map,
11339 selection.end,
11340 selection.goal,
11341 false,
11342 text_layout_details,
11343 );
11344 selection.collapse_to(cursor, goal);
11345 });
11346 });
11347
11348 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11349 {
11350 cx.propagate();
11351 }
11352 }
11353
11354 pub fn select_page_down(
11355 &mut self,
11356 _: &SelectPageDown,
11357 window: &mut Window,
11358 cx: &mut Context<Self>,
11359 ) {
11360 let Some(row_count) = self.visible_row_count() else {
11361 return;
11362 };
11363
11364 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11365
11366 let text_layout_details = &self.text_layout_details(window);
11367
11368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11369 s.move_heads_with(|map, head, goal| {
11370 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11371 })
11372 })
11373 }
11374
11375 pub fn move_page_down(
11376 &mut self,
11377 action: &MovePageDown,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 if self.take_rename(true, window, cx).is_some() {
11382 return;
11383 }
11384
11385 if self
11386 .context_menu
11387 .borrow_mut()
11388 .as_mut()
11389 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11390 .unwrap_or(false)
11391 {
11392 return;
11393 }
11394
11395 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11396 cx.propagate();
11397 return;
11398 }
11399
11400 let Some(row_count) = self.visible_row_count() else {
11401 return;
11402 };
11403
11404 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11405
11406 let autoscroll = if action.center_cursor {
11407 Autoscroll::center()
11408 } else {
11409 Autoscroll::fit()
11410 };
11411
11412 let text_layout_details = &self.text_layout_details(window);
11413 self.change_selections(Some(autoscroll), window, cx, |s| {
11414 s.move_with(|map, selection| {
11415 if !selection.is_empty() {
11416 selection.goal = SelectionGoal::None;
11417 }
11418 let (cursor, goal) = movement::down_by_rows(
11419 map,
11420 selection.end,
11421 row_count,
11422 selection.goal,
11423 false,
11424 text_layout_details,
11425 );
11426 selection.collapse_to(cursor, goal);
11427 });
11428 });
11429 }
11430
11431 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11432 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11433 let text_layout_details = &self.text_layout_details(window);
11434 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11435 s.move_heads_with(|map, head, goal| {
11436 movement::down(map, head, goal, false, text_layout_details)
11437 })
11438 });
11439 }
11440
11441 pub fn context_menu_first(
11442 &mut self,
11443 _: &ContextMenuFirst,
11444 _window: &mut Window,
11445 cx: &mut Context<Self>,
11446 ) {
11447 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11448 context_menu.select_first(self.completion_provider.as_deref(), cx);
11449 }
11450 }
11451
11452 pub fn context_menu_prev(
11453 &mut self,
11454 _: &ContextMenuPrevious,
11455 _window: &mut Window,
11456 cx: &mut Context<Self>,
11457 ) {
11458 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11459 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11460 }
11461 }
11462
11463 pub fn context_menu_next(
11464 &mut self,
11465 _: &ContextMenuNext,
11466 _window: &mut Window,
11467 cx: &mut Context<Self>,
11468 ) {
11469 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11470 context_menu.select_next(self.completion_provider.as_deref(), cx);
11471 }
11472 }
11473
11474 pub fn context_menu_last(
11475 &mut self,
11476 _: &ContextMenuLast,
11477 _window: &mut Window,
11478 cx: &mut Context<Self>,
11479 ) {
11480 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11481 context_menu.select_last(self.completion_provider.as_deref(), cx);
11482 }
11483 }
11484
11485 pub fn move_to_previous_word_start(
11486 &mut self,
11487 _: &MoveToPreviousWordStart,
11488 window: &mut Window,
11489 cx: &mut Context<Self>,
11490 ) {
11491 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11493 s.move_cursors_with(|map, head, _| {
11494 (
11495 movement::previous_word_start(map, head),
11496 SelectionGoal::None,
11497 )
11498 });
11499 })
11500 }
11501
11502 pub fn move_to_previous_subword_start(
11503 &mut self,
11504 _: &MoveToPreviousSubwordStart,
11505 window: &mut Window,
11506 cx: &mut Context<Self>,
11507 ) {
11508 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11509 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11510 s.move_cursors_with(|map, head, _| {
11511 (
11512 movement::previous_subword_start(map, head),
11513 SelectionGoal::None,
11514 )
11515 });
11516 })
11517 }
11518
11519 pub fn select_to_previous_word_start(
11520 &mut self,
11521 _: &SelectToPreviousWordStart,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11526 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11527 s.move_heads_with(|map, head, _| {
11528 (
11529 movement::previous_word_start(map, head),
11530 SelectionGoal::None,
11531 )
11532 });
11533 })
11534 }
11535
11536 pub fn select_to_previous_subword_start(
11537 &mut self,
11538 _: &SelectToPreviousSubwordStart,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11543 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11544 s.move_heads_with(|map, head, _| {
11545 (
11546 movement::previous_subword_start(map, head),
11547 SelectionGoal::None,
11548 )
11549 });
11550 })
11551 }
11552
11553 pub fn delete_to_previous_word_start(
11554 &mut self,
11555 action: &DeleteToPreviousWordStart,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11560 self.transact(window, cx, |this, window, cx| {
11561 this.select_autoclose_pair(window, cx);
11562 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11563 s.move_with(|map, selection| {
11564 if selection.is_empty() {
11565 let cursor = if action.ignore_newlines {
11566 movement::previous_word_start(map, selection.head())
11567 } else {
11568 movement::previous_word_start_or_newline(map, selection.head())
11569 };
11570 selection.set_head(cursor, SelectionGoal::None);
11571 }
11572 });
11573 });
11574 this.insert("", window, cx);
11575 });
11576 }
11577
11578 pub fn delete_to_previous_subword_start(
11579 &mut self,
11580 _: &DeleteToPreviousSubwordStart,
11581 window: &mut Window,
11582 cx: &mut Context<Self>,
11583 ) {
11584 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11585 self.transact(window, cx, |this, window, cx| {
11586 this.select_autoclose_pair(window, cx);
11587 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11588 s.move_with(|map, selection| {
11589 if selection.is_empty() {
11590 let cursor = movement::previous_subword_start(map, selection.head());
11591 selection.set_head(cursor, SelectionGoal::None);
11592 }
11593 });
11594 });
11595 this.insert("", window, cx);
11596 });
11597 }
11598
11599 pub fn move_to_next_word_end(
11600 &mut self,
11601 _: &MoveToNextWordEnd,
11602 window: &mut Window,
11603 cx: &mut Context<Self>,
11604 ) {
11605 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11606 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11607 s.move_cursors_with(|map, head, _| {
11608 (movement::next_word_end(map, head), SelectionGoal::None)
11609 });
11610 })
11611 }
11612
11613 pub fn move_to_next_subword_end(
11614 &mut self,
11615 _: &MoveToNextSubwordEnd,
11616 window: &mut Window,
11617 cx: &mut Context<Self>,
11618 ) {
11619 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11621 s.move_cursors_with(|map, head, _| {
11622 (movement::next_subword_end(map, head), SelectionGoal::None)
11623 });
11624 })
11625 }
11626
11627 pub fn select_to_next_word_end(
11628 &mut self,
11629 _: &SelectToNextWordEnd,
11630 window: &mut Window,
11631 cx: &mut Context<Self>,
11632 ) {
11633 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11635 s.move_heads_with(|map, head, _| {
11636 (movement::next_word_end(map, head), SelectionGoal::None)
11637 });
11638 })
11639 }
11640
11641 pub fn select_to_next_subword_end(
11642 &mut self,
11643 _: &SelectToNextSubwordEnd,
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_heads_with(|map, head, _| {
11650 (movement::next_subword_end(map, head), SelectionGoal::None)
11651 });
11652 })
11653 }
11654
11655 pub fn delete_to_next_word_end(
11656 &mut self,
11657 action: &DeleteToNextWordEnd,
11658 window: &mut Window,
11659 cx: &mut Context<Self>,
11660 ) {
11661 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11662 self.transact(window, cx, |this, window, cx| {
11663 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11664 s.move_with(|map, selection| {
11665 if selection.is_empty() {
11666 let cursor = if action.ignore_newlines {
11667 movement::next_word_end(map, selection.head())
11668 } else {
11669 movement::next_word_end_or_newline(map, selection.head())
11670 };
11671 selection.set_head(cursor, SelectionGoal::None);
11672 }
11673 });
11674 });
11675 this.insert("", window, cx);
11676 });
11677 }
11678
11679 pub fn delete_to_next_subword_end(
11680 &mut self,
11681 _: &DeleteToNextSubwordEnd,
11682 window: &mut Window,
11683 cx: &mut Context<Self>,
11684 ) {
11685 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11686 self.transact(window, cx, |this, window, cx| {
11687 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11688 s.move_with(|map, selection| {
11689 if selection.is_empty() {
11690 let cursor = movement::next_subword_end(map, selection.head());
11691 selection.set_head(cursor, SelectionGoal::None);
11692 }
11693 });
11694 });
11695 this.insert("", window, cx);
11696 });
11697 }
11698
11699 pub fn move_to_beginning_of_line(
11700 &mut self,
11701 action: &MoveToBeginningOfLine,
11702 window: &mut Window,
11703 cx: &mut Context<Self>,
11704 ) {
11705 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11706 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11707 s.move_cursors_with(|map, head, _| {
11708 (
11709 movement::indented_line_beginning(
11710 map,
11711 head,
11712 action.stop_at_soft_wraps,
11713 action.stop_at_indent,
11714 ),
11715 SelectionGoal::None,
11716 )
11717 });
11718 })
11719 }
11720
11721 pub fn select_to_beginning_of_line(
11722 &mut self,
11723 action: &SelectToBeginningOfLine,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11728 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11729 s.move_heads_with(|map, head, _| {
11730 (
11731 movement::indented_line_beginning(
11732 map,
11733 head,
11734 action.stop_at_soft_wraps,
11735 action.stop_at_indent,
11736 ),
11737 SelectionGoal::None,
11738 )
11739 });
11740 });
11741 }
11742
11743 pub fn delete_to_beginning_of_line(
11744 &mut self,
11745 action: &DeleteToBeginningOfLine,
11746 window: &mut Window,
11747 cx: &mut Context<Self>,
11748 ) {
11749 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11750 self.transact(window, cx, |this, window, cx| {
11751 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11752 s.move_with(|_, selection| {
11753 selection.reversed = true;
11754 });
11755 });
11756
11757 this.select_to_beginning_of_line(
11758 &SelectToBeginningOfLine {
11759 stop_at_soft_wraps: false,
11760 stop_at_indent: action.stop_at_indent,
11761 },
11762 window,
11763 cx,
11764 );
11765 this.backspace(&Backspace, window, cx);
11766 });
11767 }
11768
11769 pub fn move_to_end_of_line(
11770 &mut self,
11771 action: &MoveToEndOfLine,
11772 window: &mut Window,
11773 cx: &mut Context<Self>,
11774 ) {
11775 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11777 s.move_cursors_with(|map, head, _| {
11778 (
11779 movement::line_end(map, head, action.stop_at_soft_wraps),
11780 SelectionGoal::None,
11781 )
11782 });
11783 })
11784 }
11785
11786 pub fn select_to_end_of_line(
11787 &mut self,
11788 action: &SelectToEndOfLine,
11789 window: &mut Window,
11790 cx: &mut Context<Self>,
11791 ) {
11792 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.move_heads_with(|map, head, _| {
11795 (
11796 movement::line_end(map, head, action.stop_at_soft_wraps),
11797 SelectionGoal::None,
11798 )
11799 });
11800 })
11801 }
11802
11803 pub fn delete_to_end_of_line(
11804 &mut self,
11805 _: &DeleteToEndOfLine,
11806 window: &mut Window,
11807 cx: &mut Context<Self>,
11808 ) {
11809 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11810 self.transact(window, cx, |this, window, cx| {
11811 this.select_to_end_of_line(
11812 &SelectToEndOfLine {
11813 stop_at_soft_wraps: false,
11814 },
11815 window,
11816 cx,
11817 );
11818 this.delete(&Delete, window, cx);
11819 });
11820 }
11821
11822 pub fn cut_to_end_of_line(
11823 &mut self,
11824 _: &CutToEndOfLine,
11825 window: &mut Window,
11826 cx: &mut Context<Self>,
11827 ) {
11828 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11829 self.transact(window, cx, |this, window, cx| {
11830 this.select_to_end_of_line(
11831 &SelectToEndOfLine {
11832 stop_at_soft_wraps: false,
11833 },
11834 window,
11835 cx,
11836 );
11837 this.cut(&Cut, window, cx);
11838 });
11839 }
11840
11841 pub fn move_to_start_of_paragraph(
11842 &mut self,
11843 _: &MoveToStartOfParagraph,
11844 window: &mut Window,
11845 cx: &mut Context<Self>,
11846 ) {
11847 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11848 cx.propagate();
11849 return;
11850 }
11851 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11853 s.move_with(|map, selection| {
11854 selection.collapse_to(
11855 movement::start_of_paragraph(map, selection.head(), 1),
11856 SelectionGoal::None,
11857 )
11858 });
11859 })
11860 }
11861
11862 pub fn move_to_end_of_paragraph(
11863 &mut self,
11864 _: &MoveToEndOfParagraph,
11865 window: &mut Window,
11866 cx: &mut Context<Self>,
11867 ) {
11868 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11869 cx.propagate();
11870 return;
11871 }
11872 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11873 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11874 s.move_with(|map, selection| {
11875 selection.collapse_to(
11876 movement::end_of_paragraph(map, selection.head(), 1),
11877 SelectionGoal::None,
11878 )
11879 });
11880 })
11881 }
11882
11883 pub fn select_to_start_of_paragraph(
11884 &mut self,
11885 _: &SelectToStartOfParagraph,
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_heads_with(|map, head, _| {
11896 (
11897 movement::start_of_paragraph(map, head, 1),
11898 SelectionGoal::None,
11899 )
11900 });
11901 })
11902 }
11903
11904 pub fn select_to_end_of_paragraph(
11905 &mut self,
11906 _: &SelectToEndOfParagraph,
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_heads_with(|map, head, _| {
11917 (
11918 movement::end_of_paragraph(map, head, 1),
11919 SelectionGoal::None,
11920 )
11921 });
11922 })
11923 }
11924
11925 pub fn move_to_start_of_excerpt(
11926 &mut self,
11927 _: &MoveToStartOfExcerpt,
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_with(|map, selection| {
11938 selection.collapse_to(
11939 movement::start_of_excerpt(
11940 map,
11941 selection.head(),
11942 workspace::searchable::Direction::Prev,
11943 ),
11944 SelectionGoal::None,
11945 )
11946 });
11947 })
11948 }
11949
11950 pub fn move_to_start_of_next_excerpt(
11951 &mut self,
11952 _: &MoveToStartOfNextExcerpt,
11953 window: &mut Window,
11954 cx: &mut Context<Self>,
11955 ) {
11956 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11957 cx.propagate();
11958 return;
11959 }
11960
11961 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11962 s.move_with(|map, selection| {
11963 selection.collapse_to(
11964 movement::start_of_excerpt(
11965 map,
11966 selection.head(),
11967 workspace::searchable::Direction::Next,
11968 ),
11969 SelectionGoal::None,
11970 )
11971 });
11972 })
11973 }
11974
11975 pub fn move_to_end_of_excerpt(
11976 &mut self,
11977 _: &MoveToEndOfExcerpt,
11978 window: &mut Window,
11979 cx: &mut Context<Self>,
11980 ) {
11981 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11982 cx.propagate();
11983 return;
11984 }
11985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11986 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11987 s.move_with(|map, selection| {
11988 selection.collapse_to(
11989 movement::end_of_excerpt(
11990 map,
11991 selection.head(),
11992 workspace::searchable::Direction::Next,
11993 ),
11994 SelectionGoal::None,
11995 )
11996 });
11997 })
11998 }
11999
12000 pub fn move_to_end_of_previous_excerpt(
12001 &mut self,
12002 _: &MoveToEndOfPreviousExcerpt,
12003 window: &mut Window,
12004 cx: &mut Context<Self>,
12005 ) {
12006 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12007 cx.propagate();
12008 return;
12009 }
12010 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12011 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12012 s.move_with(|map, selection| {
12013 selection.collapse_to(
12014 movement::end_of_excerpt(
12015 map,
12016 selection.head(),
12017 workspace::searchable::Direction::Prev,
12018 ),
12019 SelectionGoal::None,
12020 )
12021 });
12022 })
12023 }
12024
12025 pub fn select_to_start_of_excerpt(
12026 &mut self,
12027 _: &SelectToStartOfExcerpt,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 ) {
12031 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12032 cx.propagate();
12033 return;
12034 }
12035 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12037 s.move_heads_with(|map, head, _| {
12038 (
12039 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12040 SelectionGoal::None,
12041 )
12042 });
12043 })
12044 }
12045
12046 pub fn select_to_start_of_next_excerpt(
12047 &mut self,
12048 _: &SelectToStartOfNextExcerpt,
12049 window: &mut Window,
12050 cx: &mut Context<Self>,
12051 ) {
12052 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12053 cx.propagate();
12054 return;
12055 }
12056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12057 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12058 s.move_heads_with(|map, head, _| {
12059 (
12060 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12061 SelectionGoal::None,
12062 )
12063 });
12064 })
12065 }
12066
12067 pub fn select_to_end_of_excerpt(
12068 &mut self,
12069 _: &SelectToEndOfExcerpt,
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::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12082 SelectionGoal::None,
12083 )
12084 });
12085 })
12086 }
12087
12088 pub fn select_to_end_of_previous_excerpt(
12089 &mut self,
12090 _: &SelectToEndOfPreviousExcerpt,
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::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12103 SelectionGoal::None,
12104 )
12105 });
12106 })
12107 }
12108
12109 pub fn move_to_beginning(
12110 &mut self,
12111 _: &MoveToBeginning,
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.select_ranges(vec![0..0]);
12122 });
12123 }
12124
12125 pub fn select_to_beginning(
12126 &mut self,
12127 _: &SelectToBeginning,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 let mut selection = self.selections.last::<Point>(cx);
12132 selection.set_head(Point::zero(), SelectionGoal::None);
12133 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12135 s.select(vec![selection]);
12136 });
12137 }
12138
12139 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12140 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12141 cx.propagate();
12142 return;
12143 }
12144 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12145 let cursor = self.buffer.read(cx).read(cx).len();
12146 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12147 s.select_ranges(vec![cursor..cursor])
12148 });
12149 }
12150
12151 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12152 self.nav_history = nav_history;
12153 }
12154
12155 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12156 self.nav_history.as_ref()
12157 }
12158
12159 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12160 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12161 }
12162
12163 fn push_to_nav_history(
12164 &mut self,
12165 cursor_anchor: Anchor,
12166 new_position: Option<Point>,
12167 is_deactivate: bool,
12168 cx: &mut Context<Self>,
12169 ) {
12170 if let Some(nav_history) = self.nav_history.as_mut() {
12171 let buffer = self.buffer.read(cx).read(cx);
12172 let cursor_position = cursor_anchor.to_point(&buffer);
12173 let scroll_state = self.scroll_manager.anchor();
12174 let scroll_top_row = scroll_state.top_row(&buffer);
12175 drop(buffer);
12176
12177 if let Some(new_position) = new_position {
12178 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12179 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12180 return;
12181 }
12182 }
12183
12184 nav_history.push(
12185 Some(NavigationData {
12186 cursor_anchor,
12187 cursor_position,
12188 scroll_anchor: scroll_state,
12189 scroll_top_row,
12190 }),
12191 cx,
12192 );
12193 cx.emit(EditorEvent::PushedToNavHistory {
12194 anchor: cursor_anchor,
12195 is_deactivate,
12196 })
12197 }
12198 }
12199
12200 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12202 let buffer = self.buffer.read(cx).snapshot(cx);
12203 let mut selection = self.selections.first::<usize>(cx);
12204 selection.set_head(buffer.len(), SelectionGoal::None);
12205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12206 s.select(vec![selection]);
12207 });
12208 }
12209
12210 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12211 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12212 let end = self.buffer.read(cx).read(cx).len();
12213 self.change_selections(None, window, cx, |s| {
12214 s.select_ranges(vec![0..end]);
12215 });
12216 }
12217
12218 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12221 let mut selections = self.selections.all::<Point>(cx);
12222 let max_point = display_map.buffer_snapshot.max_point();
12223 for selection in &mut selections {
12224 let rows = selection.spanned_rows(true, &display_map);
12225 selection.start = Point::new(rows.start.0, 0);
12226 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12227 selection.reversed = false;
12228 }
12229 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12230 s.select(selections);
12231 });
12232 }
12233
12234 pub fn split_selection_into_lines(
12235 &mut self,
12236 _: &SplitSelectionIntoLines,
12237 window: &mut Window,
12238 cx: &mut Context<Self>,
12239 ) {
12240 let selections = self
12241 .selections
12242 .all::<Point>(cx)
12243 .into_iter()
12244 .map(|selection| selection.start..selection.end)
12245 .collect::<Vec<_>>();
12246 self.unfold_ranges(&selections, true, true, cx);
12247
12248 let mut new_selection_ranges = Vec::new();
12249 {
12250 let buffer = self.buffer.read(cx).read(cx);
12251 for selection in selections {
12252 for row in selection.start.row..selection.end.row {
12253 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12254 new_selection_ranges.push(cursor..cursor);
12255 }
12256
12257 let is_multiline_selection = selection.start.row != selection.end.row;
12258 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12259 // so this action feels more ergonomic when paired with other selection operations
12260 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12261 if !should_skip_last {
12262 new_selection_ranges.push(selection.end..selection.end);
12263 }
12264 }
12265 }
12266 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12267 s.select_ranges(new_selection_ranges);
12268 });
12269 }
12270
12271 pub fn add_selection_above(
12272 &mut self,
12273 _: &AddSelectionAbove,
12274 window: &mut Window,
12275 cx: &mut Context<Self>,
12276 ) {
12277 self.add_selection(true, window, cx);
12278 }
12279
12280 pub fn add_selection_below(
12281 &mut self,
12282 _: &AddSelectionBelow,
12283 window: &mut Window,
12284 cx: &mut Context<Self>,
12285 ) {
12286 self.add_selection(false, window, cx);
12287 }
12288
12289 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12290 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12291
12292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12293 let mut selections = self.selections.all::<Point>(cx);
12294 let text_layout_details = self.text_layout_details(window);
12295 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12296 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12297 let range = oldest_selection.display_range(&display_map).sorted();
12298
12299 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12300 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12301 let positions = start_x.min(end_x)..start_x.max(end_x);
12302
12303 selections.clear();
12304 let mut stack = Vec::new();
12305 for row in range.start.row().0..=range.end.row().0 {
12306 if let Some(selection) = self.selections.build_columnar_selection(
12307 &display_map,
12308 DisplayRow(row),
12309 &positions,
12310 oldest_selection.reversed,
12311 &text_layout_details,
12312 ) {
12313 stack.push(selection.id);
12314 selections.push(selection);
12315 }
12316 }
12317
12318 if above {
12319 stack.reverse();
12320 }
12321
12322 AddSelectionsState { above, stack }
12323 });
12324
12325 let last_added_selection = *state.stack.last().unwrap();
12326 let mut new_selections = Vec::new();
12327 if above == state.above {
12328 let end_row = if above {
12329 DisplayRow(0)
12330 } else {
12331 display_map.max_point().row()
12332 };
12333
12334 'outer: for selection in selections {
12335 if selection.id == last_added_selection {
12336 let range = selection.display_range(&display_map).sorted();
12337 debug_assert_eq!(range.start.row(), range.end.row());
12338 let mut row = range.start.row();
12339 let positions =
12340 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12341 px(start)..px(end)
12342 } else {
12343 let start_x =
12344 display_map.x_for_display_point(range.start, &text_layout_details);
12345 let end_x =
12346 display_map.x_for_display_point(range.end, &text_layout_details);
12347 start_x.min(end_x)..start_x.max(end_x)
12348 };
12349
12350 while row != end_row {
12351 if above {
12352 row.0 -= 1;
12353 } else {
12354 row.0 += 1;
12355 }
12356
12357 if let Some(new_selection) = self.selections.build_columnar_selection(
12358 &display_map,
12359 row,
12360 &positions,
12361 selection.reversed,
12362 &text_layout_details,
12363 ) {
12364 state.stack.push(new_selection.id);
12365 if above {
12366 new_selections.push(new_selection);
12367 new_selections.push(selection);
12368 } else {
12369 new_selections.push(selection);
12370 new_selections.push(new_selection);
12371 }
12372
12373 continue 'outer;
12374 }
12375 }
12376 }
12377
12378 new_selections.push(selection);
12379 }
12380 } else {
12381 new_selections = selections;
12382 new_selections.retain(|s| s.id != last_added_selection);
12383 state.stack.pop();
12384 }
12385
12386 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12387 s.select(new_selections);
12388 });
12389 if state.stack.len() > 1 {
12390 self.add_selections_state = Some(state);
12391 }
12392 }
12393
12394 fn select_match_ranges(
12395 &mut self,
12396 range: Range<usize>,
12397 reversed: bool,
12398 replace_newest: bool,
12399 auto_scroll: Option<Autoscroll>,
12400 window: &mut Window,
12401 cx: &mut Context<Editor>,
12402 ) {
12403 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12404 self.change_selections(auto_scroll, window, cx, |s| {
12405 if replace_newest {
12406 s.delete(s.newest_anchor().id);
12407 }
12408 if reversed {
12409 s.insert_range(range.end..range.start);
12410 } else {
12411 s.insert_range(range);
12412 }
12413 });
12414 }
12415
12416 pub fn select_next_match_internal(
12417 &mut self,
12418 display_map: &DisplaySnapshot,
12419 replace_newest: bool,
12420 autoscroll: Option<Autoscroll>,
12421 window: &mut Window,
12422 cx: &mut Context<Self>,
12423 ) -> Result<()> {
12424 let buffer = &display_map.buffer_snapshot;
12425 let mut selections = self.selections.all::<usize>(cx);
12426 if let Some(mut select_next_state) = self.select_next_state.take() {
12427 let query = &select_next_state.query;
12428 if !select_next_state.done {
12429 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12430 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12431 let mut next_selected_range = None;
12432
12433 let bytes_after_last_selection =
12434 buffer.bytes_in_range(last_selection.end..buffer.len());
12435 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12436 let query_matches = query
12437 .stream_find_iter(bytes_after_last_selection)
12438 .map(|result| (last_selection.end, result))
12439 .chain(
12440 query
12441 .stream_find_iter(bytes_before_first_selection)
12442 .map(|result| (0, result)),
12443 );
12444
12445 for (start_offset, query_match) in query_matches {
12446 let query_match = query_match.unwrap(); // can only fail due to I/O
12447 let offset_range =
12448 start_offset + query_match.start()..start_offset + query_match.end();
12449 let display_range = offset_range.start.to_display_point(display_map)
12450 ..offset_range.end.to_display_point(display_map);
12451
12452 if !select_next_state.wordwise
12453 || (!movement::is_inside_word(display_map, display_range.start)
12454 && !movement::is_inside_word(display_map, display_range.end))
12455 {
12456 // TODO: This is n^2, because we might check all the selections
12457 if !selections
12458 .iter()
12459 .any(|selection| selection.range().overlaps(&offset_range))
12460 {
12461 next_selected_range = Some(offset_range);
12462 break;
12463 }
12464 }
12465 }
12466
12467 if let Some(next_selected_range) = next_selected_range {
12468 self.select_match_ranges(
12469 next_selected_range,
12470 last_selection.reversed,
12471 replace_newest,
12472 autoscroll,
12473 window,
12474 cx,
12475 );
12476 } else {
12477 select_next_state.done = true;
12478 }
12479 }
12480
12481 self.select_next_state = Some(select_next_state);
12482 } else {
12483 let mut only_carets = true;
12484 let mut same_text_selected = true;
12485 let mut selected_text = None;
12486
12487 let mut selections_iter = selections.iter().peekable();
12488 while let Some(selection) = selections_iter.next() {
12489 if selection.start != selection.end {
12490 only_carets = false;
12491 }
12492
12493 if same_text_selected {
12494 if selected_text.is_none() {
12495 selected_text =
12496 Some(buffer.text_for_range(selection.range()).collect::<String>());
12497 }
12498
12499 if let Some(next_selection) = selections_iter.peek() {
12500 if next_selection.range().len() == selection.range().len() {
12501 let next_selected_text = buffer
12502 .text_for_range(next_selection.range())
12503 .collect::<String>();
12504 if Some(next_selected_text) != selected_text {
12505 same_text_selected = false;
12506 selected_text = None;
12507 }
12508 } else {
12509 same_text_selected = false;
12510 selected_text = None;
12511 }
12512 }
12513 }
12514 }
12515
12516 if only_carets {
12517 for selection in &mut selections {
12518 let word_range = movement::surrounding_word(
12519 display_map,
12520 selection.start.to_display_point(display_map),
12521 );
12522 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12523 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12524 selection.goal = SelectionGoal::None;
12525 selection.reversed = false;
12526 self.select_match_ranges(
12527 selection.start..selection.end,
12528 selection.reversed,
12529 replace_newest,
12530 autoscroll,
12531 window,
12532 cx,
12533 );
12534 }
12535
12536 if selections.len() == 1 {
12537 let selection = selections
12538 .last()
12539 .expect("ensured that there's only one selection");
12540 let query = buffer
12541 .text_for_range(selection.start..selection.end)
12542 .collect::<String>();
12543 let is_empty = query.is_empty();
12544 let select_state = SelectNextState {
12545 query: AhoCorasick::new(&[query])?,
12546 wordwise: true,
12547 done: is_empty,
12548 };
12549 self.select_next_state = Some(select_state);
12550 } else {
12551 self.select_next_state = None;
12552 }
12553 } else if let Some(selected_text) = selected_text {
12554 self.select_next_state = Some(SelectNextState {
12555 query: AhoCorasick::new(&[selected_text])?,
12556 wordwise: false,
12557 done: false,
12558 });
12559 self.select_next_match_internal(
12560 display_map,
12561 replace_newest,
12562 autoscroll,
12563 window,
12564 cx,
12565 )?;
12566 }
12567 }
12568 Ok(())
12569 }
12570
12571 pub fn select_all_matches(
12572 &mut self,
12573 _action: &SelectAllMatches,
12574 window: &mut Window,
12575 cx: &mut Context<Self>,
12576 ) -> Result<()> {
12577 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12578
12579 self.push_to_selection_history();
12580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12581
12582 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12583 let Some(select_next_state) = self.select_next_state.as_mut() else {
12584 return Ok(());
12585 };
12586 if select_next_state.done {
12587 return Ok(());
12588 }
12589
12590 let mut new_selections = Vec::new();
12591
12592 let reversed = self.selections.oldest::<usize>(cx).reversed;
12593 let buffer = &display_map.buffer_snapshot;
12594 let query_matches = select_next_state
12595 .query
12596 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12597
12598 for query_match in query_matches.into_iter() {
12599 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12600 let offset_range = if reversed {
12601 query_match.end()..query_match.start()
12602 } else {
12603 query_match.start()..query_match.end()
12604 };
12605 let display_range = offset_range.start.to_display_point(&display_map)
12606 ..offset_range.end.to_display_point(&display_map);
12607
12608 if !select_next_state.wordwise
12609 || (!movement::is_inside_word(&display_map, display_range.start)
12610 && !movement::is_inside_word(&display_map, display_range.end))
12611 {
12612 new_selections.push(offset_range.start..offset_range.end);
12613 }
12614 }
12615
12616 select_next_state.done = true;
12617 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12618 self.change_selections(None, window, cx, |selections| {
12619 selections.select_ranges(new_selections)
12620 });
12621
12622 Ok(())
12623 }
12624
12625 pub fn select_next(
12626 &mut self,
12627 action: &SelectNext,
12628 window: &mut Window,
12629 cx: &mut Context<Self>,
12630 ) -> Result<()> {
12631 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12632 self.push_to_selection_history();
12633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12634 self.select_next_match_internal(
12635 &display_map,
12636 action.replace_newest,
12637 Some(Autoscroll::newest()),
12638 window,
12639 cx,
12640 )?;
12641 Ok(())
12642 }
12643
12644 pub fn select_previous(
12645 &mut self,
12646 action: &SelectPrevious,
12647 window: &mut Window,
12648 cx: &mut Context<Self>,
12649 ) -> Result<()> {
12650 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12651 self.push_to_selection_history();
12652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12653 let buffer = &display_map.buffer_snapshot;
12654 let mut selections = self.selections.all::<usize>(cx);
12655 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12656 let query = &select_prev_state.query;
12657 if !select_prev_state.done {
12658 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12659 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12660 let mut next_selected_range = None;
12661 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12662 let bytes_before_last_selection =
12663 buffer.reversed_bytes_in_range(0..last_selection.start);
12664 let bytes_after_first_selection =
12665 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12666 let query_matches = query
12667 .stream_find_iter(bytes_before_last_selection)
12668 .map(|result| (last_selection.start, result))
12669 .chain(
12670 query
12671 .stream_find_iter(bytes_after_first_selection)
12672 .map(|result| (buffer.len(), result)),
12673 );
12674 for (end_offset, query_match) in query_matches {
12675 let query_match = query_match.unwrap(); // can only fail due to I/O
12676 let offset_range =
12677 end_offset - query_match.end()..end_offset - query_match.start();
12678 let display_range = offset_range.start.to_display_point(&display_map)
12679 ..offset_range.end.to_display_point(&display_map);
12680
12681 if !select_prev_state.wordwise
12682 || (!movement::is_inside_word(&display_map, display_range.start)
12683 && !movement::is_inside_word(&display_map, display_range.end))
12684 {
12685 next_selected_range = Some(offset_range);
12686 break;
12687 }
12688 }
12689
12690 if let Some(next_selected_range) = next_selected_range {
12691 self.select_match_ranges(
12692 next_selected_range,
12693 last_selection.reversed,
12694 action.replace_newest,
12695 Some(Autoscroll::newest()),
12696 window,
12697 cx,
12698 );
12699 } else {
12700 select_prev_state.done = true;
12701 }
12702 }
12703
12704 self.select_prev_state = Some(select_prev_state);
12705 } else {
12706 let mut only_carets = true;
12707 let mut same_text_selected = true;
12708 let mut selected_text = None;
12709
12710 let mut selections_iter = selections.iter().peekable();
12711 while let Some(selection) = selections_iter.next() {
12712 if selection.start != selection.end {
12713 only_carets = false;
12714 }
12715
12716 if same_text_selected {
12717 if selected_text.is_none() {
12718 selected_text =
12719 Some(buffer.text_for_range(selection.range()).collect::<String>());
12720 }
12721
12722 if let Some(next_selection) = selections_iter.peek() {
12723 if next_selection.range().len() == selection.range().len() {
12724 let next_selected_text = buffer
12725 .text_for_range(next_selection.range())
12726 .collect::<String>();
12727 if Some(next_selected_text) != selected_text {
12728 same_text_selected = false;
12729 selected_text = None;
12730 }
12731 } else {
12732 same_text_selected = false;
12733 selected_text = None;
12734 }
12735 }
12736 }
12737 }
12738
12739 if only_carets {
12740 for selection in &mut selections {
12741 let word_range = movement::surrounding_word(
12742 &display_map,
12743 selection.start.to_display_point(&display_map),
12744 );
12745 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12746 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12747 selection.goal = SelectionGoal::None;
12748 selection.reversed = false;
12749 self.select_match_ranges(
12750 selection.start..selection.end,
12751 selection.reversed,
12752 action.replace_newest,
12753 Some(Autoscroll::newest()),
12754 window,
12755 cx,
12756 );
12757 }
12758 if selections.len() == 1 {
12759 let selection = selections
12760 .last()
12761 .expect("ensured that there's only one selection");
12762 let query = buffer
12763 .text_for_range(selection.start..selection.end)
12764 .collect::<String>();
12765 let is_empty = query.is_empty();
12766 let select_state = SelectNextState {
12767 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12768 wordwise: true,
12769 done: is_empty,
12770 };
12771 self.select_prev_state = Some(select_state);
12772 } else {
12773 self.select_prev_state = None;
12774 }
12775 } else if let Some(selected_text) = selected_text {
12776 self.select_prev_state = Some(SelectNextState {
12777 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12778 wordwise: false,
12779 done: false,
12780 });
12781 self.select_previous(action, window, cx)?;
12782 }
12783 }
12784 Ok(())
12785 }
12786
12787 pub fn find_next_match(
12788 &mut self,
12789 _: &FindNextMatch,
12790 window: &mut Window,
12791 cx: &mut Context<Self>,
12792 ) -> Result<()> {
12793 let selections = self.selections.disjoint_anchors();
12794 match selections.first() {
12795 Some(first) if selections.len() >= 2 => {
12796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12797 s.select_ranges([first.range()]);
12798 });
12799 }
12800 _ => self.select_next(
12801 &SelectNext {
12802 replace_newest: true,
12803 },
12804 window,
12805 cx,
12806 )?,
12807 }
12808 Ok(())
12809 }
12810
12811 pub fn find_previous_match(
12812 &mut self,
12813 _: &FindPreviousMatch,
12814 window: &mut Window,
12815 cx: &mut Context<Self>,
12816 ) -> Result<()> {
12817 let selections = self.selections.disjoint_anchors();
12818 match selections.last() {
12819 Some(last) if selections.len() >= 2 => {
12820 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12821 s.select_ranges([last.range()]);
12822 });
12823 }
12824 _ => self.select_previous(
12825 &SelectPrevious {
12826 replace_newest: true,
12827 },
12828 window,
12829 cx,
12830 )?,
12831 }
12832 Ok(())
12833 }
12834
12835 pub fn toggle_comments(
12836 &mut self,
12837 action: &ToggleComments,
12838 window: &mut Window,
12839 cx: &mut Context<Self>,
12840 ) {
12841 if self.read_only(cx) {
12842 return;
12843 }
12844 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12845 let text_layout_details = &self.text_layout_details(window);
12846 self.transact(window, cx, |this, window, cx| {
12847 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12848 let mut edits = Vec::new();
12849 let mut selection_edit_ranges = Vec::new();
12850 let mut last_toggled_row = None;
12851 let snapshot = this.buffer.read(cx).read(cx);
12852 let empty_str: Arc<str> = Arc::default();
12853 let mut suffixes_inserted = Vec::new();
12854 let ignore_indent = action.ignore_indent;
12855
12856 fn comment_prefix_range(
12857 snapshot: &MultiBufferSnapshot,
12858 row: MultiBufferRow,
12859 comment_prefix: &str,
12860 comment_prefix_whitespace: &str,
12861 ignore_indent: bool,
12862 ) -> Range<Point> {
12863 let indent_size = if ignore_indent {
12864 0
12865 } else {
12866 snapshot.indent_size_for_line(row).len
12867 };
12868
12869 let start = Point::new(row.0, indent_size);
12870
12871 let mut line_bytes = snapshot
12872 .bytes_in_range(start..snapshot.max_point())
12873 .flatten()
12874 .copied();
12875
12876 // If this line currently begins with the line comment prefix, then record
12877 // the range containing the prefix.
12878 if line_bytes
12879 .by_ref()
12880 .take(comment_prefix.len())
12881 .eq(comment_prefix.bytes())
12882 {
12883 // Include any whitespace that matches the comment prefix.
12884 let matching_whitespace_len = line_bytes
12885 .zip(comment_prefix_whitespace.bytes())
12886 .take_while(|(a, b)| a == b)
12887 .count() as u32;
12888 let end = Point::new(
12889 start.row,
12890 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12891 );
12892 start..end
12893 } else {
12894 start..start
12895 }
12896 }
12897
12898 fn comment_suffix_range(
12899 snapshot: &MultiBufferSnapshot,
12900 row: MultiBufferRow,
12901 comment_suffix: &str,
12902 comment_suffix_has_leading_space: bool,
12903 ) -> Range<Point> {
12904 let end = Point::new(row.0, snapshot.line_len(row));
12905 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12906
12907 let mut line_end_bytes = snapshot
12908 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12909 .flatten()
12910 .copied();
12911
12912 let leading_space_len = if suffix_start_column > 0
12913 && line_end_bytes.next() == Some(b' ')
12914 && comment_suffix_has_leading_space
12915 {
12916 1
12917 } else {
12918 0
12919 };
12920
12921 // If this line currently begins with the line comment prefix, then record
12922 // the range containing the prefix.
12923 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12924 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12925 start..end
12926 } else {
12927 end..end
12928 }
12929 }
12930
12931 // TODO: Handle selections that cross excerpts
12932 for selection in &mut selections {
12933 let start_column = snapshot
12934 .indent_size_for_line(MultiBufferRow(selection.start.row))
12935 .len;
12936 let language = if let Some(language) =
12937 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12938 {
12939 language
12940 } else {
12941 continue;
12942 };
12943
12944 selection_edit_ranges.clear();
12945
12946 // If multiple selections contain a given row, avoid processing that
12947 // row more than once.
12948 let mut start_row = MultiBufferRow(selection.start.row);
12949 if last_toggled_row == Some(start_row) {
12950 start_row = start_row.next_row();
12951 }
12952 let end_row =
12953 if selection.end.row > selection.start.row && selection.end.column == 0 {
12954 MultiBufferRow(selection.end.row - 1)
12955 } else {
12956 MultiBufferRow(selection.end.row)
12957 };
12958 last_toggled_row = Some(end_row);
12959
12960 if start_row > end_row {
12961 continue;
12962 }
12963
12964 // If the language has line comments, toggle those.
12965 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12966
12967 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12968 if ignore_indent {
12969 full_comment_prefixes = full_comment_prefixes
12970 .into_iter()
12971 .map(|s| Arc::from(s.trim_end()))
12972 .collect();
12973 }
12974
12975 if !full_comment_prefixes.is_empty() {
12976 let first_prefix = full_comment_prefixes
12977 .first()
12978 .expect("prefixes is non-empty");
12979 let prefix_trimmed_lengths = full_comment_prefixes
12980 .iter()
12981 .map(|p| p.trim_end_matches(' ').len())
12982 .collect::<SmallVec<[usize; 4]>>();
12983
12984 let mut all_selection_lines_are_comments = true;
12985
12986 for row in start_row.0..=end_row.0 {
12987 let row = MultiBufferRow(row);
12988 if start_row < end_row && snapshot.is_line_blank(row) {
12989 continue;
12990 }
12991
12992 let prefix_range = full_comment_prefixes
12993 .iter()
12994 .zip(prefix_trimmed_lengths.iter().copied())
12995 .map(|(prefix, trimmed_prefix_len)| {
12996 comment_prefix_range(
12997 snapshot.deref(),
12998 row,
12999 &prefix[..trimmed_prefix_len],
13000 &prefix[trimmed_prefix_len..],
13001 ignore_indent,
13002 )
13003 })
13004 .max_by_key(|range| range.end.column - range.start.column)
13005 .expect("prefixes is non-empty");
13006
13007 if prefix_range.is_empty() {
13008 all_selection_lines_are_comments = false;
13009 }
13010
13011 selection_edit_ranges.push(prefix_range);
13012 }
13013
13014 if all_selection_lines_are_comments {
13015 edits.extend(
13016 selection_edit_ranges
13017 .iter()
13018 .cloned()
13019 .map(|range| (range, empty_str.clone())),
13020 );
13021 } else {
13022 let min_column = selection_edit_ranges
13023 .iter()
13024 .map(|range| range.start.column)
13025 .min()
13026 .unwrap_or(0);
13027 edits.extend(selection_edit_ranges.iter().map(|range| {
13028 let position = Point::new(range.start.row, min_column);
13029 (position..position, first_prefix.clone())
13030 }));
13031 }
13032 } else if let Some((full_comment_prefix, comment_suffix)) =
13033 language.block_comment_delimiters()
13034 {
13035 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13036 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13037 let prefix_range = comment_prefix_range(
13038 snapshot.deref(),
13039 start_row,
13040 comment_prefix,
13041 comment_prefix_whitespace,
13042 ignore_indent,
13043 );
13044 let suffix_range = comment_suffix_range(
13045 snapshot.deref(),
13046 end_row,
13047 comment_suffix.trim_start_matches(' '),
13048 comment_suffix.starts_with(' '),
13049 );
13050
13051 if prefix_range.is_empty() || suffix_range.is_empty() {
13052 edits.push((
13053 prefix_range.start..prefix_range.start,
13054 full_comment_prefix.clone(),
13055 ));
13056 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13057 suffixes_inserted.push((end_row, comment_suffix.len()));
13058 } else {
13059 edits.push((prefix_range, empty_str.clone()));
13060 edits.push((suffix_range, empty_str.clone()));
13061 }
13062 } else {
13063 continue;
13064 }
13065 }
13066
13067 drop(snapshot);
13068 this.buffer.update(cx, |buffer, cx| {
13069 buffer.edit(edits, None, cx);
13070 });
13071
13072 // Adjust selections so that they end before any comment suffixes that
13073 // were inserted.
13074 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13075 let mut selections = this.selections.all::<Point>(cx);
13076 let snapshot = this.buffer.read(cx).read(cx);
13077 for selection in &mut selections {
13078 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13079 match row.cmp(&MultiBufferRow(selection.end.row)) {
13080 Ordering::Less => {
13081 suffixes_inserted.next();
13082 continue;
13083 }
13084 Ordering::Greater => break,
13085 Ordering::Equal => {
13086 if selection.end.column == snapshot.line_len(row) {
13087 if selection.is_empty() {
13088 selection.start.column -= suffix_len as u32;
13089 }
13090 selection.end.column -= suffix_len as u32;
13091 }
13092 break;
13093 }
13094 }
13095 }
13096 }
13097
13098 drop(snapshot);
13099 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13100 s.select(selections)
13101 });
13102
13103 let selections = this.selections.all::<Point>(cx);
13104 let selections_on_single_row = selections.windows(2).all(|selections| {
13105 selections[0].start.row == selections[1].start.row
13106 && selections[0].end.row == selections[1].end.row
13107 && selections[0].start.row == selections[0].end.row
13108 });
13109 let selections_selecting = selections
13110 .iter()
13111 .any(|selection| selection.start != selection.end);
13112 let advance_downwards = action.advance_downwards
13113 && selections_on_single_row
13114 && !selections_selecting
13115 && !matches!(this.mode, EditorMode::SingleLine { .. });
13116
13117 if advance_downwards {
13118 let snapshot = this.buffer.read(cx).snapshot(cx);
13119
13120 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13121 s.move_cursors_with(|display_snapshot, display_point, _| {
13122 let mut point = display_point.to_point(display_snapshot);
13123 point.row += 1;
13124 point = snapshot.clip_point(point, Bias::Left);
13125 let display_point = point.to_display_point(display_snapshot);
13126 let goal = SelectionGoal::HorizontalPosition(
13127 display_snapshot
13128 .x_for_display_point(display_point, text_layout_details)
13129 .into(),
13130 );
13131 (display_point, goal)
13132 })
13133 });
13134 }
13135 });
13136 }
13137
13138 pub fn select_enclosing_symbol(
13139 &mut self,
13140 _: &SelectEnclosingSymbol,
13141 window: &mut Window,
13142 cx: &mut Context<Self>,
13143 ) {
13144 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13145
13146 let buffer = self.buffer.read(cx).snapshot(cx);
13147 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13148
13149 fn update_selection(
13150 selection: &Selection<usize>,
13151 buffer_snap: &MultiBufferSnapshot,
13152 ) -> Option<Selection<usize>> {
13153 let cursor = selection.head();
13154 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13155 for symbol in symbols.iter().rev() {
13156 let start = symbol.range.start.to_offset(buffer_snap);
13157 let end = symbol.range.end.to_offset(buffer_snap);
13158 let new_range = start..end;
13159 if start < selection.start || end > selection.end {
13160 return Some(Selection {
13161 id: selection.id,
13162 start: new_range.start,
13163 end: new_range.end,
13164 goal: SelectionGoal::None,
13165 reversed: selection.reversed,
13166 });
13167 }
13168 }
13169 None
13170 }
13171
13172 let mut selected_larger_symbol = false;
13173 let new_selections = old_selections
13174 .iter()
13175 .map(|selection| match update_selection(selection, &buffer) {
13176 Some(new_selection) => {
13177 if new_selection.range() != selection.range() {
13178 selected_larger_symbol = true;
13179 }
13180 new_selection
13181 }
13182 None => selection.clone(),
13183 })
13184 .collect::<Vec<_>>();
13185
13186 if selected_larger_symbol {
13187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13188 s.select(new_selections);
13189 });
13190 }
13191 }
13192
13193 pub fn select_larger_syntax_node(
13194 &mut self,
13195 _: &SelectLargerSyntaxNode,
13196 window: &mut Window,
13197 cx: &mut Context<Self>,
13198 ) {
13199 let Some(visible_row_count) = self.visible_row_count() else {
13200 return;
13201 };
13202 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13203 if old_selections.is_empty() {
13204 return;
13205 }
13206
13207 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13208
13209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13210 let buffer = self.buffer.read(cx).snapshot(cx);
13211
13212 let mut selected_larger_node = false;
13213 let mut new_selections = old_selections
13214 .iter()
13215 .map(|selection| {
13216 let old_range = selection.start..selection.end;
13217
13218 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13219 // manually select word at selection
13220 if ["string_content", "inline"].contains(&node.kind()) {
13221 let word_range = {
13222 let display_point = buffer
13223 .offset_to_point(old_range.start)
13224 .to_display_point(&display_map);
13225 let Range { start, end } =
13226 movement::surrounding_word(&display_map, display_point);
13227 start.to_point(&display_map).to_offset(&buffer)
13228 ..end.to_point(&display_map).to_offset(&buffer)
13229 };
13230 // ignore if word is already selected
13231 if !word_range.is_empty() && old_range != word_range {
13232 let last_word_range = {
13233 let display_point = buffer
13234 .offset_to_point(old_range.end)
13235 .to_display_point(&display_map);
13236 let Range { start, end } =
13237 movement::surrounding_word(&display_map, display_point);
13238 start.to_point(&display_map).to_offset(&buffer)
13239 ..end.to_point(&display_map).to_offset(&buffer)
13240 };
13241 // only select word if start and end point belongs to same word
13242 if word_range == last_word_range {
13243 selected_larger_node = true;
13244 return Selection {
13245 id: selection.id,
13246 start: word_range.start,
13247 end: word_range.end,
13248 goal: SelectionGoal::None,
13249 reversed: selection.reversed,
13250 };
13251 }
13252 }
13253 }
13254 }
13255
13256 let mut new_range = old_range.clone();
13257 while let Some((_node, containing_range)) =
13258 buffer.syntax_ancestor(new_range.clone())
13259 {
13260 new_range = match containing_range {
13261 MultiOrSingleBufferOffsetRange::Single(_) => break,
13262 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13263 };
13264 if !display_map.intersects_fold(new_range.start)
13265 && !display_map.intersects_fold(new_range.end)
13266 {
13267 break;
13268 }
13269 }
13270
13271 selected_larger_node |= new_range != old_range;
13272 Selection {
13273 id: selection.id,
13274 start: new_range.start,
13275 end: new_range.end,
13276 goal: SelectionGoal::None,
13277 reversed: selection.reversed,
13278 }
13279 })
13280 .collect::<Vec<_>>();
13281
13282 if !selected_larger_node {
13283 return; // don't put this call in the history
13284 }
13285
13286 // scroll based on transformation done to the last selection created by the user
13287 let (last_old, last_new) = old_selections
13288 .last()
13289 .zip(new_selections.last().cloned())
13290 .expect("old_selections isn't empty");
13291
13292 // revert selection
13293 let is_selection_reversed = {
13294 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13295 new_selections.last_mut().expect("checked above").reversed =
13296 should_newest_selection_be_reversed;
13297 should_newest_selection_be_reversed
13298 };
13299
13300 if selected_larger_node {
13301 self.select_syntax_node_history.disable_clearing = true;
13302 self.change_selections(None, window, cx, |s| {
13303 s.select(new_selections.clone());
13304 });
13305 self.select_syntax_node_history.disable_clearing = false;
13306 }
13307
13308 let start_row = last_new.start.to_display_point(&display_map).row().0;
13309 let end_row = last_new.end.to_display_point(&display_map).row().0;
13310 let selection_height = end_row - start_row + 1;
13311 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13312
13313 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13314 let scroll_behavior = if fits_on_the_screen {
13315 self.request_autoscroll(Autoscroll::fit(), cx);
13316 SelectSyntaxNodeScrollBehavior::FitSelection
13317 } else if is_selection_reversed {
13318 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13319 SelectSyntaxNodeScrollBehavior::CursorTop
13320 } else {
13321 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13322 SelectSyntaxNodeScrollBehavior::CursorBottom
13323 };
13324
13325 self.select_syntax_node_history.push((
13326 old_selections,
13327 scroll_behavior,
13328 is_selection_reversed,
13329 ));
13330 }
13331
13332 pub fn select_smaller_syntax_node(
13333 &mut self,
13334 _: &SelectSmallerSyntaxNode,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13339
13340 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13341 self.select_syntax_node_history.pop()
13342 {
13343 if let Some(selection) = selections.last_mut() {
13344 selection.reversed = is_selection_reversed;
13345 }
13346
13347 self.select_syntax_node_history.disable_clearing = true;
13348 self.change_selections(None, window, cx, |s| {
13349 s.select(selections.to_vec());
13350 });
13351 self.select_syntax_node_history.disable_clearing = false;
13352
13353 match scroll_behavior {
13354 SelectSyntaxNodeScrollBehavior::CursorTop => {
13355 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13356 }
13357 SelectSyntaxNodeScrollBehavior::FitSelection => {
13358 self.request_autoscroll(Autoscroll::fit(), cx);
13359 }
13360 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13361 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13362 }
13363 }
13364 }
13365 }
13366
13367 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13368 if !EditorSettings::get_global(cx).gutter.runnables {
13369 self.clear_tasks();
13370 return Task::ready(());
13371 }
13372 let project = self.project.as_ref().map(Entity::downgrade);
13373 let task_sources = self.lsp_task_sources(cx);
13374 cx.spawn_in(window, async move |editor, cx| {
13375 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13376 let Some(project) = project.and_then(|p| p.upgrade()) else {
13377 return;
13378 };
13379 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13380 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13381 }) else {
13382 return;
13383 };
13384
13385 let hide_runnables = project
13386 .update(cx, |project, cx| {
13387 // Do not display any test indicators in non-dev server remote projects.
13388 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13389 })
13390 .unwrap_or(true);
13391 if hide_runnables {
13392 return;
13393 }
13394 let new_rows =
13395 cx.background_spawn({
13396 let snapshot = display_snapshot.clone();
13397 async move {
13398 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13399 }
13400 })
13401 .await;
13402 let Ok(lsp_tasks) =
13403 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13404 else {
13405 return;
13406 };
13407 let lsp_tasks = lsp_tasks.await;
13408
13409 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13410 lsp_tasks
13411 .into_iter()
13412 .flat_map(|(kind, tasks)| {
13413 tasks.into_iter().filter_map(move |(location, task)| {
13414 Some((kind.clone(), location?, task))
13415 })
13416 })
13417 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13418 let buffer = location.target.buffer;
13419 let buffer_snapshot = buffer.read(cx).snapshot();
13420 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13421 |(excerpt_id, snapshot, _)| {
13422 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13423 display_snapshot
13424 .buffer_snapshot
13425 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13426 } else {
13427 None
13428 }
13429 },
13430 );
13431 if let Some(offset) = offset {
13432 let task_buffer_range =
13433 location.target.range.to_point(&buffer_snapshot);
13434 let context_buffer_range =
13435 task_buffer_range.to_offset(&buffer_snapshot);
13436 let context_range = BufferOffset(context_buffer_range.start)
13437 ..BufferOffset(context_buffer_range.end);
13438
13439 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13440 .or_insert_with(|| RunnableTasks {
13441 templates: Vec::new(),
13442 offset,
13443 column: task_buffer_range.start.column,
13444 extra_variables: HashMap::default(),
13445 context_range,
13446 })
13447 .templates
13448 .push((kind, task.original_task().clone()));
13449 }
13450
13451 acc
13452 })
13453 }) else {
13454 return;
13455 };
13456
13457 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13458 editor
13459 .update(cx, |editor, _| {
13460 editor.clear_tasks();
13461 for (key, mut value) in rows {
13462 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13463 value.templates.extend(lsp_tasks.templates);
13464 }
13465
13466 editor.insert_tasks(key, value);
13467 }
13468 for (key, value) in lsp_tasks_by_rows {
13469 editor.insert_tasks(key, value);
13470 }
13471 })
13472 .ok();
13473 })
13474 }
13475 fn fetch_runnable_ranges(
13476 snapshot: &DisplaySnapshot,
13477 range: Range<Anchor>,
13478 ) -> Vec<language::RunnableRange> {
13479 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13480 }
13481
13482 fn runnable_rows(
13483 project: Entity<Project>,
13484 snapshot: DisplaySnapshot,
13485 runnable_ranges: Vec<RunnableRange>,
13486 mut cx: AsyncWindowContext,
13487 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13488 runnable_ranges
13489 .into_iter()
13490 .filter_map(|mut runnable| {
13491 let tasks = cx
13492 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13493 .ok()?;
13494 if tasks.is_empty() {
13495 return None;
13496 }
13497
13498 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13499
13500 let row = snapshot
13501 .buffer_snapshot
13502 .buffer_line_for_row(MultiBufferRow(point.row))?
13503 .1
13504 .start
13505 .row;
13506
13507 let context_range =
13508 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13509 Some((
13510 (runnable.buffer_id, row),
13511 RunnableTasks {
13512 templates: tasks,
13513 offset: snapshot
13514 .buffer_snapshot
13515 .anchor_before(runnable.run_range.start),
13516 context_range,
13517 column: point.column,
13518 extra_variables: runnable.extra_captures,
13519 },
13520 ))
13521 })
13522 .collect()
13523 }
13524
13525 fn templates_with_tags(
13526 project: &Entity<Project>,
13527 runnable: &mut Runnable,
13528 cx: &mut App,
13529 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13530 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13531 let (worktree_id, file) = project
13532 .buffer_for_id(runnable.buffer, cx)
13533 .and_then(|buffer| buffer.read(cx).file())
13534 .map(|file| (file.worktree_id(cx), file.clone()))
13535 .unzip();
13536
13537 (
13538 project.task_store().read(cx).task_inventory().cloned(),
13539 worktree_id,
13540 file,
13541 )
13542 });
13543
13544 let mut templates_with_tags = mem::take(&mut runnable.tags)
13545 .into_iter()
13546 .flat_map(|RunnableTag(tag)| {
13547 inventory
13548 .as_ref()
13549 .into_iter()
13550 .flat_map(|inventory| {
13551 inventory.read(cx).list_tasks(
13552 file.clone(),
13553 Some(runnable.language.clone()),
13554 worktree_id,
13555 cx,
13556 )
13557 })
13558 .filter(move |(_, template)| {
13559 template.tags.iter().any(|source_tag| source_tag == &tag)
13560 })
13561 })
13562 .sorted_by_key(|(kind, _)| kind.to_owned())
13563 .collect::<Vec<_>>();
13564 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13565 // Strongest source wins; if we have worktree tag binding, prefer that to
13566 // global and language bindings;
13567 // if we have a global binding, prefer that to language binding.
13568 let first_mismatch = templates_with_tags
13569 .iter()
13570 .position(|(tag_source, _)| tag_source != leading_tag_source);
13571 if let Some(index) = first_mismatch {
13572 templates_with_tags.truncate(index);
13573 }
13574 }
13575
13576 templates_with_tags
13577 }
13578
13579 pub fn move_to_enclosing_bracket(
13580 &mut self,
13581 _: &MoveToEnclosingBracket,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13586 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13587 s.move_offsets_with(|snapshot, selection| {
13588 let Some(enclosing_bracket_ranges) =
13589 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13590 else {
13591 return;
13592 };
13593
13594 let mut best_length = usize::MAX;
13595 let mut best_inside = false;
13596 let mut best_in_bracket_range = false;
13597 let mut best_destination = None;
13598 for (open, close) in enclosing_bracket_ranges {
13599 let close = close.to_inclusive();
13600 let length = close.end() - open.start;
13601 let inside = selection.start >= open.end && selection.end <= *close.start();
13602 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13603 || close.contains(&selection.head());
13604
13605 // If best is next to a bracket and current isn't, skip
13606 if !in_bracket_range && best_in_bracket_range {
13607 continue;
13608 }
13609
13610 // Prefer smaller lengths unless best is inside and current isn't
13611 if length > best_length && (best_inside || !inside) {
13612 continue;
13613 }
13614
13615 best_length = length;
13616 best_inside = inside;
13617 best_in_bracket_range = in_bracket_range;
13618 best_destination = Some(
13619 if close.contains(&selection.start) && close.contains(&selection.end) {
13620 if inside { open.end } else { open.start }
13621 } else if inside {
13622 *close.start()
13623 } else {
13624 *close.end()
13625 },
13626 );
13627 }
13628
13629 if let Some(destination) = best_destination {
13630 selection.collapse_to(destination, SelectionGoal::None);
13631 }
13632 })
13633 });
13634 }
13635
13636 pub fn undo_selection(
13637 &mut self,
13638 _: &UndoSelection,
13639 window: &mut Window,
13640 cx: &mut Context<Self>,
13641 ) {
13642 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13643 self.end_selection(window, cx);
13644 self.selection_history.mode = SelectionHistoryMode::Undoing;
13645 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13646 self.change_selections(None, window, cx, |s| {
13647 s.select_anchors(entry.selections.to_vec())
13648 });
13649 self.select_next_state = entry.select_next_state;
13650 self.select_prev_state = entry.select_prev_state;
13651 self.add_selections_state = entry.add_selections_state;
13652 self.request_autoscroll(Autoscroll::newest(), cx);
13653 }
13654 self.selection_history.mode = SelectionHistoryMode::Normal;
13655 }
13656
13657 pub fn redo_selection(
13658 &mut self,
13659 _: &RedoSelection,
13660 window: &mut Window,
13661 cx: &mut Context<Self>,
13662 ) {
13663 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13664 self.end_selection(window, cx);
13665 self.selection_history.mode = SelectionHistoryMode::Redoing;
13666 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13667 self.change_selections(None, window, cx, |s| {
13668 s.select_anchors(entry.selections.to_vec())
13669 });
13670 self.select_next_state = entry.select_next_state;
13671 self.select_prev_state = entry.select_prev_state;
13672 self.add_selections_state = entry.add_selections_state;
13673 self.request_autoscroll(Autoscroll::newest(), cx);
13674 }
13675 self.selection_history.mode = SelectionHistoryMode::Normal;
13676 }
13677
13678 pub fn expand_excerpts(
13679 &mut self,
13680 action: &ExpandExcerpts,
13681 _: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13685 }
13686
13687 pub fn expand_excerpts_down(
13688 &mut self,
13689 action: &ExpandExcerptsDown,
13690 _: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13694 }
13695
13696 pub fn expand_excerpts_up(
13697 &mut self,
13698 action: &ExpandExcerptsUp,
13699 _: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13703 }
13704
13705 pub fn expand_excerpts_for_direction(
13706 &mut self,
13707 lines: u32,
13708 direction: ExpandExcerptDirection,
13709
13710 cx: &mut Context<Self>,
13711 ) {
13712 let selections = self.selections.disjoint_anchors();
13713
13714 let lines = if lines == 0 {
13715 EditorSettings::get_global(cx).expand_excerpt_lines
13716 } else {
13717 lines
13718 };
13719
13720 self.buffer.update(cx, |buffer, cx| {
13721 let snapshot = buffer.snapshot(cx);
13722 let mut excerpt_ids = selections
13723 .iter()
13724 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13725 .collect::<Vec<_>>();
13726 excerpt_ids.sort();
13727 excerpt_ids.dedup();
13728 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13729 })
13730 }
13731
13732 pub fn expand_excerpt(
13733 &mut self,
13734 excerpt: ExcerptId,
13735 direction: ExpandExcerptDirection,
13736 window: &mut Window,
13737 cx: &mut Context<Self>,
13738 ) {
13739 let current_scroll_position = self.scroll_position(cx);
13740 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13741 let mut should_scroll_up = false;
13742
13743 if direction == ExpandExcerptDirection::Down {
13744 let multi_buffer = self.buffer.read(cx);
13745 let snapshot = multi_buffer.snapshot(cx);
13746 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13747 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13748 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13749 let buffer_snapshot = buffer.read(cx).snapshot();
13750 let excerpt_end_row =
13751 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13752 let last_row = buffer_snapshot.max_point().row;
13753 let lines_below = last_row.saturating_sub(excerpt_end_row);
13754 should_scroll_up = lines_below >= lines_to_expand;
13755 }
13756 }
13757 }
13758 }
13759
13760 self.buffer.update(cx, |buffer, cx| {
13761 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13762 });
13763
13764 if should_scroll_up {
13765 let new_scroll_position =
13766 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13767 self.set_scroll_position(new_scroll_position, window, cx);
13768 }
13769 }
13770
13771 pub fn go_to_singleton_buffer_point(
13772 &mut self,
13773 point: Point,
13774 window: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 self.go_to_singleton_buffer_range(point..point, window, cx);
13778 }
13779
13780 pub fn go_to_singleton_buffer_range(
13781 &mut self,
13782 range: Range<Point>,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 let multibuffer = self.buffer().read(cx);
13787 let Some(buffer) = multibuffer.as_singleton() else {
13788 return;
13789 };
13790 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13791 return;
13792 };
13793 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13794 return;
13795 };
13796 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13797 s.select_anchor_ranges([start..end])
13798 });
13799 }
13800
13801 pub fn go_to_diagnostic(
13802 &mut self,
13803 _: &GoToDiagnostic,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13808 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13809 }
13810
13811 pub fn go_to_prev_diagnostic(
13812 &mut self,
13813 _: &GoToPreviousDiagnostic,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13818 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13819 }
13820
13821 pub fn go_to_diagnostic_impl(
13822 &mut self,
13823 direction: Direction,
13824 window: &mut Window,
13825 cx: &mut Context<Self>,
13826 ) {
13827 let buffer = self.buffer.read(cx).snapshot(cx);
13828 let selection = self.selections.newest::<usize>(cx);
13829
13830 let mut active_group_id = None;
13831 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13832 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13833 active_group_id = Some(active_group.group_id);
13834 }
13835 }
13836
13837 fn filtered(
13838 snapshot: EditorSnapshot,
13839 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13840 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13841 diagnostics
13842 .filter(|entry| entry.range.start != entry.range.end)
13843 .filter(|entry| !entry.diagnostic.is_unnecessary)
13844 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13845 }
13846
13847 let snapshot = self.snapshot(window, cx);
13848 let before = filtered(
13849 snapshot.clone(),
13850 buffer
13851 .diagnostics_in_range(0..selection.start)
13852 .filter(|entry| entry.range.start <= selection.start),
13853 );
13854 let after = filtered(
13855 snapshot,
13856 buffer
13857 .diagnostics_in_range(selection.start..buffer.len())
13858 .filter(|entry| entry.range.start >= selection.start),
13859 );
13860
13861 let mut found: Option<DiagnosticEntry<usize>> = None;
13862 if direction == Direction::Prev {
13863 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13864 {
13865 for diagnostic in prev_diagnostics.into_iter().rev() {
13866 if diagnostic.range.start != selection.start
13867 || active_group_id
13868 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13869 {
13870 found = Some(diagnostic);
13871 break 'outer;
13872 }
13873 }
13874 }
13875 } else {
13876 for diagnostic in after.chain(before) {
13877 if diagnostic.range.start != selection.start
13878 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13879 {
13880 found = Some(diagnostic);
13881 break;
13882 }
13883 }
13884 }
13885 let Some(next_diagnostic) = found else {
13886 return;
13887 };
13888
13889 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13890 return;
13891 };
13892 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13893 s.select_ranges(vec![
13894 next_diagnostic.range.start..next_diagnostic.range.start,
13895 ])
13896 });
13897 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13898 self.refresh_inline_completion(false, true, window, cx);
13899 }
13900
13901 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13902 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13903 let snapshot = self.snapshot(window, cx);
13904 let selection = self.selections.newest::<Point>(cx);
13905 self.go_to_hunk_before_or_after_position(
13906 &snapshot,
13907 selection.head(),
13908 Direction::Next,
13909 window,
13910 cx,
13911 );
13912 }
13913
13914 pub fn go_to_hunk_before_or_after_position(
13915 &mut self,
13916 snapshot: &EditorSnapshot,
13917 position: Point,
13918 direction: Direction,
13919 window: &mut Window,
13920 cx: &mut Context<Editor>,
13921 ) {
13922 let row = if direction == Direction::Next {
13923 self.hunk_after_position(snapshot, position)
13924 .map(|hunk| hunk.row_range.start)
13925 } else {
13926 self.hunk_before_position(snapshot, position)
13927 };
13928
13929 if let Some(row) = row {
13930 let destination = Point::new(row.0, 0);
13931 let autoscroll = Autoscroll::center();
13932
13933 self.unfold_ranges(&[destination..destination], false, false, cx);
13934 self.change_selections(Some(autoscroll), window, cx, |s| {
13935 s.select_ranges([destination..destination]);
13936 });
13937 }
13938 }
13939
13940 fn hunk_after_position(
13941 &mut self,
13942 snapshot: &EditorSnapshot,
13943 position: Point,
13944 ) -> Option<MultiBufferDiffHunk> {
13945 snapshot
13946 .buffer_snapshot
13947 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13948 .find(|hunk| hunk.row_range.start.0 > position.row)
13949 .or_else(|| {
13950 snapshot
13951 .buffer_snapshot
13952 .diff_hunks_in_range(Point::zero()..position)
13953 .find(|hunk| hunk.row_range.end.0 < position.row)
13954 })
13955 }
13956
13957 fn go_to_prev_hunk(
13958 &mut self,
13959 _: &GoToPreviousHunk,
13960 window: &mut Window,
13961 cx: &mut Context<Self>,
13962 ) {
13963 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13964 let snapshot = self.snapshot(window, cx);
13965 let selection = self.selections.newest::<Point>(cx);
13966 self.go_to_hunk_before_or_after_position(
13967 &snapshot,
13968 selection.head(),
13969 Direction::Prev,
13970 window,
13971 cx,
13972 );
13973 }
13974
13975 fn hunk_before_position(
13976 &mut self,
13977 snapshot: &EditorSnapshot,
13978 position: Point,
13979 ) -> Option<MultiBufferRow> {
13980 snapshot
13981 .buffer_snapshot
13982 .diff_hunk_before(position)
13983 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13984 }
13985
13986 fn go_to_next_change(
13987 &mut self,
13988 _: &GoToNextChange,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if let Some(selections) = self
13993 .change_list
13994 .next_change(1, Direction::Next)
13995 .map(|s| s.to_vec())
13996 {
13997 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13998 let map = s.display_map();
13999 s.select_display_ranges(selections.iter().map(|a| {
14000 let point = a.to_display_point(&map);
14001 point..point
14002 }))
14003 })
14004 }
14005 }
14006
14007 fn go_to_previous_change(
14008 &mut self,
14009 _: &GoToPreviousChange,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) {
14013 if let Some(selections) = self
14014 .change_list
14015 .next_change(1, Direction::Prev)
14016 .map(|s| s.to_vec())
14017 {
14018 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14019 let map = s.display_map();
14020 s.select_display_ranges(selections.iter().map(|a| {
14021 let point = a.to_display_point(&map);
14022 point..point
14023 }))
14024 })
14025 }
14026 }
14027
14028 fn go_to_line<T: 'static>(
14029 &mut self,
14030 position: Anchor,
14031 highlight_color: Option<Hsla>,
14032 window: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 let snapshot = self.snapshot(window, cx).display_snapshot;
14036 let position = position.to_point(&snapshot.buffer_snapshot);
14037 let start = snapshot
14038 .buffer_snapshot
14039 .clip_point(Point::new(position.row, 0), Bias::Left);
14040 let end = start + Point::new(1, 0);
14041 let start = snapshot.buffer_snapshot.anchor_before(start);
14042 let end = snapshot.buffer_snapshot.anchor_before(end);
14043
14044 self.highlight_rows::<T>(
14045 start..end,
14046 highlight_color
14047 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14048 Default::default(),
14049 cx,
14050 );
14051
14052 if self.buffer.read(cx).is_singleton() {
14053 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14054 }
14055 }
14056
14057 pub fn go_to_definition(
14058 &mut self,
14059 _: &GoToDefinition,
14060 window: &mut Window,
14061 cx: &mut Context<Self>,
14062 ) -> Task<Result<Navigated>> {
14063 let definition =
14064 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14065 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14066 cx.spawn_in(window, async move |editor, cx| {
14067 if definition.await? == Navigated::Yes {
14068 return Ok(Navigated::Yes);
14069 }
14070 match fallback_strategy {
14071 GoToDefinitionFallback::None => Ok(Navigated::No),
14072 GoToDefinitionFallback::FindAllReferences => {
14073 match editor.update_in(cx, |editor, window, cx| {
14074 editor.find_all_references(&FindAllReferences, window, cx)
14075 })? {
14076 Some(references) => references.await,
14077 None => Ok(Navigated::No),
14078 }
14079 }
14080 }
14081 })
14082 }
14083
14084 pub fn go_to_declaration(
14085 &mut self,
14086 _: &GoToDeclaration,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) -> Task<Result<Navigated>> {
14090 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14091 }
14092
14093 pub fn go_to_declaration_split(
14094 &mut self,
14095 _: &GoToDeclaration,
14096 window: &mut Window,
14097 cx: &mut Context<Self>,
14098 ) -> Task<Result<Navigated>> {
14099 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14100 }
14101
14102 pub fn go_to_implementation(
14103 &mut self,
14104 _: &GoToImplementation,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) -> Task<Result<Navigated>> {
14108 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14109 }
14110
14111 pub fn go_to_implementation_split(
14112 &mut self,
14113 _: &GoToImplementationSplit,
14114 window: &mut Window,
14115 cx: &mut Context<Self>,
14116 ) -> Task<Result<Navigated>> {
14117 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14118 }
14119
14120 pub fn go_to_type_definition(
14121 &mut self,
14122 _: &GoToTypeDefinition,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) -> Task<Result<Navigated>> {
14126 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14127 }
14128
14129 pub fn go_to_definition_split(
14130 &mut self,
14131 _: &GoToDefinitionSplit,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) -> Task<Result<Navigated>> {
14135 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14136 }
14137
14138 pub fn go_to_type_definition_split(
14139 &mut self,
14140 _: &GoToTypeDefinitionSplit,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) -> Task<Result<Navigated>> {
14144 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14145 }
14146
14147 fn go_to_definition_of_kind(
14148 &mut self,
14149 kind: GotoDefinitionKind,
14150 split: bool,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) -> Task<Result<Navigated>> {
14154 let Some(provider) = self.semantics_provider.clone() else {
14155 return Task::ready(Ok(Navigated::No));
14156 };
14157 let head = self.selections.newest::<usize>(cx).head();
14158 let buffer = self.buffer.read(cx);
14159 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14160 text_anchor
14161 } else {
14162 return Task::ready(Ok(Navigated::No));
14163 };
14164
14165 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14166 return Task::ready(Ok(Navigated::No));
14167 };
14168
14169 cx.spawn_in(window, async move |editor, cx| {
14170 let definitions = definitions.await?;
14171 let navigated = editor
14172 .update_in(cx, |editor, window, cx| {
14173 editor.navigate_to_hover_links(
14174 Some(kind),
14175 definitions
14176 .into_iter()
14177 .filter(|location| {
14178 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14179 })
14180 .map(HoverLink::Text)
14181 .collect::<Vec<_>>(),
14182 split,
14183 window,
14184 cx,
14185 )
14186 })?
14187 .await?;
14188 anyhow::Ok(navigated)
14189 })
14190 }
14191
14192 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14193 let selection = self.selections.newest_anchor();
14194 let head = selection.head();
14195 let tail = selection.tail();
14196
14197 let Some((buffer, start_position)) =
14198 self.buffer.read(cx).text_anchor_for_position(head, cx)
14199 else {
14200 return;
14201 };
14202
14203 let end_position = if head != tail {
14204 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14205 return;
14206 };
14207 Some(pos)
14208 } else {
14209 None
14210 };
14211
14212 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14213 let url = if let Some(end_pos) = end_position {
14214 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14215 } else {
14216 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14217 };
14218
14219 if let Some(url) = url {
14220 editor.update(cx, |_, cx| {
14221 cx.open_url(&url);
14222 })
14223 } else {
14224 Ok(())
14225 }
14226 });
14227
14228 url_finder.detach();
14229 }
14230
14231 pub fn open_selected_filename(
14232 &mut self,
14233 _: &OpenSelectedFilename,
14234 window: &mut Window,
14235 cx: &mut Context<Self>,
14236 ) {
14237 let Some(workspace) = self.workspace() else {
14238 return;
14239 };
14240
14241 let position = self.selections.newest_anchor().head();
14242
14243 let Some((buffer, buffer_position)) =
14244 self.buffer.read(cx).text_anchor_for_position(position, cx)
14245 else {
14246 return;
14247 };
14248
14249 let project = self.project.clone();
14250
14251 cx.spawn_in(window, async move |_, cx| {
14252 let result = find_file(&buffer, project, buffer_position, cx).await;
14253
14254 if let Some((_, path)) = result {
14255 workspace
14256 .update_in(cx, |workspace, window, cx| {
14257 workspace.open_resolved_path(path, window, cx)
14258 })?
14259 .await?;
14260 }
14261 anyhow::Ok(())
14262 })
14263 .detach();
14264 }
14265
14266 pub(crate) fn navigate_to_hover_links(
14267 &mut self,
14268 kind: Option<GotoDefinitionKind>,
14269 mut definitions: Vec<HoverLink>,
14270 split: bool,
14271 window: &mut Window,
14272 cx: &mut Context<Editor>,
14273 ) -> Task<Result<Navigated>> {
14274 // If there is one definition, just open it directly
14275 if definitions.len() == 1 {
14276 let definition = definitions.pop().unwrap();
14277
14278 enum TargetTaskResult {
14279 Location(Option<Location>),
14280 AlreadyNavigated,
14281 }
14282
14283 let target_task = match definition {
14284 HoverLink::Text(link) => {
14285 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14286 }
14287 HoverLink::InlayHint(lsp_location, server_id) => {
14288 let computation =
14289 self.compute_target_location(lsp_location, server_id, window, cx);
14290 cx.background_spawn(async move {
14291 let location = computation.await?;
14292 Ok(TargetTaskResult::Location(location))
14293 })
14294 }
14295 HoverLink::Url(url) => {
14296 cx.open_url(&url);
14297 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14298 }
14299 HoverLink::File(path) => {
14300 if let Some(workspace) = self.workspace() {
14301 cx.spawn_in(window, async move |_, cx| {
14302 workspace
14303 .update_in(cx, |workspace, window, cx| {
14304 workspace.open_resolved_path(path, window, cx)
14305 })?
14306 .await
14307 .map(|_| TargetTaskResult::AlreadyNavigated)
14308 })
14309 } else {
14310 Task::ready(Ok(TargetTaskResult::Location(None)))
14311 }
14312 }
14313 };
14314 cx.spawn_in(window, async move |editor, cx| {
14315 let target = match target_task.await.context("target resolution task")? {
14316 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14317 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14318 TargetTaskResult::Location(Some(target)) => target,
14319 };
14320
14321 editor.update_in(cx, |editor, window, cx| {
14322 let Some(workspace) = editor.workspace() else {
14323 return Navigated::No;
14324 };
14325 let pane = workspace.read(cx).active_pane().clone();
14326
14327 let range = target.range.to_point(target.buffer.read(cx));
14328 let range = editor.range_for_match(&range);
14329 let range = collapse_multiline_range(range);
14330
14331 if !split
14332 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14333 {
14334 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14335 } else {
14336 window.defer(cx, move |window, cx| {
14337 let target_editor: Entity<Self> =
14338 workspace.update(cx, |workspace, cx| {
14339 let pane = if split {
14340 workspace.adjacent_pane(window, cx)
14341 } else {
14342 workspace.active_pane().clone()
14343 };
14344
14345 workspace.open_project_item(
14346 pane,
14347 target.buffer.clone(),
14348 true,
14349 true,
14350 window,
14351 cx,
14352 )
14353 });
14354 target_editor.update(cx, |target_editor, cx| {
14355 // When selecting a definition in a different buffer, disable the nav history
14356 // to avoid creating a history entry at the previous cursor location.
14357 pane.update(cx, |pane, _| pane.disable_history());
14358 target_editor.go_to_singleton_buffer_range(range, window, cx);
14359 pane.update(cx, |pane, _| pane.enable_history());
14360 });
14361 });
14362 }
14363 Navigated::Yes
14364 })
14365 })
14366 } else if !definitions.is_empty() {
14367 cx.spawn_in(window, async move |editor, cx| {
14368 let (title, location_tasks, workspace) = editor
14369 .update_in(cx, |editor, window, cx| {
14370 let tab_kind = match kind {
14371 Some(GotoDefinitionKind::Implementation) => "Implementations",
14372 _ => "Definitions",
14373 };
14374 let title = definitions
14375 .iter()
14376 .find_map(|definition| match definition {
14377 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14378 let buffer = origin.buffer.read(cx);
14379 format!(
14380 "{} for {}",
14381 tab_kind,
14382 buffer
14383 .text_for_range(origin.range.clone())
14384 .collect::<String>()
14385 )
14386 }),
14387 HoverLink::InlayHint(_, _) => None,
14388 HoverLink::Url(_) => None,
14389 HoverLink::File(_) => None,
14390 })
14391 .unwrap_or(tab_kind.to_string());
14392 let location_tasks = definitions
14393 .into_iter()
14394 .map(|definition| match definition {
14395 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14396 HoverLink::InlayHint(lsp_location, server_id) => editor
14397 .compute_target_location(lsp_location, server_id, window, cx),
14398 HoverLink::Url(_) => Task::ready(Ok(None)),
14399 HoverLink::File(_) => Task::ready(Ok(None)),
14400 })
14401 .collect::<Vec<_>>();
14402 (title, location_tasks, editor.workspace().clone())
14403 })
14404 .context("location tasks preparation")?;
14405
14406 let locations = future::join_all(location_tasks)
14407 .await
14408 .into_iter()
14409 .filter_map(|location| location.transpose())
14410 .collect::<Result<_>>()
14411 .context("location tasks")?;
14412
14413 let Some(workspace) = workspace else {
14414 return Ok(Navigated::No);
14415 };
14416 let opened = workspace
14417 .update_in(cx, |workspace, window, cx| {
14418 Self::open_locations_in_multibuffer(
14419 workspace,
14420 locations,
14421 title,
14422 split,
14423 MultibufferSelectionMode::First,
14424 window,
14425 cx,
14426 )
14427 })
14428 .ok();
14429
14430 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14431 })
14432 } else {
14433 Task::ready(Ok(Navigated::No))
14434 }
14435 }
14436
14437 fn compute_target_location(
14438 &self,
14439 lsp_location: lsp::Location,
14440 server_id: LanguageServerId,
14441 window: &mut Window,
14442 cx: &mut Context<Self>,
14443 ) -> Task<anyhow::Result<Option<Location>>> {
14444 let Some(project) = self.project.clone() else {
14445 return Task::ready(Ok(None));
14446 };
14447
14448 cx.spawn_in(window, async move |editor, cx| {
14449 let location_task = editor.update(cx, |_, cx| {
14450 project.update(cx, |project, cx| {
14451 let language_server_name = project
14452 .language_server_statuses(cx)
14453 .find(|(id, _)| server_id == *id)
14454 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14455 language_server_name.map(|language_server_name| {
14456 project.open_local_buffer_via_lsp(
14457 lsp_location.uri.clone(),
14458 server_id,
14459 language_server_name,
14460 cx,
14461 )
14462 })
14463 })
14464 })?;
14465 let location = match location_task {
14466 Some(task) => Some({
14467 let target_buffer_handle = task.await.context("open local buffer")?;
14468 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14469 let target_start = target_buffer
14470 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14471 let target_end = target_buffer
14472 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14473 target_buffer.anchor_after(target_start)
14474 ..target_buffer.anchor_before(target_end)
14475 })?;
14476 Location {
14477 buffer: target_buffer_handle,
14478 range,
14479 }
14480 }),
14481 None => None,
14482 };
14483 Ok(location)
14484 })
14485 }
14486
14487 pub fn find_all_references(
14488 &mut self,
14489 _: &FindAllReferences,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) -> Option<Task<Result<Navigated>>> {
14493 let selection = self.selections.newest::<usize>(cx);
14494 let multi_buffer = self.buffer.read(cx);
14495 let head = selection.head();
14496
14497 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14498 let head_anchor = multi_buffer_snapshot.anchor_at(
14499 head,
14500 if head < selection.tail() {
14501 Bias::Right
14502 } else {
14503 Bias::Left
14504 },
14505 );
14506
14507 match self
14508 .find_all_references_task_sources
14509 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14510 {
14511 Ok(_) => {
14512 log::info!(
14513 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14514 );
14515 return None;
14516 }
14517 Err(i) => {
14518 self.find_all_references_task_sources.insert(i, head_anchor);
14519 }
14520 }
14521
14522 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14523 let workspace = self.workspace()?;
14524 let project = workspace.read(cx).project().clone();
14525 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14526 Some(cx.spawn_in(window, async move |editor, cx| {
14527 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14528 if let Ok(i) = editor
14529 .find_all_references_task_sources
14530 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14531 {
14532 editor.find_all_references_task_sources.remove(i);
14533 }
14534 });
14535
14536 let locations = references.await?;
14537 if locations.is_empty() {
14538 return anyhow::Ok(Navigated::No);
14539 }
14540
14541 workspace.update_in(cx, |workspace, window, cx| {
14542 let title = locations
14543 .first()
14544 .as_ref()
14545 .map(|location| {
14546 let buffer = location.buffer.read(cx);
14547 format!(
14548 "References to `{}`",
14549 buffer
14550 .text_for_range(location.range.clone())
14551 .collect::<String>()
14552 )
14553 })
14554 .unwrap();
14555 Self::open_locations_in_multibuffer(
14556 workspace,
14557 locations,
14558 title,
14559 false,
14560 MultibufferSelectionMode::First,
14561 window,
14562 cx,
14563 );
14564 Navigated::Yes
14565 })
14566 }))
14567 }
14568
14569 /// Opens a multibuffer with the given project locations in it
14570 pub fn open_locations_in_multibuffer(
14571 workspace: &mut Workspace,
14572 mut locations: Vec<Location>,
14573 title: String,
14574 split: bool,
14575 multibuffer_selection_mode: MultibufferSelectionMode,
14576 window: &mut Window,
14577 cx: &mut Context<Workspace>,
14578 ) {
14579 // If there are multiple definitions, open them in a multibuffer
14580 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14581 let mut locations = locations.into_iter().peekable();
14582 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14583 let capability = workspace.project().read(cx).capability();
14584
14585 let excerpt_buffer = cx.new(|cx| {
14586 let mut multibuffer = MultiBuffer::new(capability);
14587 while let Some(location) = locations.next() {
14588 let buffer = location.buffer.read(cx);
14589 let mut ranges_for_buffer = Vec::new();
14590 let range = location.range.to_point(buffer);
14591 ranges_for_buffer.push(range.clone());
14592
14593 while let Some(next_location) = locations.peek() {
14594 if next_location.buffer == location.buffer {
14595 ranges_for_buffer.push(next_location.range.to_point(buffer));
14596 locations.next();
14597 } else {
14598 break;
14599 }
14600 }
14601
14602 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14603 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14604 PathKey::for_buffer(&location.buffer, cx),
14605 location.buffer.clone(),
14606 ranges_for_buffer,
14607 DEFAULT_MULTIBUFFER_CONTEXT,
14608 cx,
14609 );
14610 ranges.extend(new_ranges)
14611 }
14612
14613 multibuffer.with_title(title)
14614 });
14615
14616 let editor = cx.new(|cx| {
14617 Editor::for_multibuffer(
14618 excerpt_buffer,
14619 Some(workspace.project().clone()),
14620 window,
14621 cx,
14622 )
14623 });
14624 editor.update(cx, |editor, cx| {
14625 match multibuffer_selection_mode {
14626 MultibufferSelectionMode::First => {
14627 if let Some(first_range) = ranges.first() {
14628 editor.change_selections(None, window, cx, |selections| {
14629 selections.clear_disjoint();
14630 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14631 });
14632 }
14633 editor.highlight_background::<Self>(
14634 &ranges,
14635 |theme| theme.editor_highlighted_line_background,
14636 cx,
14637 );
14638 }
14639 MultibufferSelectionMode::All => {
14640 editor.change_selections(None, window, cx, |selections| {
14641 selections.clear_disjoint();
14642 selections.select_anchor_ranges(ranges);
14643 });
14644 }
14645 }
14646 editor.register_buffers_with_language_servers(cx);
14647 });
14648
14649 let item = Box::new(editor);
14650 let item_id = item.item_id();
14651
14652 if split {
14653 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14654 } else {
14655 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14656 let (preview_item_id, preview_item_idx) =
14657 workspace.active_pane().update(cx, |pane, _| {
14658 (pane.preview_item_id(), pane.preview_item_idx())
14659 });
14660
14661 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14662
14663 if let Some(preview_item_id) = preview_item_id {
14664 workspace.active_pane().update(cx, |pane, cx| {
14665 pane.remove_item(preview_item_id, false, false, window, cx);
14666 });
14667 }
14668 } else {
14669 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14670 }
14671 }
14672 workspace.active_pane().update(cx, |pane, cx| {
14673 pane.set_preview_item_id(Some(item_id), cx);
14674 });
14675 }
14676
14677 pub fn rename(
14678 &mut self,
14679 _: &Rename,
14680 window: &mut Window,
14681 cx: &mut Context<Self>,
14682 ) -> Option<Task<Result<()>>> {
14683 use language::ToOffset as _;
14684
14685 let provider = self.semantics_provider.clone()?;
14686 let selection = self.selections.newest_anchor().clone();
14687 let (cursor_buffer, cursor_buffer_position) = self
14688 .buffer
14689 .read(cx)
14690 .text_anchor_for_position(selection.head(), cx)?;
14691 let (tail_buffer, cursor_buffer_position_end) = self
14692 .buffer
14693 .read(cx)
14694 .text_anchor_for_position(selection.tail(), cx)?;
14695 if tail_buffer != cursor_buffer {
14696 return None;
14697 }
14698
14699 let snapshot = cursor_buffer.read(cx).snapshot();
14700 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14701 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14702 let prepare_rename = provider
14703 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14704 .unwrap_or_else(|| Task::ready(Ok(None)));
14705 drop(snapshot);
14706
14707 Some(cx.spawn_in(window, async move |this, cx| {
14708 let rename_range = if let Some(range) = prepare_rename.await? {
14709 Some(range)
14710 } else {
14711 this.update(cx, |this, cx| {
14712 let buffer = this.buffer.read(cx).snapshot(cx);
14713 let mut buffer_highlights = this
14714 .document_highlights_for_position(selection.head(), &buffer)
14715 .filter(|highlight| {
14716 highlight.start.excerpt_id == selection.head().excerpt_id
14717 && highlight.end.excerpt_id == selection.head().excerpt_id
14718 });
14719 buffer_highlights
14720 .next()
14721 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14722 })?
14723 };
14724 if let Some(rename_range) = rename_range {
14725 this.update_in(cx, |this, window, cx| {
14726 let snapshot = cursor_buffer.read(cx).snapshot();
14727 let rename_buffer_range = rename_range.to_offset(&snapshot);
14728 let cursor_offset_in_rename_range =
14729 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14730 let cursor_offset_in_rename_range_end =
14731 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14732
14733 this.take_rename(false, window, cx);
14734 let buffer = this.buffer.read(cx).read(cx);
14735 let cursor_offset = selection.head().to_offset(&buffer);
14736 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14737 let rename_end = rename_start + rename_buffer_range.len();
14738 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14739 let mut old_highlight_id = None;
14740 let old_name: Arc<str> = buffer
14741 .chunks(rename_start..rename_end, true)
14742 .map(|chunk| {
14743 if old_highlight_id.is_none() {
14744 old_highlight_id = chunk.syntax_highlight_id;
14745 }
14746 chunk.text
14747 })
14748 .collect::<String>()
14749 .into();
14750
14751 drop(buffer);
14752
14753 // Position the selection in the rename editor so that it matches the current selection.
14754 this.show_local_selections = false;
14755 let rename_editor = cx.new(|cx| {
14756 let mut editor = Editor::single_line(window, cx);
14757 editor.buffer.update(cx, |buffer, cx| {
14758 buffer.edit([(0..0, old_name.clone())], None, cx)
14759 });
14760 let rename_selection_range = match cursor_offset_in_rename_range
14761 .cmp(&cursor_offset_in_rename_range_end)
14762 {
14763 Ordering::Equal => {
14764 editor.select_all(&SelectAll, window, cx);
14765 return editor;
14766 }
14767 Ordering::Less => {
14768 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14769 }
14770 Ordering::Greater => {
14771 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14772 }
14773 };
14774 if rename_selection_range.end > old_name.len() {
14775 editor.select_all(&SelectAll, window, cx);
14776 } else {
14777 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14778 s.select_ranges([rename_selection_range]);
14779 });
14780 }
14781 editor
14782 });
14783 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14784 if e == &EditorEvent::Focused {
14785 cx.emit(EditorEvent::FocusedIn)
14786 }
14787 })
14788 .detach();
14789
14790 let write_highlights =
14791 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14792 let read_highlights =
14793 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14794 let ranges = write_highlights
14795 .iter()
14796 .flat_map(|(_, ranges)| ranges.iter())
14797 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14798 .cloned()
14799 .collect();
14800
14801 this.highlight_text::<Rename>(
14802 ranges,
14803 HighlightStyle {
14804 fade_out: Some(0.6),
14805 ..Default::default()
14806 },
14807 cx,
14808 );
14809 let rename_focus_handle = rename_editor.focus_handle(cx);
14810 window.focus(&rename_focus_handle);
14811 let block_id = this.insert_blocks(
14812 [BlockProperties {
14813 style: BlockStyle::Flex,
14814 placement: BlockPlacement::Below(range.start),
14815 height: Some(1),
14816 render: Arc::new({
14817 let rename_editor = rename_editor.clone();
14818 move |cx: &mut BlockContext| {
14819 let mut text_style = cx.editor_style.text.clone();
14820 if let Some(highlight_style) = old_highlight_id
14821 .and_then(|h| h.style(&cx.editor_style.syntax))
14822 {
14823 text_style = text_style.highlight(highlight_style);
14824 }
14825 div()
14826 .block_mouse_down()
14827 .pl(cx.anchor_x)
14828 .child(EditorElement::new(
14829 &rename_editor,
14830 EditorStyle {
14831 background: cx.theme().system().transparent,
14832 local_player: cx.editor_style.local_player,
14833 text: text_style,
14834 scrollbar_width: cx.editor_style.scrollbar_width,
14835 syntax: cx.editor_style.syntax.clone(),
14836 status: cx.editor_style.status.clone(),
14837 inlay_hints_style: HighlightStyle {
14838 font_weight: Some(FontWeight::BOLD),
14839 ..make_inlay_hints_style(cx.app)
14840 },
14841 inline_completion_styles: make_suggestion_styles(
14842 cx.app,
14843 ),
14844 ..EditorStyle::default()
14845 },
14846 ))
14847 .into_any_element()
14848 }
14849 }),
14850 priority: 0,
14851 render_in_minimap: true,
14852 }],
14853 Some(Autoscroll::fit()),
14854 cx,
14855 )[0];
14856 this.pending_rename = Some(RenameState {
14857 range,
14858 old_name,
14859 editor: rename_editor,
14860 block_id,
14861 });
14862 })?;
14863 }
14864
14865 Ok(())
14866 }))
14867 }
14868
14869 pub fn confirm_rename(
14870 &mut self,
14871 _: &ConfirmRename,
14872 window: &mut Window,
14873 cx: &mut Context<Self>,
14874 ) -> Option<Task<Result<()>>> {
14875 let rename = self.take_rename(false, window, cx)?;
14876 let workspace = self.workspace()?.downgrade();
14877 let (buffer, start) = self
14878 .buffer
14879 .read(cx)
14880 .text_anchor_for_position(rename.range.start, cx)?;
14881 let (end_buffer, _) = self
14882 .buffer
14883 .read(cx)
14884 .text_anchor_for_position(rename.range.end, cx)?;
14885 if buffer != end_buffer {
14886 return None;
14887 }
14888
14889 let old_name = rename.old_name;
14890 let new_name = rename.editor.read(cx).text(cx);
14891
14892 let rename = self.semantics_provider.as_ref()?.perform_rename(
14893 &buffer,
14894 start,
14895 new_name.clone(),
14896 cx,
14897 )?;
14898
14899 Some(cx.spawn_in(window, async move |editor, cx| {
14900 let project_transaction = rename.await?;
14901 Self::open_project_transaction(
14902 &editor,
14903 workspace,
14904 project_transaction,
14905 format!("Rename: {} → {}", old_name, new_name),
14906 cx,
14907 )
14908 .await?;
14909
14910 editor.update(cx, |editor, cx| {
14911 editor.refresh_document_highlights(cx);
14912 })?;
14913 Ok(())
14914 }))
14915 }
14916
14917 fn take_rename(
14918 &mut self,
14919 moving_cursor: bool,
14920 window: &mut Window,
14921 cx: &mut Context<Self>,
14922 ) -> Option<RenameState> {
14923 let rename = self.pending_rename.take()?;
14924 if rename.editor.focus_handle(cx).is_focused(window) {
14925 window.focus(&self.focus_handle);
14926 }
14927
14928 self.remove_blocks(
14929 [rename.block_id].into_iter().collect(),
14930 Some(Autoscroll::fit()),
14931 cx,
14932 );
14933 self.clear_highlights::<Rename>(cx);
14934 self.show_local_selections = true;
14935
14936 if moving_cursor {
14937 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14938 editor.selections.newest::<usize>(cx).head()
14939 });
14940
14941 // Update the selection to match the position of the selection inside
14942 // the rename editor.
14943 let snapshot = self.buffer.read(cx).read(cx);
14944 let rename_range = rename.range.to_offset(&snapshot);
14945 let cursor_in_editor = snapshot
14946 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14947 .min(rename_range.end);
14948 drop(snapshot);
14949
14950 self.change_selections(None, window, cx, |s| {
14951 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14952 });
14953 } else {
14954 self.refresh_document_highlights(cx);
14955 }
14956
14957 Some(rename)
14958 }
14959
14960 pub fn pending_rename(&self) -> Option<&RenameState> {
14961 self.pending_rename.as_ref()
14962 }
14963
14964 fn format(
14965 &mut self,
14966 _: &Format,
14967 window: &mut Window,
14968 cx: &mut Context<Self>,
14969 ) -> Option<Task<Result<()>>> {
14970 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14971
14972 let project = match &self.project {
14973 Some(project) => project.clone(),
14974 None => return None,
14975 };
14976
14977 Some(self.perform_format(
14978 project,
14979 FormatTrigger::Manual,
14980 FormatTarget::Buffers,
14981 window,
14982 cx,
14983 ))
14984 }
14985
14986 fn format_selections(
14987 &mut self,
14988 _: &FormatSelections,
14989 window: &mut Window,
14990 cx: &mut Context<Self>,
14991 ) -> Option<Task<Result<()>>> {
14992 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14993
14994 let project = match &self.project {
14995 Some(project) => project.clone(),
14996 None => return None,
14997 };
14998
14999 let ranges = self
15000 .selections
15001 .all_adjusted(cx)
15002 .into_iter()
15003 .map(|selection| selection.range())
15004 .collect_vec();
15005
15006 Some(self.perform_format(
15007 project,
15008 FormatTrigger::Manual,
15009 FormatTarget::Ranges(ranges),
15010 window,
15011 cx,
15012 ))
15013 }
15014
15015 fn perform_format(
15016 &mut self,
15017 project: Entity<Project>,
15018 trigger: FormatTrigger,
15019 target: FormatTarget,
15020 window: &mut Window,
15021 cx: &mut Context<Self>,
15022 ) -> Task<Result<()>> {
15023 let buffer = self.buffer.clone();
15024 let (buffers, target) = match target {
15025 FormatTarget::Buffers => {
15026 let mut buffers = buffer.read(cx).all_buffers();
15027 if trigger == FormatTrigger::Save {
15028 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15029 }
15030 (buffers, LspFormatTarget::Buffers)
15031 }
15032 FormatTarget::Ranges(selection_ranges) => {
15033 let multi_buffer = buffer.read(cx);
15034 let snapshot = multi_buffer.read(cx);
15035 let mut buffers = HashSet::default();
15036 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15037 BTreeMap::new();
15038 for selection_range in selection_ranges {
15039 for (buffer, buffer_range, _) in
15040 snapshot.range_to_buffer_ranges(selection_range)
15041 {
15042 let buffer_id = buffer.remote_id();
15043 let start = buffer.anchor_before(buffer_range.start);
15044 let end = buffer.anchor_after(buffer_range.end);
15045 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15046 buffer_id_to_ranges
15047 .entry(buffer_id)
15048 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15049 .or_insert_with(|| vec![start..end]);
15050 }
15051 }
15052 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15053 }
15054 };
15055
15056 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15057 let selections_prev = transaction_id_prev
15058 .and_then(|transaction_id_prev| {
15059 // default to selections as they were after the last edit, if we have them,
15060 // instead of how they are now.
15061 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15062 // will take you back to where you made the last edit, instead of staying where you scrolled
15063 self.selection_history
15064 .transaction(transaction_id_prev)
15065 .map(|t| t.0.clone())
15066 })
15067 .unwrap_or_else(|| {
15068 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15069 self.selections.disjoint_anchors()
15070 });
15071
15072 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15073 let format = project.update(cx, |project, cx| {
15074 project.format(buffers, target, true, trigger, cx)
15075 });
15076
15077 cx.spawn_in(window, async move |editor, cx| {
15078 let transaction = futures::select_biased! {
15079 transaction = format.log_err().fuse() => transaction,
15080 () = timeout => {
15081 log::warn!("timed out waiting for formatting");
15082 None
15083 }
15084 };
15085
15086 buffer
15087 .update(cx, |buffer, cx| {
15088 if let Some(transaction) = transaction {
15089 if !buffer.is_singleton() {
15090 buffer.push_transaction(&transaction.0, cx);
15091 }
15092 }
15093 cx.notify();
15094 })
15095 .ok();
15096
15097 if let Some(transaction_id_now) =
15098 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15099 {
15100 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15101 if has_new_transaction {
15102 _ = editor.update(cx, |editor, _| {
15103 editor
15104 .selection_history
15105 .insert_transaction(transaction_id_now, selections_prev);
15106 });
15107 }
15108 }
15109
15110 Ok(())
15111 })
15112 }
15113
15114 fn organize_imports(
15115 &mut self,
15116 _: &OrganizeImports,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) -> Option<Task<Result<()>>> {
15120 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15121 let project = match &self.project {
15122 Some(project) => project.clone(),
15123 None => return None,
15124 };
15125 Some(self.perform_code_action_kind(
15126 project,
15127 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15128 window,
15129 cx,
15130 ))
15131 }
15132
15133 fn perform_code_action_kind(
15134 &mut self,
15135 project: Entity<Project>,
15136 kind: CodeActionKind,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) -> Task<Result<()>> {
15140 let buffer = self.buffer.clone();
15141 let buffers = buffer.read(cx).all_buffers();
15142 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15143 let apply_action = project.update(cx, |project, cx| {
15144 project.apply_code_action_kind(buffers, kind, true, cx)
15145 });
15146 cx.spawn_in(window, async move |_, cx| {
15147 let transaction = futures::select_biased! {
15148 () = timeout => {
15149 log::warn!("timed out waiting for executing code action");
15150 None
15151 }
15152 transaction = apply_action.log_err().fuse() => transaction,
15153 };
15154 buffer
15155 .update(cx, |buffer, cx| {
15156 // check if we need this
15157 if let Some(transaction) = transaction {
15158 if !buffer.is_singleton() {
15159 buffer.push_transaction(&transaction.0, cx);
15160 }
15161 }
15162 cx.notify();
15163 })
15164 .ok();
15165 Ok(())
15166 })
15167 }
15168
15169 fn restart_language_server(
15170 &mut self,
15171 _: &RestartLanguageServer,
15172 _: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 if let Some(project) = self.project.clone() {
15176 self.buffer.update(cx, |multi_buffer, cx| {
15177 project.update(cx, |project, cx| {
15178 project.restart_language_servers_for_buffers(
15179 multi_buffer.all_buffers().into_iter().collect(),
15180 cx,
15181 );
15182 });
15183 })
15184 }
15185 }
15186
15187 fn stop_language_server(
15188 &mut self,
15189 _: &StopLanguageServer,
15190 _: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 if let Some(project) = self.project.clone() {
15194 self.buffer.update(cx, |multi_buffer, cx| {
15195 project.update(cx, |project, cx| {
15196 project.stop_language_servers_for_buffers(
15197 multi_buffer.all_buffers().into_iter().collect(),
15198 cx,
15199 );
15200 cx.emit(project::Event::RefreshInlayHints);
15201 });
15202 });
15203 }
15204 }
15205
15206 fn cancel_language_server_work(
15207 workspace: &mut Workspace,
15208 _: &actions::CancelLanguageServerWork,
15209 _: &mut Window,
15210 cx: &mut Context<Workspace>,
15211 ) {
15212 let project = workspace.project();
15213 let buffers = workspace
15214 .active_item(cx)
15215 .and_then(|item| item.act_as::<Editor>(cx))
15216 .map_or(HashSet::default(), |editor| {
15217 editor.read(cx).buffer.read(cx).all_buffers()
15218 });
15219 project.update(cx, |project, cx| {
15220 project.cancel_language_server_work_for_buffers(buffers, cx);
15221 });
15222 }
15223
15224 fn show_character_palette(
15225 &mut self,
15226 _: &ShowCharacterPalette,
15227 window: &mut Window,
15228 _: &mut Context<Self>,
15229 ) {
15230 window.show_character_palette();
15231 }
15232
15233 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15234 if self.mode.is_minimap() {
15235 return;
15236 }
15237
15238 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15239 let buffer = self.buffer.read(cx).snapshot(cx);
15240 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15241 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15242 let is_valid = buffer
15243 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15244 .any(|entry| {
15245 entry.diagnostic.is_primary
15246 && !entry.range.is_empty()
15247 && entry.range.start == primary_range_start
15248 && entry.diagnostic.message == active_diagnostics.active_message
15249 });
15250
15251 if !is_valid {
15252 self.dismiss_diagnostics(cx);
15253 }
15254 }
15255 }
15256
15257 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15258 match &self.active_diagnostics {
15259 ActiveDiagnostic::Group(group) => Some(group),
15260 _ => None,
15261 }
15262 }
15263
15264 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15265 self.dismiss_diagnostics(cx);
15266 self.active_diagnostics = ActiveDiagnostic::All;
15267 }
15268
15269 fn activate_diagnostics(
15270 &mut self,
15271 buffer_id: BufferId,
15272 diagnostic: DiagnosticEntry<usize>,
15273 window: &mut Window,
15274 cx: &mut Context<Self>,
15275 ) {
15276 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15277 return;
15278 }
15279 self.dismiss_diagnostics(cx);
15280 let snapshot = self.snapshot(window, cx);
15281 let buffer = self.buffer.read(cx).snapshot(cx);
15282 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15283 return;
15284 };
15285
15286 let diagnostic_group = buffer
15287 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15288 .collect::<Vec<_>>();
15289
15290 let blocks =
15291 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15292
15293 let blocks = self.display_map.update(cx, |display_map, cx| {
15294 display_map.insert_blocks(blocks, cx).into_iter().collect()
15295 });
15296 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15297 active_range: buffer.anchor_before(diagnostic.range.start)
15298 ..buffer.anchor_after(diagnostic.range.end),
15299 active_message: diagnostic.diagnostic.message.clone(),
15300 group_id: diagnostic.diagnostic.group_id,
15301 blocks,
15302 });
15303 cx.notify();
15304 }
15305
15306 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15307 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15308 return;
15309 };
15310
15311 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15312 if let ActiveDiagnostic::Group(group) = prev {
15313 self.display_map.update(cx, |display_map, cx| {
15314 display_map.remove_blocks(group.blocks, cx);
15315 });
15316 cx.notify();
15317 }
15318 }
15319
15320 /// Disable inline diagnostics rendering for this editor.
15321 pub fn disable_inline_diagnostics(&mut self) {
15322 self.inline_diagnostics_enabled = false;
15323 self.inline_diagnostics_update = Task::ready(());
15324 self.inline_diagnostics.clear();
15325 }
15326
15327 pub fn diagnostics_enabled(&self) -> bool {
15328 self.mode.is_full()
15329 }
15330
15331 pub fn inline_diagnostics_enabled(&self) -> bool {
15332 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15333 }
15334
15335 pub fn show_inline_diagnostics(&self) -> bool {
15336 self.show_inline_diagnostics
15337 }
15338
15339 pub fn toggle_inline_diagnostics(
15340 &mut self,
15341 _: &ToggleInlineDiagnostics,
15342 window: &mut Window,
15343 cx: &mut Context<Editor>,
15344 ) {
15345 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15346 self.refresh_inline_diagnostics(false, window, cx);
15347 }
15348
15349 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15350 self.diagnostics_max_severity = severity;
15351 self.display_map.update(cx, |display_map, _| {
15352 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15353 });
15354 }
15355
15356 pub fn toggle_diagnostics(
15357 &mut self,
15358 _: &ToggleDiagnostics,
15359 window: &mut Window,
15360 cx: &mut Context<Editor>,
15361 ) {
15362 if !self.diagnostics_enabled() {
15363 return;
15364 }
15365
15366 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15367 EditorSettings::get_global(cx)
15368 .diagnostics_max_severity
15369 .filter(|severity| severity != &DiagnosticSeverity::Off)
15370 .unwrap_or(DiagnosticSeverity::Hint)
15371 } else {
15372 DiagnosticSeverity::Off
15373 };
15374 self.set_max_diagnostics_severity(new_severity, cx);
15375 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15376 self.active_diagnostics = ActiveDiagnostic::None;
15377 self.inline_diagnostics_update = Task::ready(());
15378 self.inline_diagnostics.clear();
15379 } else {
15380 self.refresh_inline_diagnostics(false, window, cx);
15381 }
15382
15383 cx.notify();
15384 }
15385
15386 pub fn toggle_minimap(
15387 &mut self,
15388 _: &ToggleMinimap,
15389 window: &mut Window,
15390 cx: &mut Context<Editor>,
15391 ) {
15392 if self.supports_minimap(cx) {
15393 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15394 }
15395 }
15396
15397 fn refresh_inline_diagnostics(
15398 &mut self,
15399 debounce: bool,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 let max_severity = ProjectSettings::get_global(cx)
15404 .diagnostics
15405 .inline
15406 .max_severity
15407 .unwrap_or(self.diagnostics_max_severity);
15408
15409 if self.mode.is_minimap()
15410 || !self.inline_diagnostics_enabled()
15411 || !self.show_inline_diagnostics
15412 || max_severity == DiagnosticSeverity::Off
15413 {
15414 self.inline_diagnostics_update = Task::ready(());
15415 self.inline_diagnostics.clear();
15416 return;
15417 }
15418
15419 let debounce_ms = ProjectSettings::get_global(cx)
15420 .diagnostics
15421 .inline
15422 .update_debounce_ms;
15423 let debounce = if debounce && debounce_ms > 0 {
15424 Some(Duration::from_millis(debounce_ms))
15425 } else {
15426 None
15427 };
15428 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15429 let editor = editor.upgrade().unwrap();
15430
15431 if let Some(debounce) = debounce {
15432 cx.background_executor().timer(debounce).await;
15433 }
15434 let Some(snapshot) = editor
15435 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15436 .ok()
15437 else {
15438 return;
15439 };
15440
15441 let new_inline_diagnostics = cx
15442 .background_spawn(async move {
15443 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15444 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15445 let message = diagnostic_entry
15446 .diagnostic
15447 .message
15448 .split_once('\n')
15449 .map(|(line, _)| line)
15450 .map(SharedString::new)
15451 .unwrap_or_else(|| {
15452 SharedString::from(diagnostic_entry.diagnostic.message)
15453 });
15454 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15455 let (Ok(i) | Err(i)) = inline_diagnostics
15456 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15457 inline_diagnostics.insert(
15458 i,
15459 (
15460 start_anchor,
15461 InlineDiagnostic {
15462 message,
15463 group_id: diagnostic_entry.diagnostic.group_id,
15464 start: diagnostic_entry.range.start.to_point(&snapshot),
15465 is_primary: diagnostic_entry.diagnostic.is_primary,
15466 severity: diagnostic_entry.diagnostic.severity,
15467 },
15468 ),
15469 );
15470 }
15471 inline_diagnostics
15472 })
15473 .await;
15474
15475 editor
15476 .update(cx, |editor, cx| {
15477 editor.inline_diagnostics = new_inline_diagnostics;
15478 cx.notify();
15479 })
15480 .ok();
15481 });
15482 }
15483
15484 pub fn set_selections_from_remote(
15485 &mut self,
15486 selections: Vec<Selection<Anchor>>,
15487 pending_selection: Option<Selection<Anchor>>,
15488 window: &mut Window,
15489 cx: &mut Context<Self>,
15490 ) {
15491 let old_cursor_position = self.selections.newest_anchor().head();
15492 self.selections.change_with(cx, |s| {
15493 s.select_anchors(selections);
15494 if let Some(pending_selection) = pending_selection {
15495 s.set_pending(pending_selection, SelectMode::Character);
15496 } else {
15497 s.clear_pending();
15498 }
15499 });
15500 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15501 }
15502
15503 fn push_to_selection_history(&mut self) {
15504 self.selection_history.push(SelectionHistoryEntry {
15505 selections: self.selections.disjoint_anchors(),
15506 select_next_state: self.select_next_state.clone(),
15507 select_prev_state: self.select_prev_state.clone(),
15508 add_selections_state: self.add_selections_state.clone(),
15509 });
15510 }
15511
15512 pub fn transact(
15513 &mut self,
15514 window: &mut Window,
15515 cx: &mut Context<Self>,
15516 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15517 ) -> Option<TransactionId> {
15518 self.start_transaction_at(Instant::now(), window, cx);
15519 update(self, window, cx);
15520 self.end_transaction_at(Instant::now(), cx)
15521 }
15522
15523 pub fn start_transaction_at(
15524 &mut self,
15525 now: Instant,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 self.end_selection(window, cx);
15530 if let Some(tx_id) = self
15531 .buffer
15532 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15533 {
15534 self.selection_history
15535 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15536 cx.emit(EditorEvent::TransactionBegun {
15537 transaction_id: tx_id,
15538 })
15539 }
15540 }
15541
15542 pub fn end_transaction_at(
15543 &mut self,
15544 now: Instant,
15545 cx: &mut Context<Self>,
15546 ) -> Option<TransactionId> {
15547 if let Some(transaction_id) = self
15548 .buffer
15549 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15550 {
15551 if let Some((_, end_selections)) =
15552 self.selection_history.transaction_mut(transaction_id)
15553 {
15554 *end_selections = Some(self.selections.disjoint_anchors());
15555 } else {
15556 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15557 }
15558
15559 cx.emit(EditorEvent::Edited { transaction_id });
15560 Some(transaction_id)
15561 } else {
15562 None
15563 }
15564 }
15565
15566 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15567 if self.selection_mark_mode {
15568 self.change_selections(None, window, cx, |s| {
15569 s.move_with(|_, sel| {
15570 sel.collapse_to(sel.head(), SelectionGoal::None);
15571 });
15572 })
15573 }
15574 self.selection_mark_mode = true;
15575 cx.notify();
15576 }
15577
15578 pub fn swap_selection_ends(
15579 &mut self,
15580 _: &actions::SwapSelectionEnds,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) {
15584 self.change_selections(None, window, cx, |s| {
15585 s.move_with(|_, sel| {
15586 if sel.start != sel.end {
15587 sel.reversed = !sel.reversed
15588 }
15589 });
15590 });
15591 self.request_autoscroll(Autoscroll::newest(), cx);
15592 cx.notify();
15593 }
15594
15595 pub fn toggle_fold(
15596 &mut self,
15597 _: &actions::ToggleFold,
15598 window: &mut Window,
15599 cx: &mut Context<Self>,
15600 ) {
15601 if self.is_singleton(cx) {
15602 let selection = self.selections.newest::<Point>(cx);
15603
15604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15605 let range = if selection.is_empty() {
15606 let point = selection.head().to_display_point(&display_map);
15607 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15608 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15609 .to_point(&display_map);
15610 start..end
15611 } else {
15612 selection.range()
15613 };
15614 if display_map.folds_in_range(range).next().is_some() {
15615 self.unfold_lines(&Default::default(), window, cx)
15616 } else {
15617 self.fold(&Default::default(), window, cx)
15618 }
15619 } else {
15620 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15621 let buffer_ids: HashSet<_> = self
15622 .selections
15623 .disjoint_anchor_ranges()
15624 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15625 .collect();
15626
15627 let should_unfold = buffer_ids
15628 .iter()
15629 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15630
15631 for buffer_id in buffer_ids {
15632 if should_unfold {
15633 self.unfold_buffer(buffer_id, cx);
15634 } else {
15635 self.fold_buffer(buffer_id, cx);
15636 }
15637 }
15638 }
15639 }
15640
15641 pub fn toggle_fold_recursive(
15642 &mut self,
15643 _: &actions::ToggleFoldRecursive,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) {
15647 let selection = self.selections.newest::<Point>(cx);
15648
15649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15650 let range = if selection.is_empty() {
15651 let point = selection.head().to_display_point(&display_map);
15652 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15653 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15654 .to_point(&display_map);
15655 start..end
15656 } else {
15657 selection.range()
15658 };
15659 if display_map.folds_in_range(range).next().is_some() {
15660 self.unfold_recursive(&Default::default(), window, cx)
15661 } else {
15662 self.fold_recursive(&Default::default(), window, cx)
15663 }
15664 }
15665
15666 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15667 if self.is_singleton(cx) {
15668 let mut to_fold = Vec::new();
15669 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15670 let selections = self.selections.all_adjusted(cx);
15671
15672 for selection in selections {
15673 let range = selection.range().sorted();
15674 let buffer_start_row = range.start.row;
15675
15676 if range.start.row != range.end.row {
15677 let mut found = false;
15678 let mut row = range.start.row;
15679 while row <= range.end.row {
15680 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15681 {
15682 found = true;
15683 row = crease.range().end.row + 1;
15684 to_fold.push(crease);
15685 } else {
15686 row += 1
15687 }
15688 }
15689 if found {
15690 continue;
15691 }
15692 }
15693
15694 for row in (0..=range.start.row).rev() {
15695 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15696 if crease.range().end.row >= buffer_start_row {
15697 to_fold.push(crease);
15698 if row <= range.start.row {
15699 break;
15700 }
15701 }
15702 }
15703 }
15704 }
15705
15706 self.fold_creases(to_fold, true, window, cx);
15707 } else {
15708 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15709 let buffer_ids = self
15710 .selections
15711 .disjoint_anchor_ranges()
15712 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15713 .collect::<HashSet<_>>();
15714 for buffer_id in buffer_ids {
15715 self.fold_buffer(buffer_id, cx);
15716 }
15717 }
15718 }
15719
15720 fn fold_at_level(
15721 &mut self,
15722 fold_at: &FoldAtLevel,
15723 window: &mut Window,
15724 cx: &mut Context<Self>,
15725 ) {
15726 if !self.buffer.read(cx).is_singleton() {
15727 return;
15728 }
15729
15730 let fold_at_level = fold_at.0;
15731 let snapshot = self.buffer.read(cx).snapshot(cx);
15732 let mut to_fold = Vec::new();
15733 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15734
15735 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15736 while start_row < end_row {
15737 match self
15738 .snapshot(window, cx)
15739 .crease_for_buffer_row(MultiBufferRow(start_row))
15740 {
15741 Some(crease) => {
15742 let nested_start_row = crease.range().start.row + 1;
15743 let nested_end_row = crease.range().end.row;
15744
15745 if current_level < fold_at_level {
15746 stack.push((nested_start_row, nested_end_row, current_level + 1));
15747 } else if current_level == fold_at_level {
15748 to_fold.push(crease);
15749 }
15750
15751 start_row = nested_end_row + 1;
15752 }
15753 None => start_row += 1,
15754 }
15755 }
15756 }
15757
15758 self.fold_creases(to_fold, true, window, cx);
15759 }
15760
15761 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15762 if self.buffer.read(cx).is_singleton() {
15763 let mut fold_ranges = Vec::new();
15764 let snapshot = self.buffer.read(cx).snapshot(cx);
15765
15766 for row in 0..snapshot.max_row().0 {
15767 if let Some(foldable_range) = self
15768 .snapshot(window, cx)
15769 .crease_for_buffer_row(MultiBufferRow(row))
15770 {
15771 fold_ranges.push(foldable_range);
15772 }
15773 }
15774
15775 self.fold_creases(fold_ranges, true, window, cx);
15776 } else {
15777 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15778 editor
15779 .update_in(cx, |editor, _, cx| {
15780 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15781 editor.fold_buffer(buffer_id, cx);
15782 }
15783 })
15784 .ok();
15785 });
15786 }
15787 }
15788
15789 pub fn fold_function_bodies(
15790 &mut self,
15791 _: &actions::FoldFunctionBodies,
15792 window: &mut Window,
15793 cx: &mut Context<Self>,
15794 ) {
15795 let snapshot = self.buffer.read(cx).snapshot(cx);
15796
15797 let ranges = snapshot
15798 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15799 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15800 .collect::<Vec<_>>();
15801
15802 let creases = ranges
15803 .into_iter()
15804 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15805 .collect();
15806
15807 self.fold_creases(creases, true, window, cx);
15808 }
15809
15810 pub fn fold_recursive(
15811 &mut self,
15812 _: &actions::FoldRecursive,
15813 window: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) {
15816 let mut to_fold = Vec::new();
15817 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15818 let selections = self.selections.all_adjusted(cx);
15819
15820 for selection in selections {
15821 let range = selection.range().sorted();
15822 let buffer_start_row = range.start.row;
15823
15824 if range.start.row != range.end.row {
15825 let mut found = false;
15826 for row in range.start.row..=range.end.row {
15827 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15828 found = true;
15829 to_fold.push(crease);
15830 }
15831 }
15832 if found {
15833 continue;
15834 }
15835 }
15836
15837 for row in (0..=range.start.row).rev() {
15838 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15839 if crease.range().end.row >= buffer_start_row {
15840 to_fold.push(crease);
15841 } else {
15842 break;
15843 }
15844 }
15845 }
15846 }
15847
15848 self.fold_creases(to_fold, true, window, cx);
15849 }
15850
15851 pub fn fold_at(
15852 &mut self,
15853 buffer_row: MultiBufferRow,
15854 window: &mut Window,
15855 cx: &mut Context<Self>,
15856 ) {
15857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15858
15859 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15860 let autoscroll = self
15861 .selections
15862 .all::<Point>(cx)
15863 .iter()
15864 .any(|selection| crease.range().overlaps(&selection.range()));
15865
15866 self.fold_creases(vec![crease], autoscroll, window, cx);
15867 }
15868 }
15869
15870 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15871 if self.is_singleton(cx) {
15872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15873 let buffer = &display_map.buffer_snapshot;
15874 let selections = self.selections.all::<Point>(cx);
15875 let ranges = selections
15876 .iter()
15877 .map(|s| {
15878 let range = s.display_range(&display_map).sorted();
15879 let mut start = range.start.to_point(&display_map);
15880 let mut end = range.end.to_point(&display_map);
15881 start.column = 0;
15882 end.column = buffer.line_len(MultiBufferRow(end.row));
15883 start..end
15884 })
15885 .collect::<Vec<_>>();
15886
15887 self.unfold_ranges(&ranges, true, true, cx);
15888 } else {
15889 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15890 let buffer_ids = self
15891 .selections
15892 .disjoint_anchor_ranges()
15893 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15894 .collect::<HashSet<_>>();
15895 for buffer_id in buffer_ids {
15896 self.unfold_buffer(buffer_id, cx);
15897 }
15898 }
15899 }
15900
15901 pub fn unfold_recursive(
15902 &mut self,
15903 _: &UnfoldRecursive,
15904 _window: &mut Window,
15905 cx: &mut Context<Self>,
15906 ) {
15907 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15908 let selections = self.selections.all::<Point>(cx);
15909 let ranges = selections
15910 .iter()
15911 .map(|s| {
15912 let mut range = s.display_range(&display_map).sorted();
15913 *range.start.column_mut() = 0;
15914 *range.end.column_mut() = display_map.line_len(range.end.row());
15915 let start = range.start.to_point(&display_map);
15916 let end = range.end.to_point(&display_map);
15917 start..end
15918 })
15919 .collect::<Vec<_>>();
15920
15921 self.unfold_ranges(&ranges, true, true, cx);
15922 }
15923
15924 pub fn unfold_at(
15925 &mut self,
15926 buffer_row: MultiBufferRow,
15927 _window: &mut Window,
15928 cx: &mut Context<Self>,
15929 ) {
15930 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15931
15932 let intersection_range = Point::new(buffer_row.0, 0)
15933 ..Point::new(
15934 buffer_row.0,
15935 display_map.buffer_snapshot.line_len(buffer_row),
15936 );
15937
15938 let autoscroll = self
15939 .selections
15940 .all::<Point>(cx)
15941 .iter()
15942 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15943
15944 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15945 }
15946
15947 pub fn unfold_all(
15948 &mut self,
15949 _: &actions::UnfoldAll,
15950 _window: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 if self.buffer.read(cx).is_singleton() {
15954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15955 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15956 } else {
15957 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15958 editor
15959 .update(cx, |editor, cx| {
15960 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15961 editor.unfold_buffer(buffer_id, cx);
15962 }
15963 })
15964 .ok();
15965 });
15966 }
15967 }
15968
15969 pub fn fold_selected_ranges(
15970 &mut self,
15971 _: &FoldSelectedRanges,
15972 window: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) {
15975 let selections = self.selections.all_adjusted(cx);
15976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15977 let ranges = selections
15978 .into_iter()
15979 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15980 .collect::<Vec<_>>();
15981 self.fold_creases(ranges, true, window, cx);
15982 }
15983
15984 pub fn fold_ranges<T: ToOffset + Clone>(
15985 &mut self,
15986 ranges: Vec<Range<T>>,
15987 auto_scroll: bool,
15988 window: &mut Window,
15989 cx: &mut Context<Self>,
15990 ) {
15991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15992 let ranges = ranges
15993 .into_iter()
15994 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15995 .collect::<Vec<_>>();
15996 self.fold_creases(ranges, auto_scroll, window, cx);
15997 }
15998
15999 pub fn fold_creases<T: ToOffset + Clone>(
16000 &mut self,
16001 creases: Vec<Crease<T>>,
16002 auto_scroll: bool,
16003 _window: &mut Window,
16004 cx: &mut Context<Self>,
16005 ) {
16006 if creases.is_empty() {
16007 return;
16008 }
16009
16010 let mut buffers_affected = HashSet::default();
16011 let multi_buffer = self.buffer().read(cx);
16012 for crease in &creases {
16013 if let Some((_, buffer, _)) =
16014 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16015 {
16016 buffers_affected.insert(buffer.read(cx).remote_id());
16017 };
16018 }
16019
16020 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16021
16022 if auto_scroll {
16023 self.request_autoscroll(Autoscroll::fit(), cx);
16024 }
16025
16026 cx.notify();
16027
16028 self.scrollbar_marker_state.dirty = true;
16029 self.folds_did_change(cx);
16030 }
16031
16032 /// Removes any folds whose ranges intersect any of the given ranges.
16033 pub fn unfold_ranges<T: ToOffset + Clone>(
16034 &mut self,
16035 ranges: &[Range<T>],
16036 inclusive: bool,
16037 auto_scroll: bool,
16038 cx: &mut Context<Self>,
16039 ) {
16040 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16041 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16042 });
16043 self.folds_did_change(cx);
16044 }
16045
16046 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16047 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16048 return;
16049 }
16050 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16051 self.display_map.update(cx, |display_map, cx| {
16052 display_map.fold_buffers([buffer_id], cx)
16053 });
16054 cx.emit(EditorEvent::BufferFoldToggled {
16055 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16056 folded: true,
16057 });
16058 cx.notify();
16059 }
16060
16061 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16062 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16063 return;
16064 }
16065 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16066 self.display_map.update(cx, |display_map, cx| {
16067 display_map.unfold_buffers([buffer_id], cx);
16068 });
16069 cx.emit(EditorEvent::BufferFoldToggled {
16070 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16071 folded: false,
16072 });
16073 cx.notify();
16074 }
16075
16076 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16077 self.display_map.read(cx).is_buffer_folded(buffer)
16078 }
16079
16080 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16081 self.display_map.read(cx).folded_buffers()
16082 }
16083
16084 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16085 self.display_map.update(cx, |display_map, cx| {
16086 display_map.disable_header_for_buffer(buffer_id, cx);
16087 });
16088 cx.notify();
16089 }
16090
16091 /// Removes any folds with the given ranges.
16092 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16093 &mut self,
16094 ranges: &[Range<T>],
16095 type_id: TypeId,
16096 auto_scroll: bool,
16097 cx: &mut Context<Self>,
16098 ) {
16099 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16100 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16101 });
16102 self.folds_did_change(cx);
16103 }
16104
16105 fn remove_folds_with<T: ToOffset + Clone>(
16106 &mut self,
16107 ranges: &[Range<T>],
16108 auto_scroll: bool,
16109 cx: &mut Context<Self>,
16110 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16111 ) {
16112 if ranges.is_empty() {
16113 return;
16114 }
16115
16116 let mut buffers_affected = HashSet::default();
16117 let multi_buffer = self.buffer().read(cx);
16118 for range in ranges {
16119 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16120 buffers_affected.insert(buffer.read(cx).remote_id());
16121 };
16122 }
16123
16124 self.display_map.update(cx, update);
16125
16126 if auto_scroll {
16127 self.request_autoscroll(Autoscroll::fit(), cx);
16128 }
16129
16130 cx.notify();
16131 self.scrollbar_marker_state.dirty = true;
16132 self.active_indent_guides_state.dirty = true;
16133 }
16134
16135 pub fn update_fold_widths(
16136 &mut self,
16137 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16138 cx: &mut Context<Self>,
16139 ) -> bool {
16140 self.display_map
16141 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16142 }
16143
16144 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16145 self.display_map.read(cx).fold_placeholder.clone()
16146 }
16147
16148 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16149 self.buffer.update(cx, |buffer, cx| {
16150 buffer.set_all_diff_hunks_expanded(cx);
16151 });
16152 }
16153
16154 pub fn expand_all_diff_hunks(
16155 &mut self,
16156 _: &ExpandAllDiffHunks,
16157 _window: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) {
16160 self.buffer.update(cx, |buffer, cx| {
16161 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16162 });
16163 }
16164
16165 pub fn toggle_selected_diff_hunks(
16166 &mut self,
16167 _: &ToggleSelectedDiffHunks,
16168 _window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) {
16171 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16172 self.toggle_diff_hunks_in_ranges(ranges, cx);
16173 }
16174
16175 pub fn diff_hunks_in_ranges<'a>(
16176 &'a self,
16177 ranges: &'a [Range<Anchor>],
16178 buffer: &'a MultiBufferSnapshot,
16179 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16180 ranges.iter().flat_map(move |range| {
16181 let end_excerpt_id = range.end.excerpt_id;
16182 let range = range.to_point(buffer);
16183 let mut peek_end = range.end;
16184 if range.end.row < buffer.max_row().0 {
16185 peek_end = Point::new(range.end.row + 1, 0);
16186 }
16187 buffer
16188 .diff_hunks_in_range(range.start..peek_end)
16189 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16190 })
16191 }
16192
16193 pub fn has_stageable_diff_hunks_in_ranges(
16194 &self,
16195 ranges: &[Range<Anchor>],
16196 snapshot: &MultiBufferSnapshot,
16197 ) -> bool {
16198 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16199 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16200 }
16201
16202 pub fn toggle_staged_selected_diff_hunks(
16203 &mut self,
16204 _: &::git::ToggleStaged,
16205 _: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) {
16208 let snapshot = self.buffer.read(cx).snapshot(cx);
16209 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16210 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16211 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16212 }
16213
16214 pub fn set_render_diff_hunk_controls(
16215 &mut self,
16216 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16217 cx: &mut Context<Self>,
16218 ) {
16219 self.render_diff_hunk_controls = render_diff_hunk_controls;
16220 cx.notify();
16221 }
16222
16223 pub fn stage_and_next(
16224 &mut self,
16225 _: &::git::StageAndNext,
16226 window: &mut Window,
16227 cx: &mut Context<Self>,
16228 ) {
16229 self.do_stage_or_unstage_and_next(true, window, cx);
16230 }
16231
16232 pub fn unstage_and_next(
16233 &mut self,
16234 _: &::git::UnstageAndNext,
16235 window: &mut Window,
16236 cx: &mut Context<Self>,
16237 ) {
16238 self.do_stage_or_unstage_and_next(false, window, cx);
16239 }
16240
16241 pub fn stage_or_unstage_diff_hunks(
16242 &mut self,
16243 stage: bool,
16244 ranges: Vec<Range<Anchor>>,
16245 cx: &mut Context<Self>,
16246 ) {
16247 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16248 cx.spawn(async move |this, cx| {
16249 task.await?;
16250 this.update(cx, |this, cx| {
16251 let snapshot = this.buffer.read(cx).snapshot(cx);
16252 let chunk_by = this
16253 .diff_hunks_in_ranges(&ranges, &snapshot)
16254 .chunk_by(|hunk| hunk.buffer_id);
16255 for (buffer_id, hunks) in &chunk_by {
16256 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16257 }
16258 })
16259 })
16260 .detach_and_log_err(cx);
16261 }
16262
16263 fn save_buffers_for_ranges_if_needed(
16264 &mut self,
16265 ranges: &[Range<Anchor>],
16266 cx: &mut Context<Editor>,
16267 ) -> Task<Result<()>> {
16268 let multibuffer = self.buffer.read(cx);
16269 let snapshot = multibuffer.read(cx);
16270 let buffer_ids: HashSet<_> = ranges
16271 .iter()
16272 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16273 .collect();
16274 drop(snapshot);
16275
16276 let mut buffers = HashSet::default();
16277 for buffer_id in buffer_ids {
16278 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16279 let buffer = buffer_entity.read(cx);
16280 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16281 {
16282 buffers.insert(buffer_entity);
16283 }
16284 }
16285 }
16286
16287 if let Some(project) = &self.project {
16288 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16289 } else {
16290 Task::ready(Ok(()))
16291 }
16292 }
16293
16294 fn do_stage_or_unstage_and_next(
16295 &mut self,
16296 stage: bool,
16297 window: &mut Window,
16298 cx: &mut Context<Self>,
16299 ) {
16300 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16301
16302 if ranges.iter().any(|range| range.start != range.end) {
16303 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16304 return;
16305 }
16306
16307 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16308 let snapshot = self.snapshot(window, cx);
16309 let position = self.selections.newest::<Point>(cx).head();
16310 let mut row = snapshot
16311 .buffer_snapshot
16312 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16313 .find(|hunk| hunk.row_range.start.0 > position.row)
16314 .map(|hunk| hunk.row_range.start);
16315
16316 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16317 // Outside of the project diff editor, wrap around to the beginning.
16318 if !all_diff_hunks_expanded {
16319 row = row.or_else(|| {
16320 snapshot
16321 .buffer_snapshot
16322 .diff_hunks_in_range(Point::zero()..position)
16323 .find(|hunk| hunk.row_range.end.0 < position.row)
16324 .map(|hunk| hunk.row_range.start)
16325 });
16326 }
16327
16328 if let Some(row) = row {
16329 let destination = Point::new(row.0, 0);
16330 let autoscroll = Autoscroll::center();
16331
16332 self.unfold_ranges(&[destination..destination], false, false, cx);
16333 self.change_selections(Some(autoscroll), window, cx, |s| {
16334 s.select_ranges([destination..destination]);
16335 });
16336 }
16337 }
16338
16339 fn do_stage_or_unstage(
16340 &self,
16341 stage: bool,
16342 buffer_id: BufferId,
16343 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16344 cx: &mut App,
16345 ) -> Option<()> {
16346 let project = self.project.as_ref()?;
16347 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16348 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16349 let buffer_snapshot = buffer.read(cx).snapshot();
16350 let file_exists = buffer_snapshot
16351 .file()
16352 .is_some_and(|file| file.disk_state().exists());
16353 diff.update(cx, |diff, cx| {
16354 diff.stage_or_unstage_hunks(
16355 stage,
16356 &hunks
16357 .map(|hunk| buffer_diff::DiffHunk {
16358 buffer_range: hunk.buffer_range,
16359 diff_base_byte_range: hunk.diff_base_byte_range,
16360 secondary_status: hunk.secondary_status,
16361 range: Point::zero()..Point::zero(), // unused
16362 })
16363 .collect::<Vec<_>>(),
16364 &buffer_snapshot,
16365 file_exists,
16366 cx,
16367 )
16368 });
16369 None
16370 }
16371
16372 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16373 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16374 self.buffer
16375 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16376 }
16377
16378 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16379 self.buffer.update(cx, |buffer, cx| {
16380 let ranges = vec![Anchor::min()..Anchor::max()];
16381 if !buffer.all_diff_hunks_expanded()
16382 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16383 {
16384 buffer.collapse_diff_hunks(ranges, cx);
16385 true
16386 } else {
16387 false
16388 }
16389 })
16390 }
16391
16392 fn toggle_diff_hunks_in_ranges(
16393 &mut self,
16394 ranges: Vec<Range<Anchor>>,
16395 cx: &mut Context<Editor>,
16396 ) {
16397 self.buffer.update(cx, |buffer, cx| {
16398 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16399 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16400 })
16401 }
16402
16403 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16404 self.buffer.update(cx, |buffer, cx| {
16405 let snapshot = buffer.snapshot(cx);
16406 let excerpt_id = range.end.excerpt_id;
16407 let point_range = range.to_point(&snapshot);
16408 let expand = !buffer.single_hunk_is_expanded(range, cx);
16409 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16410 })
16411 }
16412
16413 pub(crate) fn apply_all_diff_hunks(
16414 &mut self,
16415 _: &ApplyAllDiffHunks,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 ) {
16419 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16420
16421 let buffers = self.buffer.read(cx).all_buffers();
16422 for branch_buffer in buffers {
16423 branch_buffer.update(cx, |branch_buffer, cx| {
16424 branch_buffer.merge_into_base(Vec::new(), cx);
16425 });
16426 }
16427
16428 if let Some(project) = self.project.clone() {
16429 self.save(true, project, window, cx).detach_and_log_err(cx);
16430 }
16431 }
16432
16433 pub(crate) fn apply_selected_diff_hunks(
16434 &mut self,
16435 _: &ApplyDiffHunk,
16436 window: &mut Window,
16437 cx: &mut Context<Self>,
16438 ) {
16439 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16440 let snapshot = self.snapshot(window, cx);
16441 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16442 let mut ranges_by_buffer = HashMap::default();
16443 self.transact(window, cx, |editor, _window, cx| {
16444 for hunk in hunks {
16445 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16446 ranges_by_buffer
16447 .entry(buffer.clone())
16448 .or_insert_with(Vec::new)
16449 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16450 }
16451 }
16452
16453 for (buffer, ranges) in ranges_by_buffer {
16454 buffer.update(cx, |buffer, cx| {
16455 buffer.merge_into_base(ranges, cx);
16456 });
16457 }
16458 });
16459
16460 if let Some(project) = self.project.clone() {
16461 self.save(true, project, window, cx).detach_and_log_err(cx);
16462 }
16463 }
16464
16465 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16466 if hovered != self.gutter_hovered {
16467 self.gutter_hovered = hovered;
16468 cx.notify();
16469 }
16470 }
16471
16472 pub fn insert_blocks(
16473 &mut self,
16474 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16475 autoscroll: Option<Autoscroll>,
16476 cx: &mut Context<Self>,
16477 ) -> Vec<CustomBlockId> {
16478 let blocks = self
16479 .display_map
16480 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16481 if let Some(autoscroll) = autoscroll {
16482 self.request_autoscroll(autoscroll, cx);
16483 }
16484 cx.notify();
16485 blocks
16486 }
16487
16488 pub fn resize_blocks(
16489 &mut self,
16490 heights: HashMap<CustomBlockId, u32>,
16491 autoscroll: Option<Autoscroll>,
16492 cx: &mut Context<Self>,
16493 ) {
16494 self.display_map
16495 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16496 if let Some(autoscroll) = autoscroll {
16497 self.request_autoscroll(autoscroll, cx);
16498 }
16499 cx.notify();
16500 }
16501
16502 pub fn replace_blocks(
16503 &mut self,
16504 renderers: HashMap<CustomBlockId, RenderBlock>,
16505 autoscroll: Option<Autoscroll>,
16506 cx: &mut Context<Self>,
16507 ) {
16508 self.display_map
16509 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16510 if let Some(autoscroll) = autoscroll {
16511 self.request_autoscroll(autoscroll, cx);
16512 }
16513 cx.notify();
16514 }
16515
16516 pub fn remove_blocks(
16517 &mut self,
16518 block_ids: HashSet<CustomBlockId>,
16519 autoscroll: Option<Autoscroll>,
16520 cx: &mut Context<Self>,
16521 ) {
16522 self.display_map.update(cx, |display_map, cx| {
16523 display_map.remove_blocks(block_ids, cx)
16524 });
16525 if let Some(autoscroll) = autoscroll {
16526 self.request_autoscroll(autoscroll, cx);
16527 }
16528 cx.notify();
16529 }
16530
16531 pub fn row_for_block(
16532 &self,
16533 block_id: CustomBlockId,
16534 cx: &mut Context<Self>,
16535 ) -> Option<DisplayRow> {
16536 self.display_map
16537 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16538 }
16539
16540 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16541 self.focused_block = Some(focused_block);
16542 }
16543
16544 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16545 self.focused_block.take()
16546 }
16547
16548 pub fn insert_creases(
16549 &mut self,
16550 creases: impl IntoIterator<Item = Crease<Anchor>>,
16551 cx: &mut Context<Self>,
16552 ) -> Vec<CreaseId> {
16553 self.display_map
16554 .update(cx, |map, cx| map.insert_creases(creases, cx))
16555 }
16556
16557 pub fn remove_creases(
16558 &mut self,
16559 ids: impl IntoIterator<Item = CreaseId>,
16560 cx: &mut Context<Self>,
16561 ) -> Vec<(CreaseId, Range<Anchor>)> {
16562 self.display_map
16563 .update(cx, |map, cx| map.remove_creases(ids, cx))
16564 }
16565
16566 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16567 self.display_map
16568 .update(cx, |map, cx| map.snapshot(cx))
16569 .longest_row()
16570 }
16571
16572 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16573 self.display_map
16574 .update(cx, |map, cx| map.snapshot(cx))
16575 .max_point()
16576 }
16577
16578 pub fn text(&self, cx: &App) -> String {
16579 self.buffer.read(cx).read(cx).text()
16580 }
16581
16582 pub fn is_empty(&self, cx: &App) -> bool {
16583 self.buffer.read(cx).read(cx).is_empty()
16584 }
16585
16586 pub fn text_option(&self, cx: &App) -> Option<String> {
16587 let text = self.text(cx);
16588 let text = text.trim();
16589
16590 if text.is_empty() {
16591 return None;
16592 }
16593
16594 Some(text.to_string())
16595 }
16596
16597 pub fn set_text(
16598 &mut self,
16599 text: impl Into<Arc<str>>,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 self.transact(window, cx, |this, _, cx| {
16604 this.buffer
16605 .read(cx)
16606 .as_singleton()
16607 .expect("you can only call set_text on editors for singleton buffers")
16608 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16609 });
16610 }
16611
16612 pub fn display_text(&self, cx: &mut App) -> String {
16613 self.display_map
16614 .update(cx, |map, cx| map.snapshot(cx))
16615 .text()
16616 }
16617
16618 fn create_minimap(
16619 &self,
16620 minimap_settings: MinimapSettings,
16621 window: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) -> Option<Entity<Self>> {
16624 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16625 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16626 }
16627
16628 fn initialize_new_minimap(
16629 &self,
16630 minimap_settings: MinimapSettings,
16631 window: &mut Window,
16632 cx: &mut Context<Self>,
16633 ) -> Entity<Self> {
16634 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16635
16636 let mut minimap = Editor::new_internal(
16637 EditorMode::Minimap {
16638 parent: cx.weak_entity(),
16639 },
16640 self.buffer.clone(),
16641 self.project.clone(),
16642 Some(self.display_map.clone()),
16643 window,
16644 cx,
16645 );
16646 minimap.scroll_manager.clone_state(&self.scroll_manager);
16647 minimap.set_text_style_refinement(TextStyleRefinement {
16648 font_size: Some(MINIMAP_FONT_SIZE),
16649 font_weight: Some(MINIMAP_FONT_WEIGHT),
16650 ..Default::default()
16651 });
16652 minimap.update_minimap_configuration(minimap_settings, cx);
16653 cx.new(|_| minimap)
16654 }
16655
16656 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16657 let current_line_highlight = minimap_settings
16658 .current_line_highlight
16659 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16660 self.set_current_line_highlight(Some(current_line_highlight));
16661 }
16662
16663 pub fn minimap(&self) -> Option<&Entity<Self>> {
16664 self.minimap
16665 .as_ref()
16666 .filter(|_| self.minimap_visibility.visible())
16667 }
16668
16669 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16670 let mut wrap_guides = smallvec::smallvec![];
16671
16672 if self.show_wrap_guides == Some(false) {
16673 return wrap_guides;
16674 }
16675
16676 let settings = self.buffer.read(cx).language_settings(cx);
16677 if settings.show_wrap_guides {
16678 match self.soft_wrap_mode(cx) {
16679 SoftWrap::Column(soft_wrap) => {
16680 wrap_guides.push((soft_wrap as usize, true));
16681 }
16682 SoftWrap::Bounded(soft_wrap) => {
16683 wrap_guides.push((soft_wrap as usize, true));
16684 }
16685 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16686 }
16687 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16688 }
16689
16690 wrap_guides
16691 }
16692
16693 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16694 let settings = self.buffer.read(cx).language_settings(cx);
16695 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16696 match mode {
16697 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16698 SoftWrap::None
16699 }
16700 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16701 language_settings::SoftWrap::PreferredLineLength => {
16702 SoftWrap::Column(settings.preferred_line_length)
16703 }
16704 language_settings::SoftWrap::Bounded => {
16705 SoftWrap::Bounded(settings.preferred_line_length)
16706 }
16707 }
16708 }
16709
16710 pub fn set_soft_wrap_mode(
16711 &mut self,
16712 mode: language_settings::SoftWrap,
16713
16714 cx: &mut Context<Self>,
16715 ) {
16716 self.soft_wrap_mode_override = Some(mode);
16717 cx.notify();
16718 }
16719
16720 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16721 self.hard_wrap = hard_wrap;
16722 cx.notify();
16723 }
16724
16725 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16726 self.text_style_refinement = Some(style);
16727 }
16728
16729 /// called by the Element so we know what style we were most recently rendered with.
16730 pub(crate) fn set_style(
16731 &mut self,
16732 style: EditorStyle,
16733 window: &mut Window,
16734 cx: &mut Context<Self>,
16735 ) {
16736 // We intentionally do not inform the display map about the minimap style
16737 // so that wrapping is not recalculated and stays consistent for the editor
16738 // and its linked minimap.
16739 if !self.mode.is_minimap() {
16740 let rem_size = window.rem_size();
16741 self.display_map.update(cx, |map, cx| {
16742 map.set_font(
16743 style.text.font(),
16744 style.text.font_size.to_pixels(rem_size),
16745 cx,
16746 )
16747 });
16748 }
16749 self.style = Some(style);
16750 }
16751
16752 pub fn style(&self) -> Option<&EditorStyle> {
16753 self.style.as_ref()
16754 }
16755
16756 // Called by the element. This method is not designed to be called outside of the editor
16757 // element's layout code because it does not notify when rewrapping is computed synchronously.
16758 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16759 self.display_map
16760 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16761 }
16762
16763 pub fn set_soft_wrap(&mut self) {
16764 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16765 }
16766
16767 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16768 if self.soft_wrap_mode_override.is_some() {
16769 self.soft_wrap_mode_override.take();
16770 } else {
16771 let soft_wrap = match self.soft_wrap_mode(cx) {
16772 SoftWrap::GitDiff => return,
16773 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16774 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16775 language_settings::SoftWrap::None
16776 }
16777 };
16778 self.soft_wrap_mode_override = Some(soft_wrap);
16779 }
16780 cx.notify();
16781 }
16782
16783 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16784 let Some(workspace) = self.workspace() else {
16785 return;
16786 };
16787 let fs = workspace.read(cx).app_state().fs.clone();
16788 let current_show = TabBarSettings::get_global(cx).show;
16789 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16790 setting.show = Some(!current_show);
16791 });
16792 }
16793
16794 pub fn toggle_indent_guides(
16795 &mut self,
16796 _: &ToggleIndentGuides,
16797 _: &mut Window,
16798 cx: &mut Context<Self>,
16799 ) {
16800 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16801 self.buffer
16802 .read(cx)
16803 .language_settings(cx)
16804 .indent_guides
16805 .enabled
16806 });
16807 self.show_indent_guides = Some(!currently_enabled);
16808 cx.notify();
16809 }
16810
16811 fn should_show_indent_guides(&self) -> Option<bool> {
16812 self.show_indent_guides
16813 }
16814
16815 pub fn toggle_line_numbers(
16816 &mut self,
16817 _: &ToggleLineNumbers,
16818 _: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) {
16821 let mut editor_settings = EditorSettings::get_global(cx).clone();
16822 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16823 EditorSettings::override_global(editor_settings, cx);
16824 }
16825
16826 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16827 if let Some(show_line_numbers) = self.show_line_numbers {
16828 return show_line_numbers;
16829 }
16830 EditorSettings::get_global(cx).gutter.line_numbers
16831 }
16832
16833 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16834 self.use_relative_line_numbers
16835 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16836 }
16837
16838 pub fn toggle_relative_line_numbers(
16839 &mut self,
16840 _: &ToggleRelativeLineNumbers,
16841 _: &mut Window,
16842 cx: &mut Context<Self>,
16843 ) {
16844 let is_relative = self.should_use_relative_line_numbers(cx);
16845 self.set_relative_line_number(Some(!is_relative), cx)
16846 }
16847
16848 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16849 self.use_relative_line_numbers = is_relative;
16850 cx.notify();
16851 }
16852
16853 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16854 self.show_gutter = show_gutter;
16855 cx.notify();
16856 }
16857
16858 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16859 self.show_scrollbars = show_scrollbars;
16860 cx.notify();
16861 }
16862
16863 pub fn set_minimap_visibility(
16864 &mut self,
16865 minimap_visibility: MinimapVisibility,
16866 window: &mut Window,
16867 cx: &mut Context<Self>,
16868 ) {
16869 if self.minimap_visibility != minimap_visibility {
16870 if minimap_visibility.visible() && self.minimap.is_none() {
16871 let minimap_settings = EditorSettings::get_global(cx).minimap;
16872 self.minimap =
16873 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16874 }
16875 self.minimap_visibility = minimap_visibility;
16876 cx.notify();
16877 }
16878 }
16879
16880 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16881 self.set_show_scrollbars(false, cx);
16882 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16883 }
16884
16885 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16886 self.show_line_numbers = Some(show_line_numbers);
16887 cx.notify();
16888 }
16889
16890 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16891 self.disable_expand_excerpt_buttons = true;
16892 cx.notify();
16893 }
16894
16895 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16896 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16897 cx.notify();
16898 }
16899
16900 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16901 self.show_code_actions = Some(show_code_actions);
16902 cx.notify();
16903 }
16904
16905 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16906 self.show_runnables = Some(show_runnables);
16907 cx.notify();
16908 }
16909
16910 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16911 self.show_breakpoints = Some(show_breakpoints);
16912 cx.notify();
16913 }
16914
16915 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16916 if self.display_map.read(cx).masked != masked {
16917 self.display_map.update(cx, |map, _| map.masked = masked);
16918 }
16919 cx.notify()
16920 }
16921
16922 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16923 self.show_wrap_guides = Some(show_wrap_guides);
16924 cx.notify();
16925 }
16926
16927 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16928 self.show_indent_guides = Some(show_indent_guides);
16929 cx.notify();
16930 }
16931
16932 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16933 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16934 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16935 if let Some(dir) = file.abs_path(cx).parent() {
16936 return Some(dir.to_owned());
16937 }
16938 }
16939
16940 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16941 return Some(project_path.path.to_path_buf());
16942 }
16943 }
16944
16945 None
16946 }
16947
16948 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16949 self.active_excerpt(cx)?
16950 .1
16951 .read(cx)
16952 .file()
16953 .and_then(|f| f.as_local())
16954 }
16955
16956 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16957 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16958 let buffer = buffer.read(cx);
16959 if let Some(project_path) = buffer.project_path(cx) {
16960 let project = self.project.as_ref()?.read(cx);
16961 project.absolute_path(&project_path, cx)
16962 } else {
16963 buffer
16964 .file()
16965 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16966 }
16967 })
16968 }
16969
16970 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16971 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16972 let project_path = buffer.read(cx).project_path(cx)?;
16973 let project = self.project.as_ref()?.read(cx);
16974 let entry = project.entry_for_path(&project_path, cx)?;
16975 let path = entry.path.to_path_buf();
16976 Some(path)
16977 })
16978 }
16979
16980 pub fn reveal_in_finder(
16981 &mut self,
16982 _: &RevealInFileManager,
16983 _window: &mut Window,
16984 cx: &mut Context<Self>,
16985 ) {
16986 if let Some(target) = self.target_file(cx) {
16987 cx.reveal_path(&target.abs_path(cx));
16988 }
16989 }
16990
16991 pub fn copy_path(
16992 &mut self,
16993 _: &zed_actions::workspace::CopyPath,
16994 _window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 if let Some(path) = self.target_file_abs_path(cx) {
16998 if let Some(path) = path.to_str() {
16999 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17000 }
17001 }
17002 }
17003
17004 pub fn copy_relative_path(
17005 &mut self,
17006 _: &zed_actions::workspace::CopyRelativePath,
17007 _window: &mut Window,
17008 cx: &mut Context<Self>,
17009 ) {
17010 if let Some(path) = self.target_file_path(cx) {
17011 if let Some(path) = path.to_str() {
17012 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17013 }
17014 }
17015 }
17016
17017 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17018 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17019 buffer.read(cx).project_path(cx)
17020 } else {
17021 None
17022 }
17023 }
17024
17025 // Returns true if the editor handled a go-to-line request
17026 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17027 maybe!({
17028 let breakpoint_store = self.breakpoint_store.as_ref()?;
17029
17030 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17031 else {
17032 self.clear_row_highlights::<ActiveDebugLine>();
17033 return None;
17034 };
17035
17036 let position = active_stack_frame.position;
17037 let buffer_id = position.buffer_id?;
17038 let snapshot = self
17039 .project
17040 .as_ref()?
17041 .read(cx)
17042 .buffer_for_id(buffer_id, cx)?
17043 .read(cx)
17044 .snapshot();
17045
17046 let mut handled = false;
17047 for (id, ExcerptRange { context, .. }) in
17048 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17049 {
17050 if context.start.cmp(&position, &snapshot).is_ge()
17051 || context.end.cmp(&position, &snapshot).is_lt()
17052 {
17053 continue;
17054 }
17055 let snapshot = self.buffer.read(cx).snapshot(cx);
17056 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17057
17058 handled = true;
17059 self.clear_row_highlights::<ActiveDebugLine>();
17060
17061 self.go_to_line::<ActiveDebugLine>(
17062 multibuffer_anchor,
17063 Some(cx.theme().colors().editor_debugger_active_line_background),
17064 window,
17065 cx,
17066 );
17067
17068 cx.notify();
17069 }
17070
17071 handled.then_some(())
17072 })
17073 .is_some()
17074 }
17075
17076 pub fn copy_file_name_without_extension(
17077 &mut self,
17078 _: &CopyFileNameWithoutExtension,
17079 _: &mut Window,
17080 cx: &mut Context<Self>,
17081 ) {
17082 if let Some(file) = self.target_file(cx) {
17083 if let Some(file_stem) = file.path().file_stem() {
17084 if let Some(name) = file_stem.to_str() {
17085 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17086 }
17087 }
17088 }
17089 }
17090
17091 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17092 if let Some(file) = self.target_file(cx) {
17093 if let Some(file_name) = file.path().file_name() {
17094 if let Some(name) = file_name.to_str() {
17095 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17096 }
17097 }
17098 }
17099 }
17100
17101 pub fn toggle_git_blame(
17102 &mut self,
17103 _: &::git::Blame,
17104 window: &mut Window,
17105 cx: &mut Context<Self>,
17106 ) {
17107 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17108
17109 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17110 self.start_git_blame(true, window, cx);
17111 }
17112
17113 cx.notify();
17114 }
17115
17116 pub fn toggle_git_blame_inline(
17117 &mut self,
17118 _: &ToggleGitBlameInline,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) {
17122 self.toggle_git_blame_inline_internal(true, window, cx);
17123 cx.notify();
17124 }
17125
17126 pub fn open_git_blame_commit(
17127 &mut self,
17128 _: &OpenGitBlameCommit,
17129 window: &mut Window,
17130 cx: &mut Context<Self>,
17131 ) {
17132 self.open_git_blame_commit_internal(window, cx);
17133 }
17134
17135 fn open_git_blame_commit_internal(
17136 &mut self,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) -> Option<()> {
17140 let blame = self.blame.as_ref()?;
17141 let snapshot = self.snapshot(window, cx);
17142 let cursor = self.selections.newest::<Point>(cx).head();
17143 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17144 let blame_entry = blame
17145 .update(cx, |blame, cx| {
17146 blame
17147 .blame_for_rows(
17148 &[RowInfo {
17149 buffer_id: Some(buffer.remote_id()),
17150 buffer_row: Some(point.row),
17151 ..Default::default()
17152 }],
17153 cx,
17154 )
17155 .next()
17156 })
17157 .flatten()?;
17158 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17159 let repo = blame.read(cx).repository(cx)?;
17160 let workspace = self.workspace()?.downgrade();
17161 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17162 None
17163 }
17164
17165 pub fn git_blame_inline_enabled(&self) -> bool {
17166 self.git_blame_inline_enabled
17167 }
17168
17169 pub fn toggle_selection_menu(
17170 &mut self,
17171 _: &ToggleSelectionMenu,
17172 _: &mut Window,
17173 cx: &mut Context<Self>,
17174 ) {
17175 self.show_selection_menu = self
17176 .show_selection_menu
17177 .map(|show_selections_menu| !show_selections_menu)
17178 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17179
17180 cx.notify();
17181 }
17182
17183 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17184 self.show_selection_menu
17185 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17186 }
17187
17188 fn start_git_blame(
17189 &mut self,
17190 user_triggered: bool,
17191 window: &mut Window,
17192 cx: &mut Context<Self>,
17193 ) {
17194 if let Some(project) = self.project.as_ref() {
17195 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17196 return;
17197 };
17198
17199 if buffer.read(cx).file().is_none() {
17200 return;
17201 }
17202
17203 let focused = self.focus_handle(cx).contains_focused(window, cx);
17204
17205 let project = project.clone();
17206 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17207 self.blame_subscription =
17208 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17209 self.blame = Some(blame);
17210 }
17211 }
17212
17213 fn toggle_git_blame_inline_internal(
17214 &mut self,
17215 user_triggered: bool,
17216 window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) {
17219 if self.git_blame_inline_enabled {
17220 self.git_blame_inline_enabled = false;
17221 self.show_git_blame_inline = false;
17222 self.show_git_blame_inline_delay_task.take();
17223 } else {
17224 self.git_blame_inline_enabled = true;
17225 self.start_git_blame_inline(user_triggered, window, cx);
17226 }
17227
17228 cx.notify();
17229 }
17230
17231 fn start_git_blame_inline(
17232 &mut self,
17233 user_triggered: bool,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) {
17237 self.start_git_blame(user_triggered, window, cx);
17238
17239 if ProjectSettings::get_global(cx)
17240 .git
17241 .inline_blame_delay()
17242 .is_some()
17243 {
17244 self.start_inline_blame_timer(window, cx);
17245 } else {
17246 self.show_git_blame_inline = true
17247 }
17248 }
17249
17250 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17251 self.blame.as_ref()
17252 }
17253
17254 pub fn show_git_blame_gutter(&self) -> bool {
17255 self.show_git_blame_gutter
17256 }
17257
17258 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17259 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17260 }
17261
17262 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17263 self.show_git_blame_inline
17264 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17265 && !self.newest_selection_head_on_empty_line(cx)
17266 && self.has_blame_entries(cx)
17267 }
17268
17269 fn has_blame_entries(&self, cx: &App) -> bool {
17270 self.blame()
17271 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17272 }
17273
17274 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17275 let cursor_anchor = self.selections.newest_anchor().head();
17276
17277 let snapshot = self.buffer.read(cx).snapshot(cx);
17278 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17279
17280 snapshot.line_len(buffer_row) == 0
17281 }
17282
17283 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17284 let buffer_and_selection = maybe!({
17285 let selection = self.selections.newest::<Point>(cx);
17286 let selection_range = selection.range();
17287
17288 let multi_buffer = self.buffer().read(cx);
17289 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17290 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17291
17292 let (buffer, range, _) = if selection.reversed {
17293 buffer_ranges.first()
17294 } else {
17295 buffer_ranges.last()
17296 }?;
17297
17298 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17299 ..text::ToPoint::to_point(&range.end, &buffer).row;
17300 Some((
17301 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17302 selection,
17303 ))
17304 });
17305
17306 let Some((buffer, selection)) = buffer_and_selection else {
17307 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17308 };
17309
17310 let Some(project) = self.project.as_ref() else {
17311 return Task::ready(Err(anyhow!("editor does not have project")));
17312 };
17313
17314 project.update(cx, |project, cx| {
17315 project.get_permalink_to_line(&buffer, selection, cx)
17316 })
17317 }
17318
17319 pub fn copy_permalink_to_line(
17320 &mut self,
17321 _: &CopyPermalinkToLine,
17322 window: &mut Window,
17323 cx: &mut Context<Self>,
17324 ) {
17325 let permalink_task = self.get_permalink_to_line(cx);
17326 let workspace = self.workspace();
17327
17328 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17329 Ok(permalink) => {
17330 cx.update(|_, cx| {
17331 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17332 })
17333 .ok();
17334 }
17335 Err(err) => {
17336 let message = format!("Failed to copy permalink: {err}");
17337
17338 anyhow::Result::<()>::Err(err).log_err();
17339
17340 if let Some(workspace) = workspace {
17341 workspace
17342 .update_in(cx, |workspace, _, cx| {
17343 struct CopyPermalinkToLine;
17344
17345 workspace.show_toast(
17346 Toast::new(
17347 NotificationId::unique::<CopyPermalinkToLine>(),
17348 message,
17349 ),
17350 cx,
17351 )
17352 })
17353 .ok();
17354 }
17355 }
17356 })
17357 .detach();
17358 }
17359
17360 pub fn copy_file_location(
17361 &mut self,
17362 _: &CopyFileLocation,
17363 _: &mut Window,
17364 cx: &mut Context<Self>,
17365 ) {
17366 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17367 if let Some(file) = self.target_file(cx) {
17368 if let Some(path) = file.path().to_str() {
17369 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17370 }
17371 }
17372 }
17373
17374 pub fn open_permalink_to_line(
17375 &mut self,
17376 _: &OpenPermalinkToLine,
17377 window: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) {
17380 let permalink_task = self.get_permalink_to_line(cx);
17381 let workspace = self.workspace();
17382
17383 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17384 Ok(permalink) => {
17385 cx.update(|_, cx| {
17386 cx.open_url(permalink.as_ref());
17387 })
17388 .ok();
17389 }
17390 Err(err) => {
17391 let message = format!("Failed to open permalink: {err}");
17392
17393 anyhow::Result::<()>::Err(err).log_err();
17394
17395 if let Some(workspace) = workspace {
17396 workspace
17397 .update(cx, |workspace, cx| {
17398 struct OpenPermalinkToLine;
17399
17400 workspace.show_toast(
17401 Toast::new(
17402 NotificationId::unique::<OpenPermalinkToLine>(),
17403 message,
17404 ),
17405 cx,
17406 )
17407 })
17408 .ok();
17409 }
17410 }
17411 })
17412 .detach();
17413 }
17414
17415 pub fn insert_uuid_v4(
17416 &mut self,
17417 _: &InsertUuidV4,
17418 window: &mut Window,
17419 cx: &mut Context<Self>,
17420 ) {
17421 self.insert_uuid(UuidVersion::V4, window, cx);
17422 }
17423
17424 pub fn insert_uuid_v7(
17425 &mut self,
17426 _: &InsertUuidV7,
17427 window: &mut Window,
17428 cx: &mut Context<Self>,
17429 ) {
17430 self.insert_uuid(UuidVersion::V7, window, cx);
17431 }
17432
17433 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17434 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17435 self.transact(window, cx, |this, window, cx| {
17436 let edits = this
17437 .selections
17438 .all::<Point>(cx)
17439 .into_iter()
17440 .map(|selection| {
17441 let uuid = match version {
17442 UuidVersion::V4 => uuid::Uuid::new_v4(),
17443 UuidVersion::V7 => uuid::Uuid::now_v7(),
17444 };
17445
17446 (selection.range(), uuid.to_string())
17447 });
17448 this.edit(edits, cx);
17449 this.refresh_inline_completion(true, false, window, cx);
17450 });
17451 }
17452
17453 pub fn open_selections_in_multibuffer(
17454 &mut self,
17455 _: &OpenSelectionsInMultibuffer,
17456 window: &mut Window,
17457 cx: &mut Context<Self>,
17458 ) {
17459 let multibuffer = self.buffer.read(cx);
17460
17461 let Some(buffer) = multibuffer.as_singleton() else {
17462 return;
17463 };
17464
17465 let Some(workspace) = self.workspace() else {
17466 return;
17467 };
17468
17469 let locations = self
17470 .selections
17471 .disjoint_anchors()
17472 .iter()
17473 .map(|range| Location {
17474 buffer: buffer.clone(),
17475 range: range.start.text_anchor..range.end.text_anchor,
17476 })
17477 .collect::<Vec<_>>();
17478
17479 let title = multibuffer.title(cx).to_string();
17480
17481 cx.spawn_in(window, async move |_, cx| {
17482 workspace.update_in(cx, |workspace, window, cx| {
17483 Self::open_locations_in_multibuffer(
17484 workspace,
17485 locations,
17486 format!("Selections for '{title}'"),
17487 false,
17488 MultibufferSelectionMode::All,
17489 window,
17490 cx,
17491 );
17492 })
17493 })
17494 .detach();
17495 }
17496
17497 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17498 /// last highlight added will be used.
17499 ///
17500 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17501 pub fn highlight_rows<T: 'static>(
17502 &mut self,
17503 range: Range<Anchor>,
17504 color: Hsla,
17505 options: RowHighlightOptions,
17506 cx: &mut Context<Self>,
17507 ) {
17508 let snapshot = self.buffer().read(cx).snapshot(cx);
17509 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17510 let ix = row_highlights.binary_search_by(|highlight| {
17511 Ordering::Equal
17512 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17513 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17514 });
17515
17516 if let Err(mut ix) = ix {
17517 let index = post_inc(&mut self.highlight_order);
17518
17519 // If this range intersects with the preceding highlight, then merge it with
17520 // the preceding highlight. Otherwise insert a new highlight.
17521 let mut merged = false;
17522 if ix > 0 {
17523 let prev_highlight = &mut row_highlights[ix - 1];
17524 if prev_highlight
17525 .range
17526 .end
17527 .cmp(&range.start, &snapshot)
17528 .is_ge()
17529 {
17530 ix -= 1;
17531 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17532 prev_highlight.range.end = range.end;
17533 }
17534 merged = true;
17535 prev_highlight.index = index;
17536 prev_highlight.color = color;
17537 prev_highlight.options = options;
17538 }
17539 }
17540
17541 if !merged {
17542 row_highlights.insert(
17543 ix,
17544 RowHighlight {
17545 range: range.clone(),
17546 index,
17547 color,
17548 options,
17549 type_id: TypeId::of::<T>(),
17550 },
17551 );
17552 }
17553
17554 // If any of the following highlights intersect with this one, merge them.
17555 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17556 let highlight = &row_highlights[ix];
17557 if next_highlight
17558 .range
17559 .start
17560 .cmp(&highlight.range.end, &snapshot)
17561 .is_le()
17562 {
17563 if next_highlight
17564 .range
17565 .end
17566 .cmp(&highlight.range.end, &snapshot)
17567 .is_gt()
17568 {
17569 row_highlights[ix].range.end = next_highlight.range.end;
17570 }
17571 row_highlights.remove(ix + 1);
17572 } else {
17573 break;
17574 }
17575 }
17576 }
17577 }
17578
17579 /// Remove any highlighted row ranges of the given type that intersect the
17580 /// given ranges.
17581 pub fn remove_highlighted_rows<T: 'static>(
17582 &mut self,
17583 ranges_to_remove: Vec<Range<Anchor>>,
17584 cx: &mut Context<Self>,
17585 ) {
17586 let snapshot = self.buffer().read(cx).snapshot(cx);
17587 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17588 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17589 row_highlights.retain(|highlight| {
17590 while let Some(range_to_remove) = ranges_to_remove.peek() {
17591 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17592 Ordering::Less | Ordering::Equal => {
17593 ranges_to_remove.next();
17594 }
17595 Ordering::Greater => {
17596 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17597 Ordering::Less | Ordering::Equal => {
17598 return false;
17599 }
17600 Ordering::Greater => break,
17601 }
17602 }
17603 }
17604 }
17605
17606 true
17607 })
17608 }
17609
17610 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17611 pub fn clear_row_highlights<T: 'static>(&mut self) {
17612 self.highlighted_rows.remove(&TypeId::of::<T>());
17613 }
17614
17615 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17616 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17617 self.highlighted_rows
17618 .get(&TypeId::of::<T>())
17619 .map_or(&[] as &[_], |vec| vec.as_slice())
17620 .iter()
17621 .map(|highlight| (highlight.range.clone(), highlight.color))
17622 }
17623
17624 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17625 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17626 /// Allows to ignore certain kinds of highlights.
17627 pub fn highlighted_display_rows(
17628 &self,
17629 window: &mut Window,
17630 cx: &mut App,
17631 ) -> BTreeMap<DisplayRow, LineHighlight> {
17632 let snapshot = self.snapshot(window, cx);
17633 let mut used_highlight_orders = HashMap::default();
17634 self.highlighted_rows
17635 .iter()
17636 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17637 .fold(
17638 BTreeMap::<DisplayRow, LineHighlight>::new(),
17639 |mut unique_rows, highlight| {
17640 let start = highlight.range.start.to_display_point(&snapshot);
17641 let end = highlight.range.end.to_display_point(&snapshot);
17642 let start_row = start.row().0;
17643 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17644 && end.column() == 0
17645 {
17646 end.row().0.saturating_sub(1)
17647 } else {
17648 end.row().0
17649 };
17650 for row in start_row..=end_row {
17651 let used_index =
17652 used_highlight_orders.entry(row).or_insert(highlight.index);
17653 if highlight.index >= *used_index {
17654 *used_index = highlight.index;
17655 unique_rows.insert(
17656 DisplayRow(row),
17657 LineHighlight {
17658 include_gutter: highlight.options.include_gutter,
17659 border: None,
17660 background: highlight.color.into(),
17661 type_id: Some(highlight.type_id),
17662 },
17663 );
17664 }
17665 }
17666 unique_rows
17667 },
17668 )
17669 }
17670
17671 pub fn highlighted_display_row_for_autoscroll(
17672 &self,
17673 snapshot: &DisplaySnapshot,
17674 ) -> Option<DisplayRow> {
17675 self.highlighted_rows
17676 .values()
17677 .flat_map(|highlighted_rows| highlighted_rows.iter())
17678 .filter_map(|highlight| {
17679 if highlight.options.autoscroll {
17680 Some(highlight.range.start.to_display_point(snapshot).row())
17681 } else {
17682 None
17683 }
17684 })
17685 .min()
17686 }
17687
17688 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17689 self.highlight_background::<SearchWithinRange>(
17690 ranges,
17691 |colors| colors.editor_document_highlight_read_background,
17692 cx,
17693 )
17694 }
17695
17696 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17697 self.breadcrumb_header = Some(new_header);
17698 }
17699
17700 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17701 self.clear_background_highlights::<SearchWithinRange>(cx);
17702 }
17703
17704 pub fn highlight_background<T: 'static>(
17705 &mut self,
17706 ranges: &[Range<Anchor>],
17707 color_fetcher: fn(&ThemeColors) -> Hsla,
17708 cx: &mut Context<Self>,
17709 ) {
17710 self.background_highlights
17711 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17712 self.scrollbar_marker_state.dirty = true;
17713 cx.notify();
17714 }
17715
17716 pub fn clear_background_highlights<T: 'static>(
17717 &mut self,
17718 cx: &mut Context<Self>,
17719 ) -> Option<BackgroundHighlight> {
17720 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17721 if !text_highlights.1.is_empty() {
17722 self.scrollbar_marker_state.dirty = true;
17723 cx.notify();
17724 }
17725 Some(text_highlights)
17726 }
17727
17728 pub fn highlight_gutter<T: 'static>(
17729 &mut self,
17730 ranges: &[Range<Anchor>],
17731 color_fetcher: fn(&App) -> Hsla,
17732 cx: &mut Context<Self>,
17733 ) {
17734 self.gutter_highlights
17735 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17736 cx.notify();
17737 }
17738
17739 pub fn clear_gutter_highlights<T: 'static>(
17740 &mut self,
17741 cx: &mut Context<Self>,
17742 ) -> Option<GutterHighlight> {
17743 cx.notify();
17744 self.gutter_highlights.remove(&TypeId::of::<T>())
17745 }
17746
17747 #[cfg(feature = "test-support")]
17748 pub fn all_text_background_highlights(
17749 &self,
17750 window: &mut Window,
17751 cx: &mut Context<Self>,
17752 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17753 let snapshot = self.snapshot(window, cx);
17754 let buffer = &snapshot.buffer_snapshot;
17755 let start = buffer.anchor_before(0);
17756 let end = buffer.anchor_after(buffer.len());
17757 let theme = cx.theme().colors();
17758 self.background_highlights_in_range(start..end, &snapshot, theme)
17759 }
17760
17761 #[cfg(feature = "test-support")]
17762 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17763 let snapshot = self.buffer().read(cx).snapshot(cx);
17764
17765 let highlights = self
17766 .background_highlights
17767 .get(&TypeId::of::<items::BufferSearchHighlights>());
17768
17769 if let Some((_color, ranges)) = highlights {
17770 ranges
17771 .iter()
17772 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17773 .collect_vec()
17774 } else {
17775 vec![]
17776 }
17777 }
17778
17779 fn document_highlights_for_position<'a>(
17780 &'a self,
17781 position: Anchor,
17782 buffer: &'a MultiBufferSnapshot,
17783 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17784 let read_highlights = self
17785 .background_highlights
17786 .get(&TypeId::of::<DocumentHighlightRead>())
17787 .map(|h| &h.1);
17788 let write_highlights = self
17789 .background_highlights
17790 .get(&TypeId::of::<DocumentHighlightWrite>())
17791 .map(|h| &h.1);
17792 let left_position = position.bias_left(buffer);
17793 let right_position = position.bias_right(buffer);
17794 read_highlights
17795 .into_iter()
17796 .chain(write_highlights)
17797 .flat_map(move |ranges| {
17798 let start_ix = match ranges.binary_search_by(|probe| {
17799 let cmp = probe.end.cmp(&left_position, buffer);
17800 if cmp.is_ge() {
17801 Ordering::Greater
17802 } else {
17803 Ordering::Less
17804 }
17805 }) {
17806 Ok(i) | Err(i) => i,
17807 };
17808
17809 ranges[start_ix..]
17810 .iter()
17811 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17812 })
17813 }
17814
17815 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17816 self.background_highlights
17817 .get(&TypeId::of::<T>())
17818 .map_or(false, |(_, highlights)| !highlights.is_empty())
17819 }
17820
17821 pub fn background_highlights_in_range(
17822 &self,
17823 search_range: Range<Anchor>,
17824 display_snapshot: &DisplaySnapshot,
17825 theme: &ThemeColors,
17826 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17827 let mut results = Vec::new();
17828 for (color_fetcher, ranges) in self.background_highlights.values() {
17829 let color = color_fetcher(theme);
17830 let start_ix = match ranges.binary_search_by(|probe| {
17831 let cmp = probe
17832 .end
17833 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17834 if cmp.is_gt() {
17835 Ordering::Greater
17836 } else {
17837 Ordering::Less
17838 }
17839 }) {
17840 Ok(i) | Err(i) => i,
17841 };
17842 for range in &ranges[start_ix..] {
17843 if range
17844 .start
17845 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17846 .is_ge()
17847 {
17848 break;
17849 }
17850
17851 let start = range.start.to_display_point(display_snapshot);
17852 let end = range.end.to_display_point(display_snapshot);
17853 results.push((start..end, color))
17854 }
17855 }
17856 results
17857 }
17858
17859 pub fn background_highlight_row_ranges<T: 'static>(
17860 &self,
17861 search_range: Range<Anchor>,
17862 display_snapshot: &DisplaySnapshot,
17863 count: usize,
17864 ) -> Vec<RangeInclusive<DisplayPoint>> {
17865 let mut results = Vec::new();
17866 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17867 return vec![];
17868 };
17869
17870 let start_ix = match ranges.binary_search_by(|probe| {
17871 let cmp = probe
17872 .end
17873 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17874 if cmp.is_gt() {
17875 Ordering::Greater
17876 } else {
17877 Ordering::Less
17878 }
17879 }) {
17880 Ok(i) | Err(i) => i,
17881 };
17882 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17883 if let (Some(start_display), Some(end_display)) = (start, end) {
17884 results.push(
17885 start_display.to_display_point(display_snapshot)
17886 ..=end_display.to_display_point(display_snapshot),
17887 );
17888 }
17889 };
17890 let mut start_row: Option<Point> = None;
17891 let mut end_row: Option<Point> = None;
17892 if ranges.len() > count {
17893 return Vec::new();
17894 }
17895 for range in &ranges[start_ix..] {
17896 if range
17897 .start
17898 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17899 .is_ge()
17900 {
17901 break;
17902 }
17903 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17904 if let Some(current_row) = &end_row {
17905 if end.row == current_row.row {
17906 continue;
17907 }
17908 }
17909 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17910 if start_row.is_none() {
17911 assert_eq!(end_row, None);
17912 start_row = Some(start);
17913 end_row = Some(end);
17914 continue;
17915 }
17916 if let Some(current_end) = end_row.as_mut() {
17917 if start.row > current_end.row + 1 {
17918 push_region(start_row, end_row);
17919 start_row = Some(start);
17920 end_row = Some(end);
17921 } else {
17922 // Merge two hunks.
17923 *current_end = end;
17924 }
17925 } else {
17926 unreachable!();
17927 }
17928 }
17929 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17930 push_region(start_row, end_row);
17931 results
17932 }
17933
17934 pub fn gutter_highlights_in_range(
17935 &self,
17936 search_range: Range<Anchor>,
17937 display_snapshot: &DisplaySnapshot,
17938 cx: &App,
17939 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17940 let mut results = Vec::new();
17941 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17942 let color = color_fetcher(cx);
17943 let start_ix = match ranges.binary_search_by(|probe| {
17944 let cmp = probe
17945 .end
17946 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17947 if cmp.is_gt() {
17948 Ordering::Greater
17949 } else {
17950 Ordering::Less
17951 }
17952 }) {
17953 Ok(i) | Err(i) => i,
17954 };
17955 for range in &ranges[start_ix..] {
17956 if range
17957 .start
17958 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17959 .is_ge()
17960 {
17961 break;
17962 }
17963
17964 let start = range.start.to_display_point(display_snapshot);
17965 let end = range.end.to_display_point(display_snapshot);
17966 results.push((start..end, color))
17967 }
17968 }
17969 results
17970 }
17971
17972 /// Get the text ranges corresponding to the redaction query
17973 pub fn redacted_ranges(
17974 &self,
17975 search_range: Range<Anchor>,
17976 display_snapshot: &DisplaySnapshot,
17977 cx: &App,
17978 ) -> Vec<Range<DisplayPoint>> {
17979 display_snapshot
17980 .buffer_snapshot
17981 .redacted_ranges(search_range, |file| {
17982 if let Some(file) = file {
17983 file.is_private()
17984 && EditorSettings::get(
17985 Some(SettingsLocation {
17986 worktree_id: file.worktree_id(cx),
17987 path: file.path().as_ref(),
17988 }),
17989 cx,
17990 )
17991 .redact_private_values
17992 } else {
17993 false
17994 }
17995 })
17996 .map(|range| {
17997 range.start.to_display_point(display_snapshot)
17998 ..range.end.to_display_point(display_snapshot)
17999 })
18000 .collect()
18001 }
18002
18003 pub fn highlight_text<T: 'static>(
18004 &mut self,
18005 ranges: Vec<Range<Anchor>>,
18006 style: HighlightStyle,
18007 cx: &mut Context<Self>,
18008 ) {
18009 self.display_map.update(cx, |map, _| {
18010 map.highlight_text(TypeId::of::<T>(), ranges, style)
18011 });
18012 cx.notify();
18013 }
18014
18015 pub(crate) fn highlight_inlays<T: 'static>(
18016 &mut self,
18017 highlights: Vec<InlayHighlight>,
18018 style: HighlightStyle,
18019 cx: &mut Context<Self>,
18020 ) {
18021 self.display_map.update(cx, |map, _| {
18022 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18023 });
18024 cx.notify();
18025 }
18026
18027 pub fn text_highlights<'a, T: 'static>(
18028 &'a self,
18029 cx: &'a App,
18030 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18031 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18032 }
18033
18034 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18035 let cleared = self
18036 .display_map
18037 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18038 if cleared {
18039 cx.notify();
18040 }
18041 }
18042
18043 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18044 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18045 && self.focus_handle.is_focused(window)
18046 }
18047
18048 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18049 self.show_cursor_when_unfocused = is_enabled;
18050 cx.notify();
18051 }
18052
18053 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18054 cx.notify();
18055 }
18056
18057 fn on_debug_session_event(
18058 &mut self,
18059 _session: Entity<Session>,
18060 event: &SessionEvent,
18061 cx: &mut Context<Self>,
18062 ) {
18063 match event {
18064 SessionEvent::InvalidateInlineValue => {
18065 self.refresh_inline_values(cx);
18066 }
18067 _ => {}
18068 }
18069 }
18070
18071 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18072 let Some(project) = self.project.clone() else {
18073 return;
18074 };
18075
18076 if !self.inline_value_cache.enabled {
18077 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18078 self.splice_inlays(&inlays, Vec::new(), cx);
18079 return;
18080 }
18081
18082 let current_execution_position = self
18083 .highlighted_rows
18084 .get(&TypeId::of::<ActiveDebugLine>())
18085 .and_then(|lines| lines.last().map(|line| line.range.start));
18086
18087 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18088 let inline_values = editor
18089 .update(cx, |editor, cx| {
18090 let Some(current_execution_position) = current_execution_position else {
18091 return Some(Task::ready(Ok(Vec::new())));
18092 };
18093
18094 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18095 let snapshot = buffer.snapshot(cx);
18096
18097 let excerpt = snapshot.excerpt_containing(
18098 current_execution_position..current_execution_position,
18099 )?;
18100
18101 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18102 })?;
18103
18104 let range =
18105 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18106
18107 project.inline_values(buffer, range, cx)
18108 })
18109 .ok()
18110 .flatten()?
18111 .await
18112 .context("refreshing debugger inlays")
18113 .log_err()?;
18114
18115 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18116
18117 for (buffer_id, inline_value) in inline_values
18118 .into_iter()
18119 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18120 {
18121 buffer_inline_values
18122 .entry(buffer_id)
18123 .or_default()
18124 .push(inline_value);
18125 }
18126
18127 editor
18128 .update(cx, |editor, cx| {
18129 let snapshot = editor.buffer.read(cx).snapshot(cx);
18130 let mut new_inlays = Vec::default();
18131
18132 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18133 let buffer_id = buffer_snapshot.remote_id();
18134 buffer_inline_values
18135 .get(&buffer_id)
18136 .into_iter()
18137 .flatten()
18138 .for_each(|hint| {
18139 let inlay = Inlay::debugger_hint(
18140 post_inc(&mut editor.next_inlay_id),
18141 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18142 hint.text(),
18143 );
18144
18145 new_inlays.push(inlay);
18146 });
18147 }
18148
18149 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18150 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18151
18152 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18153 })
18154 .ok()?;
18155 Some(())
18156 });
18157 }
18158
18159 fn on_buffer_event(
18160 &mut self,
18161 multibuffer: &Entity<MultiBuffer>,
18162 event: &multi_buffer::Event,
18163 window: &mut Window,
18164 cx: &mut Context<Self>,
18165 ) {
18166 match event {
18167 multi_buffer::Event::Edited {
18168 singleton_buffer_edited,
18169 edited_buffer: buffer_edited,
18170 } => {
18171 self.scrollbar_marker_state.dirty = true;
18172 self.active_indent_guides_state.dirty = true;
18173 self.refresh_active_diagnostics(cx);
18174 self.refresh_code_actions(window, cx);
18175 self.refresh_selected_text_highlights(true, window, cx);
18176 refresh_matching_bracket_highlights(self, window, cx);
18177 if self.has_active_inline_completion() {
18178 self.update_visible_inline_completion(window, cx);
18179 }
18180 if let Some(buffer) = buffer_edited {
18181 let buffer_id = buffer.read(cx).remote_id();
18182 if !self.registered_buffers.contains_key(&buffer_id) {
18183 if let Some(project) = self.project.as_ref() {
18184 project.update(cx, |project, cx| {
18185 self.registered_buffers.insert(
18186 buffer_id,
18187 project.register_buffer_with_language_servers(&buffer, cx),
18188 );
18189 })
18190 }
18191 }
18192 }
18193 cx.emit(EditorEvent::BufferEdited);
18194 cx.emit(SearchEvent::MatchesInvalidated);
18195 if *singleton_buffer_edited {
18196 if let Some(project) = &self.project {
18197 #[allow(clippy::mutable_key_type)]
18198 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18199 multibuffer
18200 .all_buffers()
18201 .into_iter()
18202 .filter_map(|buffer| {
18203 buffer.update(cx, |buffer, cx| {
18204 let language = buffer.language()?;
18205 let should_discard = project.update(cx, |project, cx| {
18206 project.is_local()
18207 && !project.has_language_servers_for(buffer, cx)
18208 });
18209 should_discard.not().then_some(language.clone())
18210 })
18211 })
18212 .collect::<HashSet<_>>()
18213 });
18214 if !languages_affected.is_empty() {
18215 self.refresh_inlay_hints(
18216 InlayHintRefreshReason::BufferEdited(languages_affected),
18217 cx,
18218 );
18219 }
18220 }
18221 }
18222
18223 let Some(project) = &self.project else { return };
18224 let (telemetry, is_via_ssh) = {
18225 let project = project.read(cx);
18226 let telemetry = project.client().telemetry().clone();
18227 let is_via_ssh = project.is_via_ssh();
18228 (telemetry, is_via_ssh)
18229 };
18230 refresh_linked_ranges(self, window, cx);
18231 telemetry.log_edit_event("editor", is_via_ssh);
18232 }
18233 multi_buffer::Event::ExcerptsAdded {
18234 buffer,
18235 predecessor,
18236 excerpts,
18237 } => {
18238 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18239 let buffer_id = buffer.read(cx).remote_id();
18240 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18241 if let Some(project) = &self.project {
18242 update_uncommitted_diff_for_buffer(
18243 cx.entity(),
18244 project,
18245 [buffer.clone()],
18246 self.buffer.clone(),
18247 cx,
18248 )
18249 .detach();
18250 }
18251 }
18252 cx.emit(EditorEvent::ExcerptsAdded {
18253 buffer: buffer.clone(),
18254 predecessor: *predecessor,
18255 excerpts: excerpts.clone(),
18256 });
18257 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18258 }
18259 multi_buffer::Event::ExcerptsRemoved {
18260 ids,
18261 removed_buffer_ids,
18262 } => {
18263 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18264 let buffer = self.buffer.read(cx);
18265 self.registered_buffers
18266 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18267 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18268 cx.emit(EditorEvent::ExcerptsRemoved {
18269 ids: ids.clone(),
18270 removed_buffer_ids: removed_buffer_ids.clone(),
18271 })
18272 }
18273 multi_buffer::Event::ExcerptsEdited {
18274 excerpt_ids,
18275 buffer_ids,
18276 } => {
18277 self.display_map.update(cx, |map, cx| {
18278 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18279 });
18280 cx.emit(EditorEvent::ExcerptsEdited {
18281 ids: excerpt_ids.clone(),
18282 })
18283 }
18284 multi_buffer::Event::ExcerptsExpanded { ids } => {
18285 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18286 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18287 }
18288 multi_buffer::Event::Reparsed(buffer_id) => {
18289 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18290 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18291
18292 cx.emit(EditorEvent::Reparsed(*buffer_id));
18293 }
18294 multi_buffer::Event::DiffHunksToggled => {
18295 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18296 }
18297 multi_buffer::Event::LanguageChanged(buffer_id) => {
18298 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18299 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18300 cx.emit(EditorEvent::Reparsed(*buffer_id));
18301 cx.notify();
18302 }
18303 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18304 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18305 multi_buffer::Event::FileHandleChanged
18306 | multi_buffer::Event::Reloaded
18307 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18308 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18309 multi_buffer::Event::DiagnosticsUpdated => {
18310 self.refresh_active_diagnostics(cx);
18311 self.refresh_inline_diagnostics(true, window, cx);
18312 self.scrollbar_marker_state.dirty = true;
18313 cx.notify();
18314 }
18315 _ => {}
18316 };
18317 }
18318
18319 pub fn start_temporary_diff_override(&mut self) {
18320 self.load_diff_task.take();
18321 self.temporary_diff_override = true;
18322 }
18323
18324 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18325 self.temporary_diff_override = false;
18326 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18327 self.buffer.update(cx, |buffer, cx| {
18328 buffer.set_all_diff_hunks_collapsed(cx);
18329 });
18330
18331 if let Some(project) = self.project.clone() {
18332 self.load_diff_task = Some(
18333 update_uncommitted_diff_for_buffer(
18334 cx.entity(),
18335 &project,
18336 self.buffer.read(cx).all_buffers(),
18337 self.buffer.clone(),
18338 cx,
18339 )
18340 .shared(),
18341 );
18342 }
18343 }
18344
18345 fn on_display_map_changed(
18346 &mut self,
18347 _: Entity<DisplayMap>,
18348 _: &mut Window,
18349 cx: &mut Context<Self>,
18350 ) {
18351 cx.notify();
18352 }
18353
18354 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18355 let new_severity = if self.diagnostics_enabled() {
18356 EditorSettings::get_global(cx)
18357 .diagnostics_max_severity
18358 .unwrap_or(DiagnosticSeverity::Hint)
18359 } else {
18360 DiagnosticSeverity::Off
18361 };
18362 self.set_max_diagnostics_severity(new_severity, cx);
18363 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18364 self.update_edit_prediction_settings(cx);
18365 self.refresh_inline_completion(true, false, window, cx);
18366 self.refresh_inlay_hints(
18367 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18368 self.selections.newest_anchor().head(),
18369 &self.buffer.read(cx).snapshot(cx),
18370 cx,
18371 )),
18372 cx,
18373 );
18374
18375 let old_cursor_shape = self.cursor_shape;
18376
18377 {
18378 let editor_settings = EditorSettings::get_global(cx);
18379 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18380 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18381 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18382 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18383 }
18384
18385 if old_cursor_shape != self.cursor_shape {
18386 cx.emit(EditorEvent::CursorShapeChanged);
18387 }
18388
18389 let project_settings = ProjectSettings::get_global(cx);
18390 self.serialize_dirty_buffers =
18391 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18392
18393 if self.mode.is_full() {
18394 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18395 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18396 if self.show_inline_diagnostics != show_inline_diagnostics {
18397 self.show_inline_diagnostics = show_inline_diagnostics;
18398 self.refresh_inline_diagnostics(false, window, cx);
18399 }
18400
18401 if self.git_blame_inline_enabled != inline_blame_enabled {
18402 self.toggle_git_blame_inline_internal(false, window, cx);
18403 }
18404
18405 let minimap_settings = EditorSettings::get_global(cx).minimap;
18406 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18407 self.set_minimap_visibility(
18408 self.minimap_visibility.toggle_visibility(),
18409 window,
18410 cx,
18411 );
18412 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18413 minimap_entity.update(cx, |minimap_editor, cx| {
18414 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18415 })
18416 }
18417 }
18418
18419 cx.notify();
18420 }
18421
18422 pub fn set_searchable(&mut self, searchable: bool) {
18423 self.searchable = searchable;
18424 }
18425
18426 pub fn searchable(&self) -> bool {
18427 self.searchable
18428 }
18429
18430 fn open_proposed_changes_editor(
18431 &mut self,
18432 _: &OpenProposedChangesEditor,
18433 window: &mut Window,
18434 cx: &mut Context<Self>,
18435 ) {
18436 let Some(workspace) = self.workspace() else {
18437 cx.propagate();
18438 return;
18439 };
18440
18441 let selections = self.selections.all::<usize>(cx);
18442 let multi_buffer = self.buffer.read(cx);
18443 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18444 let mut new_selections_by_buffer = HashMap::default();
18445 for selection in selections {
18446 for (buffer, range, _) in
18447 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18448 {
18449 let mut range = range.to_point(buffer);
18450 range.start.column = 0;
18451 range.end.column = buffer.line_len(range.end.row);
18452 new_selections_by_buffer
18453 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18454 .or_insert(Vec::new())
18455 .push(range)
18456 }
18457 }
18458
18459 let proposed_changes_buffers = new_selections_by_buffer
18460 .into_iter()
18461 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18462 .collect::<Vec<_>>();
18463 let proposed_changes_editor = cx.new(|cx| {
18464 ProposedChangesEditor::new(
18465 "Proposed changes",
18466 proposed_changes_buffers,
18467 self.project.clone(),
18468 window,
18469 cx,
18470 )
18471 });
18472
18473 window.defer(cx, move |window, cx| {
18474 workspace.update(cx, |workspace, cx| {
18475 workspace.active_pane().update(cx, |pane, cx| {
18476 pane.add_item(
18477 Box::new(proposed_changes_editor),
18478 true,
18479 true,
18480 None,
18481 window,
18482 cx,
18483 );
18484 });
18485 });
18486 });
18487 }
18488
18489 pub fn open_excerpts_in_split(
18490 &mut self,
18491 _: &OpenExcerptsSplit,
18492 window: &mut Window,
18493 cx: &mut Context<Self>,
18494 ) {
18495 self.open_excerpts_common(None, true, window, cx)
18496 }
18497
18498 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18499 self.open_excerpts_common(None, false, window, cx)
18500 }
18501
18502 fn open_excerpts_common(
18503 &mut self,
18504 jump_data: Option<JumpData>,
18505 split: bool,
18506 window: &mut Window,
18507 cx: &mut Context<Self>,
18508 ) {
18509 let Some(workspace) = self.workspace() else {
18510 cx.propagate();
18511 return;
18512 };
18513
18514 if self.buffer.read(cx).is_singleton() {
18515 cx.propagate();
18516 return;
18517 }
18518
18519 let mut new_selections_by_buffer = HashMap::default();
18520 match &jump_data {
18521 Some(JumpData::MultiBufferPoint {
18522 excerpt_id,
18523 position,
18524 anchor,
18525 line_offset_from_top,
18526 }) => {
18527 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18528 if let Some(buffer) = multi_buffer_snapshot
18529 .buffer_id_for_excerpt(*excerpt_id)
18530 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18531 {
18532 let buffer_snapshot = buffer.read(cx).snapshot();
18533 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18534 language::ToPoint::to_point(anchor, &buffer_snapshot)
18535 } else {
18536 buffer_snapshot.clip_point(*position, Bias::Left)
18537 };
18538 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18539 new_selections_by_buffer.insert(
18540 buffer,
18541 (
18542 vec![jump_to_offset..jump_to_offset],
18543 Some(*line_offset_from_top),
18544 ),
18545 );
18546 }
18547 }
18548 Some(JumpData::MultiBufferRow {
18549 row,
18550 line_offset_from_top,
18551 }) => {
18552 let point = MultiBufferPoint::new(row.0, 0);
18553 if let Some((buffer, buffer_point, _)) =
18554 self.buffer.read(cx).point_to_buffer_point(point, cx)
18555 {
18556 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18557 new_selections_by_buffer
18558 .entry(buffer)
18559 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18560 .0
18561 .push(buffer_offset..buffer_offset)
18562 }
18563 }
18564 None => {
18565 let selections = self.selections.all::<usize>(cx);
18566 let multi_buffer = self.buffer.read(cx);
18567 for selection in selections {
18568 for (snapshot, range, _, anchor) in multi_buffer
18569 .snapshot(cx)
18570 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18571 {
18572 if let Some(anchor) = anchor {
18573 // selection is in a deleted hunk
18574 let Some(buffer_id) = anchor.buffer_id else {
18575 continue;
18576 };
18577 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18578 continue;
18579 };
18580 let offset = text::ToOffset::to_offset(
18581 &anchor.text_anchor,
18582 &buffer_handle.read(cx).snapshot(),
18583 );
18584 let range = offset..offset;
18585 new_selections_by_buffer
18586 .entry(buffer_handle)
18587 .or_insert((Vec::new(), None))
18588 .0
18589 .push(range)
18590 } else {
18591 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18592 else {
18593 continue;
18594 };
18595 new_selections_by_buffer
18596 .entry(buffer_handle)
18597 .or_insert((Vec::new(), None))
18598 .0
18599 .push(range)
18600 }
18601 }
18602 }
18603 }
18604 }
18605
18606 new_selections_by_buffer
18607 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18608
18609 if new_selections_by_buffer.is_empty() {
18610 return;
18611 }
18612
18613 // We defer the pane interaction because we ourselves are a workspace item
18614 // and activating a new item causes the pane to call a method on us reentrantly,
18615 // which panics if we're on the stack.
18616 window.defer(cx, move |window, cx| {
18617 workspace.update(cx, |workspace, cx| {
18618 let pane = if split {
18619 workspace.adjacent_pane(window, cx)
18620 } else {
18621 workspace.active_pane().clone()
18622 };
18623
18624 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18625 let editor = buffer
18626 .read(cx)
18627 .file()
18628 .is_none()
18629 .then(|| {
18630 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18631 // so `workspace.open_project_item` will never find them, always opening a new editor.
18632 // Instead, we try to activate the existing editor in the pane first.
18633 let (editor, pane_item_index) =
18634 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18635 let editor = item.downcast::<Editor>()?;
18636 let singleton_buffer =
18637 editor.read(cx).buffer().read(cx).as_singleton()?;
18638 if singleton_buffer == buffer {
18639 Some((editor, i))
18640 } else {
18641 None
18642 }
18643 })?;
18644 pane.update(cx, |pane, cx| {
18645 pane.activate_item(pane_item_index, true, true, window, cx)
18646 });
18647 Some(editor)
18648 })
18649 .flatten()
18650 .unwrap_or_else(|| {
18651 workspace.open_project_item::<Self>(
18652 pane.clone(),
18653 buffer,
18654 true,
18655 true,
18656 window,
18657 cx,
18658 )
18659 });
18660
18661 editor.update(cx, |editor, cx| {
18662 let autoscroll = match scroll_offset {
18663 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18664 None => Autoscroll::newest(),
18665 };
18666 let nav_history = editor.nav_history.take();
18667 editor.change_selections(Some(autoscroll), window, cx, |s| {
18668 s.select_ranges(ranges);
18669 });
18670 editor.nav_history = nav_history;
18671 });
18672 }
18673 })
18674 });
18675 }
18676
18677 // For now, don't allow opening excerpts in buffers that aren't backed by
18678 // regular project files.
18679 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18680 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18681 }
18682
18683 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18684 let snapshot = self.buffer.read(cx).read(cx);
18685 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18686 Some(
18687 ranges
18688 .iter()
18689 .map(move |range| {
18690 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18691 })
18692 .collect(),
18693 )
18694 }
18695
18696 fn selection_replacement_ranges(
18697 &self,
18698 range: Range<OffsetUtf16>,
18699 cx: &mut App,
18700 ) -> Vec<Range<OffsetUtf16>> {
18701 let selections = self.selections.all::<OffsetUtf16>(cx);
18702 let newest_selection = selections
18703 .iter()
18704 .max_by_key(|selection| selection.id)
18705 .unwrap();
18706 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18707 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18708 let snapshot = self.buffer.read(cx).read(cx);
18709 selections
18710 .into_iter()
18711 .map(|mut selection| {
18712 selection.start.0 =
18713 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18714 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18715 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18716 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18717 })
18718 .collect()
18719 }
18720
18721 fn report_editor_event(
18722 &self,
18723 event_type: &'static str,
18724 file_extension: Option<String>,
18725 cx: &App,
18726 ) {
18727 if cfg!(any(test, feature = "test-support")) {
18728 return;
18729 }
18730
18731 let Some(project) = &self.project else { return };
18732
18733 // If None, we are in a file without an extension
18734 let file = self
18735 .buffer
18736 .read(cx)
18737 .as_singleton()
18738 .and_then(|b| b.read(cx).file());
18739 let file_extension = file_extension.or(file
18740 .as_ref()
18741 .and_then(|file| Path::new(file.file_name(cx)).extension())
18742 .and_then(|e| e.to_str())
18743 .map(|a| a.to_string()));
18744
18745 let vim_mode = vim_enabled(cx);
18746
18747 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18748 let copilot_enabled = edit_predictions_provider
18749 == language::language_settings::EditPredictionProvider::Copilot;
18750 let copilot_enabled_for_language = self
18751 .buffer
18752 .read(cx)
18753 .language_settings(cx)
18754 .show_edit_predictions;
18755
18756 let project = project.read(cx);
18757 telemetry::event!(
18758 event_type,
18759 file_extension,
18760 vim_mode,
18761 copilot_enabled,
18762 copilot_enabled_for_language,
18763 edit_predictions_provider,
18764 is_via_ssh = project.is_via_ssh(),
18765 );
18766 }
18767
18768 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18769 /// with each line being an array of {text, highlight} objects.
18770 fn copy_highlight_json(
18771 &mut self,
18772 _: &CopyHighlightJson,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 #[derive(Serialize)]
18777 struct Chunk<'a> {
18778 text: String,
18779 highlight: Option<&'a str>,
18780 }
18781
18782 let snapshot = self.buffer.read(cx).snapshot(cx);
18783 let range = self
18784 .selected_text_range(false, window, cx)
18785 .and_then(|selection| {
18786 if selection.range.is_empty() {
18787 None
18788 } else {
18789 Some(selection.range)
18790 }
18791 })
18792 .unwrap_or_else(|| 0..snapshot.len());
18793
18794 let chunks = snapshot.chunks(range, true);
18795 let mut lines = Vec::new();
18796 let mut line: VecDeque<Chunk> = VecDeque::new();
18797
18798 let Some(style) = self.style.as_ref() else {
18799 return;
18800 };
18801
18802 for chunk in chunks {
18803 let highlight = chunk
18804 .syntax_highlight_id
18805 .and_then(|id| id.name(&style.syntax));
18806 let mut chunk_lines = chunk.text.split('\n').peekable();
18807 while let Some(text) = chunk_lines.next() {
18808 let mut merged_with_last_token = false;
18809 if let Some(last_token) = line.back_mut() {
18810 if last_token.highlight == highlight {
18811 last_token.text.push_str(text);
18812 merged_with_last_token = true;
18813 }
18814 }
18815
18816 if !merged_with_last_token {
18817 line.push_back(Chunk {
18818 text: text.into(),
18819 highlight,
18820 });
18821 }
18822
18823 if chunk_lines.peek().is_some() {
18824 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18825 line.pop_front();
18826 }
18827 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18828 line.pop_back();
18829 }
18830
18831 lines.push(mem::take(&mut line));
18832 }
18833 }
18834 }
18835
18836 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18837 return;
18838 };
18839 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18840 }
18841
18842 pub fn open_context_menu(
18843 &mut self,
18844 _: &OpenContextMenu,
18845 window: &mut Window,
18846 cx: &mut Context<Self>,
18847 ) {
18848 self.request_autoscroll(Autoscroll::newest(), cx);
18849 let position = self.selections.newest_display(cx).start;
18850 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18851 }
18852
18853 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18854 &self.inlay_hint_cache
18855 }
18856
18857 pub fn replay_insert_event(
18858 &mut self,
18859 text: &str,
18860 relative_utf16_range: Option<Range<isize>>,
18861 window: &mut Window,
18862 cx: &mut Context<Self>,
18863 ) {
18864 if !self.input_enabled {
18865 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18866 return;
18867 }
18868 if let Some(relative_utf16_range) = relative_utf16_range {
18869 let selections = self.selections.all::<OffsetUtf16>(cx);
18870 self.change_selections(None, window, cx, |s| {
18871 let new_ranges = selections.into_iter().map(|range| {
18872 let start = OffsetUtf16(
18873 range
18874 .head()
18875 .0
18876 .saturating_add_signed(relative_utf16_range.start),
18877 );
18878 let end = OffsetUtf16(
18879 range
18880 .head()
18881 .0
18882 .saturating_add_signed(relative_utf16_range.end),
18883 );
18884 start..end
18885 });
18886 s.select_ranges(new_ranges);
18887 });
18888 }
18889
18890 self.handle_input(text, window, cx);
18891 }
18892
18893 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18894 let Some(provider) = self.semantics_provider.as_ref() else {
18895 return false;
18896 };
18897
18898 let mut supports = false;
18899 self.buffer().update(cx, |this, cx| {
18900 this.for_each_buffer(|buffer| {
18901 supports |= provider.supports_inlay_hints(buffer, cx);
18902 });
18903 });
18904
18905 supports
18906 }
18907
18908 pub fn is_focused(&self, window: &Window) -> bool {
18909 self.focus_handle.is_focused(window)
18910 }
18911
18912 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18913 cx.emit(EditorEvent::Focused);
18914
18915 if let Some(descendant) = self
18916 .last_focused_descendant
18917 .take()
18918 .and_then(|descendant| descendant.upgrade())
18919 {
18920 window.focus(&descendant);
18921 } else {
18922 if let Some(blame) = self.blame.as_ref() {
18923 blame.update(cx, GitBlame::focus)
18924 }
18925
18926 self.blink_manager.update(cx, BlinkManager::enable);
18927 self.show_cursor_names(window, cx);
18928 self.buffer.update(cx, |buffer, cx| {
18929 buffer.finalize_last_transaction(cx);
18930 if self.leader_id.is_none() {
18931 buffer.set_active_selections(
18932 &self.selections.disjoint_anchors(),
18933 self.selections.line_mode,
18934 self.cursor_shape,
18935 cx,
18936 );
18937 }
18938 });
18939 }
18940 }
18941
18942 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18943 cx.emit(EditorEvent::FocusedIn)
18944 }
18945
18946 fn handle_focus_out(
18947 &mut self,
18948 event: FocusOutEvent,
18949 _window: &mut Window,
18950 cx: &mut Context<Self>,
18951 ) {
18952 if event.blurred != self.focus_handle {
18953 self.last_focused_descendant = Some(event.blurred);
18954 }
18955 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18956 }
18957
18958 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18959 self.blink_manager.update(cx, BlinkManager::disable);
18960 self.buffer
18961 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18962
18963 if let Some(blame) = self.blame.as_ref() {
18964 blame.update(cx, GitBlame::blur)
18965 }
18966 if !self.hover_state.focused(window, cx) {
18967 hide_hover(self, cx);
18968 }
18969 if !self
18970 .context_menu
18971 .borrow()
18972 .as_ref()
18973 .is_some_and(|context_menu| context_menu.focused(window, cx))
18974 {
18975 self.hide_context_menu(window, cx);
18976 }
18977 self.discard_inline_completion(false, cx);
18978 cx.emit(EditorEvent::Blurred);
18979 cx.notify();
18980 }
18981
18982 pub fn register_action<A: Action>(
18983 &mut self,
18984 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18985 ) -> Subscription {
18986 let id = self.next_editor_action_id.post_inc();
18987 let listener = Arc::new(listener);
18988 self.editor_actions.borrow_mut().insert(
18989 id,
18990 Box::new(move |window, _| {
18991 let listener = listener.clone();
18992 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18993 let action = action.downcast_ref().unwrap();
18994 if phase == DispatchPhase::Bubble {
18995 listener(action, window, cx)
18996 }
18997 })
18998 }),
18999 );
19000
19001 let editor_actions = self.editor_actions.clone();
19002 Subscription::new(move || {
19003 editor_actions.borrow_mut().remove(&id);
19004 })
19005 }
19006
19007 pub fn file_header_size(&self) -> u32 {
19008 FILE_HEADER_HEIGHT
19009 }
19010
19011 pub fn restore(
19012 &mut self,
19013 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19014 window: &mut Window,
19015 cx: &mut Context<Self>,
19016 ) {
19017 let workspace = self.workspace();
19018 let project = self.project.as_ref();
19019 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19020 let mut tasks = Vec::new();
19021 for (buffer_id, changes) in revert_changes {
19022 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19023 buffer.update(cx, |buffer, cx| {
19024 buffer.edit(
19025 changes
19026 .into_iter()
19027 .map(|(range, text)| (range, text.to_string())),
19028 None,
19029 cx,
19030 );
19031 });
19032
19033 if let Some(project) =
19034 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19035 {
19036 project.update(cx, |project, cx| {
19037 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19038 })
19039 }
19040 }
19041 }
19042 tasks
19043 });
19044 cx.spawn_in(window, async move |_, cx| {
19045 for (buffer, task) in save_tasks {
19046 let result = task.await;
19047 if result.is_err() {
19048 let Some(path) = buffer
19049 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19050 .ok()
19051 else {
19052 continue;
19053 };
19054 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19055 let Some(task) = cx
19056 .update_window_entity(&workspace, |workspace, window, cx| {
19057 workspace
19058 .open_path_preview(path, None, false, false, false, window, cx)
19059 })
19060 .ok()
19061 else {
19062 continue;
19063 };
19064 task.await.log_err();
19065 }
19066 }
19067 }
19068 })
19069 .detach();
19070 self.change_selections(None, window, cx, |selections| selections.refresh());
19071 }
19072
19073 pub fn to_pixel_point(
19074 &self,
19075 source: multi_buffer::Anchor,
19076 editor_snapshot: &EditorSnapshot,
19077 window: &mut Window,
19078 ) -> Option<gpui::Point<Pixels>> {
19079 let source_point = source.to_display_point(editor_snapshot);
19080 self.display_to_pixel_point(source_point, editor_snapshot, window)
19081 }
19082
19083 pub fn display_to_pixel_point(
19084 &self,
19085 source: DisplayPoint,
19086 editor_snapshot: &EditorSnapshot,
19087 window: &mut Window,
19088 ) -> Option<gpui::Point<Pixels>> {
19089 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19090 let text_layout_details = self.text_layout_details(window);
19091 let scroll_top = text_layout_details
19092 .scroll_anchor
19093 .scroll_position(editor_snapshot)
19094 .y;
19095
19096 if source.row().as_f32() < scroll_top.floor() {
19097 return None;
19098 }
19099 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19100 let source_y = line_height * (source.row().as_f32() - scroll_top);
19101 Some(gpui::Point::new(source_x, source_y))
19102 }
19103
19104 pub fn has_visible_completions_menu(&self) -> bool {
19105 !self.edit_prediction_preview_is_active()
19106 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19107 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19108 })
19109 }
19110
19111 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19112 if self.mode.is_minimap() {
19113 return;
19114 }
19115 self.addons
19116 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19117 }
19118
19119 pub fn unregister_addon<T: Addon>(&mut self) {
19120 self.addons.remove(&std::any::TypeId::of::<T>());
19121 }
19122
19123 pub fn addon<T: Addon>(&self) -> Option<&T> {
19124 let type_id = std::any::TypeId::of::<T>();
19125 self.addons
19126 .get(&type_id)
19127 .and_then(|item| item.to_any().downcast_ref::<T>())
19128 }
19129
19130 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19131 let type_id = std::any::TypeId::of::<T>();
19132 self.addons
19133 .get_mut(&type_id)
19134 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19135 }
19136
19137 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19138 let text_layout_details = self.text_layout_details(window);
19139 let style = &text_layout_details.editor_style;
19140 let font_id = window.text_system().resolve_font(&style.text.font());
19141 let font_size = style.text.font_size.to_pixels(window.rem_size());
19142 let line_height = style.text.line_height_in_pixels(window.rem_size());
19143 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19144
19145 gpui::Size::new(em_width, line_height)
19146 }
19147
19148 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19149 self.load_diff_task.clone()
19150 }
19151
19152 fn read_metadata_from_db(
19153 &mut self,
19154 item_id: u64,
19155 workspace_id: WorkspaceId,
19156 window: &mut Window,
19157 cx: &mut Context<Editor>,
19158 ) {
19159 if self.is_singleton(cx)
19160 && !self.mode.is_minimap()
19161 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19162 {
19163 let buffer_snapshot = OnceCell::new();
19164
19165 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19166 if !folds.is_empty() {
19167 let snapshot =
19168 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19169 self.fold_ranges(
19170 folds
19171 .into_iter()
19172 .map(|(start, end)| {
19173 snapshot.clip_offset(start, Bias::Left)
19174 ..snapshot.clip_offset(end, Bias::Right)
19175 })
19176 .collect(),
19177 false,
19178 window,
19179 cx,
19180 );
19181 }
19182 }
19183
19184 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19185 if !selections.is_empty() {
19186 let snapshot =
19187 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19188 self.change_selections(None, window, cx, |s| {
19189 s.select_ranges(selections.into_iter().map(|(start, end)| {
19190 snapshot.clip_offset(start, Bias::Left)
19191 ..snapshot.clip_offset(end, Bias::Right)
19192 }));
19193 });
19194 }
19195 };
19196 }
19197
19198 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19199 }
19200}
19201
19202fn vim_enabled(cx: &App) -> bool {
19203 cx.global::<SettingsStore>()
19204 .raw_user_settings()
19205 .get("vim_mode")
19206 == Some(&serde_json::Value::Bool(true))
19207}
19208
19209// Consider user intent and default settings
19210fn choose_completion_range(
19211 completion: &Completion,
19212 intent: CompletionIntent,
19213 buffer: &Entity<Buffer>,
19214 cx: &mut Context<Editor>,
19215) -> Range<usize> {
19216 fn should_replace(
19217 completion: &Completion,
19218 insert_range: &Range<text::Anchor>,
19219 intent: CompletionIntent,
19220 completion_mode_setting: LspInsertMode,
19221 buffer: &Buffer,
19222 ) -> bool {
19223 // specific actions take precedence over settings
19224 match intent {
19225 CompletionIntent::CompleteWithInsert => return false,
19226 CompletionIntent::CompleteWithReplace => return true,
19227 CompletionIntent::Complete | CompletionIntent::Compose => {}
19228 }
19229
19230 match completion_mode_setting {
19231 LspInsertMode::Insert => false,
19232 LspInsertMode::Replace => true,
19233 LspInsertMode::ReplaceSubsequence => {
19234 let mut text_to_replace = buffer.chars_for_range(
19235 buffer.anchor_before(completion.replace_range.start)
19236 ..buffer.anchor_after(completion.replace_range.end),
19237 );
19238 let mut completion_text = completion.new_text.chars();
19239
19240 // is `text_to_replace` a subsequence of `completion_text`
19241 text_to_replace
19242 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19243 }
19244 LspInsertMode::ReplaceSuffix => {
19245 let range_after_cursor = insert_range.end..completion.replace_range.end;
19246
19247 let text_after_cursor = buffer
19248 .text_for_range(
19249 buffer.anchor_before(range_after_cursor.start)
19250 ..buffer.anchor_after(range_after_cursor.end),
19251 )
19252 .collect::<String>();
19253 completion.new_text.ends_with(&text_after_cursor)
19254 }
19255 }
19256 }
19257
19258 let buffer = buffer.read(cx);
19259
19260 if let CompletionSource::Lsp {
19261 insert_range: Some(insert_range),
19262 ..
19263 } = &completion.source
19264 {
19265 let completion_mode_setting =
19266 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19267 .completions
19268 .lsp_insert_mode;
19269
19270 if !should_replace(
19271 completion,
19272 &insert_range,
19273 intent,
19274 completion_mode_setting,
19275 buffer,
19276 ) {
19277 return insert_range.to_offset(buffer);
19278 }
19279 }
19280
19281 completion.replace_range.to_offset(buffer)
19282}
19283
19284fn insert_extra_newline_brackets(
19285 buffer: &MultiBufferSnapshot,
19286 range: Range<usize>,
19287 language: &language::LanguageScope,
19288) -> bool {
19289 let leading_whitespace_len = buffer
19290 .reversed_chars_at(range.start)
19291 .take_while(|c| c.is_whitespace() && *c != '\n')
19292 .map(|c| c.len_utf8())
19293 .sum::<usize>();
19294 let trailing_whitespace_len = buffer
19295 .chars_at(range.end)
19296 .take_while(|c| c.is_whitespace() && *c != '\n')
19297 .map(|c| c.len_utf8())
19298 .sum::<usize>();
19299 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19300
19301 language.brackets().any(|(pair, enabled)| {
19302 let pair_start = pair.start.trim_end();
19303 let pair_end = pair.end.trim_start();
19304
19305 enabled
19306 && pair.newline
19307 && buffer.contains_str_at(range.end, pair_end)
19308 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19309 })
19310}
19311
19312fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19313 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19314 [(buffer, range, _)] => (*buffer, range.clone()),
19315 _ => return false,
19316 };
19317 let pair = {
19318 let mut result: Option<BracketMatch> = None;
19319
19320 for pair in buffer
19321 .all_bracket_ranges(range.clone())
19322 .filter(move |pair| {
19323 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19324 })
19325 {
19326 let len = pair.close_range.end - pair.open_range.start;
19327
19328 if let Some(existing) = &result {
19329 let existing_len = existing.close_range.end - existing.open_range.start;
19330 if len > existing_len {
19331 continue;
19332 }
19333 }
19334
19335 result = Some(pair);
19336 }
19337
19338 result
19339 };
19340 let Some(pair) = pair else {
19341 return false;
19342 };
19343 pair.newline_only
19344 && buffer
19345 .chars_for_range(pair.open_range.end..range.start)
19346 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19347 .all(|c| c.is_whitespace() && c != '\n')
19348}
19349
19350fn update_uncommitted_diff_for_buffer(
19351 editor: Entity<Editor>,
19352 project: &Entity<Project>,
19353 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19354 buffer: Entity<MultiBuffer>,
19355 cx: &mut App,
19356) -> Task<()> {
19357 let mut tasks = Vec::new();
19358 project.update(cx, |project, cx| {
19359 for buffer in buffers {
19360 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19361 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19362 }
19363 }
19364 });
19365 cx.spawn(async move |cx| {
19366 let diffs = future::join_all(tasks).await;
19367 if editor
19368 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19369 .unwrap_or(false)
19370 {
19371 return;
19372 }
19373
19374 buffer
19375 .update(cx, |buffer, cx| {
19376 for diff in diffs.into_iter().flatten() {
19377 buffer.add_diff(diff, cx);
19378 }
19379 })
19380 .ok();
19381 })
19382}
19383
19384fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19385 let tab_size = tab_size.get() as usize;
19386 let mut width = offset;
19387
19388 for ch in text.chars() {
19389 width += if ch == '\t' {
19390 tab_size - (width % tab_size)
19391 } else {
19392 1
19393 };
19394 }
19395
19396 width - offset
19397}
19398
19399#[cfg(test)]
19400mod tests {
19401 use super::*;
19402
19403 #[test]
19404 fn test_string_size_with_expanded_tabs() {
19405 let nz = |val| NonZeroU32::new(val).unwrap();
19406 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19407 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19408 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19409 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19410 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19411 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19412 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19413 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19414 }
19415}
19416
19417/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19418struct WordBreakingTokenizer<'a> {
19419 input: &'a str,
19420}
19421
19422impl<'a> WordBreakingTokenizer<'a> {
19423 fn new(input: &'a str) -> Self {
19424 Self { input }
19425 }
19426}
19427
19428fn is_char_ideographic(ch: char) -> bool {
19429 use unicode_script::Script::*;
19430 use unicode_script::UnicodeScript;
19431 matches!(ch.script(), Han | Tangut | Yi)
19432}
19433
19434fn is_grapheme_ideographic(text: &str) -> bool {
19435 text.chars().any(is_char_ideographic)
19436}
19437
19438fn is_grapheme_whitespace(text: &str) -> bool {
19439 text.chars().any(|x| x.is_whitespace())
19440}
19441
19442fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19443 text.chars().next().map_or(false, |ch| {
19444 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19445 })
19446}
19447
19448#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19449enum WordBreakToken<'a> {
19450 Word { token: &'a str, grapheme_len: usize },
19451 InlineWhitespace { token: &'a str, grapheme_len: usize },
19452 Newline,
19453}
19454
19455impl<'a> Iterator for WordBreakingTokenizer<'a> {
19456 /// Yields a span, the count of graphemes in the token, and whether it was
19457 /// whitespace. Note that it also breaks at word boundaries.
19458 type Item = WordBreakToken<'a>;
19459
19460 fn next(&mut self) -> Option<Self::Item> {
19461 use unicode_segmentation::UnicodeSegmentation;
19462 if self.input.is_empty() {
19463 return None;
19464 }
19465
19466 let mut iter = self.input.graphemes(true).peekable();
19467 let mut offset = 0;
19468 let mut grapheme_len = 0;
19469 if let Some(first_grapheme) = iter.next() {
19470 let is_newline = first_grapheme == "\n";
19471 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19472 offset += first_grapheme.len();
19473 grapheme_len += 1;
19474 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19475 if let Some(grapheme) = iter.peek().copied() {
19476 if should_stay_with_preceding_ideograph(grapheme) {
19477 offset += grapheme.len();
19478 grapheme_len += 1;
19479 }
19480 }
19481 } else {
19482 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19483 let mut next_word_bound = words.peek().copied();
19484 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19485 next_word_bound = words.next();
19486 }
19487 while let Some(grapheme) = iter.peek().copied() {
19488 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19489 break;
19490 };
19491 if is_grapheme_whitespace(grapheme) != is_whitespace
19492 || (grapheme == "\n") != is_newline
19493 {
19494 break;
19495 };
19496 offset += grapheme.len();
19497 grapheme_len += 1;
19498 iter.next();
19499 }
19500 }
19501 let token = &self.input[..offset];
19502 self.input = &self.input[offset..];
19503 if token == "\n" {
19504 Some(WordBreakToken::Newline)
19505 } else if is_whitespace {
19506 Some(WordBreakToken::InlineWhitespace {
19507 token,
19508 grapheme_len,
19509 })
19510 } else {
19511 Some(WordBreakToken::Word {
19512 token,
19513 grapheme_len,
19514 })
19515 }
19516 } else {
19517 None
19518 }
19519 }
19520}
19521
19522#[test]
19523fn test_word_breaking_tokenizer() {
19524 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19525 ("", &[]),
19526 (" ", &[whitespace(" ", 2)]),
19527 ("Ʒ", &[word("Ʒ", 1)]),
19528 ("Ǽ", &[word("Ǽ", 1)]),
19529 ("⋑", &[word("⋑", 1)]),
19530 ("⋑⋑", &[word("⋑⋑", 2)]),
19531 (
19532 "原理,进而",
19533 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19534 ),
19535 (
19536 "hello world",
19537 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19538 ),
19539 (
19540 "hello, world",
19541 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19542 ),
19543 (
19544 " hello world",
19545 &[
19546 whitespace(" ", 2),
19547 word("hello", 5),
19548 whitespace(" ", 1),
19549 word("world", 5),
19550 ],
19551 ),
19552 (
19553 "这是什么 \n 钢笔",
19554 &[
19555 word("这", 1),
19556 word("是", 1),
19557 word("什", 1),
19558 word("么", 1),
19559 whitespace(" ", 1),
19560 newline(),
19561 whitespace(" ", 1),
19562 word("钢", 1),
19563 word("笔", 1),
19564 ],
19565 ),
19566 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19567 ];
19568
19569 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19570 WordBreakToken::Word {
19571 token,
19572 grapheme_len,
19573 }
19574 }
19575
19576 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19577 WordBreakToken::InlineWhitespace {
19578 token,
19579 grapheme_len,
19580 }
19581 }
19582
19583 fn newline() -> WordBreakToken<'static> {
19584 WordBreakToken::Newline
19585 }
19586
19587 for (input, result) in tests {
19588 assert_eq!(
19589 WordBreakingTokenizer::new(input)
19590 .collect::<Vec<_>>()
19591 .as_slice(),
19592 *result,
19593 );
19594 }
19595}
19596
19597fn wrap_with_prefix(
19598 line_prefix: String,
19599 unwrapped_text: String,
19600 wrap_column: usize,
19601 tab_size: NonZeroU32,
19602 preserve_existing_whitespace: bool,
19603) -> String {
19604 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19605 let mut wrapped_text = String::new();
19606 let mut current_line = line_prefix.clone();
19607
19608 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19609 let mut current_line_len = line_prefix_len;
19610 let mut in_whitespace = false;
19611 for token in tokenizer {
19612 let have_preceding_whitespace = in_whitespace;
19613 match token {
19614 WordBreakToken::Word {
19615 token,
19616 grapheme_len,
19617 } => {
19618 in_whitespace = false;
19619 if current_line_len + grapheme_len > wrap_column
19620 && current_line_len != line_prefix_len
19621 {
19622 wrapped_text.push_str(current_line.trim_end());
19623 wrapped_text.push('\n');
19624 current_line.truncate(line_prefix.len());
19625 current_line_len = line_prefix_len;
19626 }
19627 current_line.push_str(token);
19628 current_line_len += grapheme_len;
19629 }
19630 WordBreakToken::InlineWhitespace {
19631 mut token,
19632 mut grapheme_len,
19633 } => {
19634 in_whitespace = true;
19635 if have_preceding_whitespace && !preserve_existing_whitespace {
19636 continue;
19637 }
19638 if !preserve_existing_whitespace {
19639 token = " ";
19640 grapheme_len = 1;
19641 }
19642 if current_line_len + grapheme_len > wrap_column {
19643 wrapped_text.push_str(current_line.trim_end());
19644 wrapped_text.push('\n');
19645 current_line.truncate(line_prefix.len());
19646 current_line_len = line_prefix_len;
19647 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19648 current_line.push_str(token);
19649 current_line_len += grapheme_len;
19650 }
19651 }
19652 WordBreakToken::Newline => {
19653 in_whitespace = true;
19654 if preserve_existing_whitespace {
19655 wrapped_text.push_str(current_line.trim_end());
19656 wrapped_text.push('\n');
19657 current_line.truncate(line_prefix.len());
19658 current_line_len = line_prefix_len;
19659 } else if have_preceding_whitespace {
19660 continue;
19661 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19662 {
19663 wrapped_text.push_str(current_line.trim_end());
19664 wrapped_text.push('\n');
19665 current_line.truncate(line_prefix.len());
19666 current_line_len = line_prefix_len;
19667 } else if current_line_len != line_prefix_len {
19668 current_line.push(' ');
19669 current_line_len += 1;
19670 }
19671 }
19672 }
19673 }
19674
19675 if !current_line.is_empty() {
19676 wrapped_text.push_str(¤t_line);
19677 }
19678 wrapped_text
19679}
19680
19681#[test]
19682fn test_wrap_with_prefix() {
19683 assert_eq!(
19684 wrap_with_prefix(
19685 "# ".to_string(),
19686 "abcdefg".to_string(),
19687 4,
19688 NonZeroU32::new(4).unwrap(),
19689 false,
19690 ),
19691 "# abcdefg"
19692 );
19693 assert_eq!(
19694 wrap_with_prefix(
19695 "".to_string(),
19696 "\thello world".to_string(),
19697 8,
19698 NonZeroU32::new(4).unwrap(),
19699 false,
19700 ),
19701 "hello\nworld"
19702 );
19703 assert_eq!(
19704 wrap_with_prefix(
19705 "// ".to_string(),
19706 "xx \nyy zz aa bb cc".to_string(),
19707 12,
19708 NonZeroU32::new(4).unwrap(),
19709 false,
19710 ),
19711 "// xx yy zz\n// aa bb cc"
19712 );
19713 assert_eq!(
19714 wrap_with_prefix(
19715 String::new(),
19716 "这是什么 \n 钢笔".to_string(),
19717 3,
19718 NonZeroU32::new(4).unwrap(),
19719 false,
19720 ),
19721 "这是什\n么 钢\n笔"
19722 );
19723}
19724
19725pub trait CollaborationHub {
19726 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19727 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19728 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19729}
19730
19731impl CollaborationHub for Entity<Project> {
19732 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19733 self.read(cx).collaborators()
19734 }
19735
19736 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19737 self.read(cx).user_store().read(cx).participant_indices()
19738 }
19739
19740 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19741 let this = self.read(cx);
19742 let user_ids = this.collaborators().values().map(|c| c.user_id);
19743 this.user_store().read_with(cx, |user_store, cx| {
19744 user_store.participant_names(user_ids, cx)
19745 })
19746 }
19747}
19748
19749pub trait SemanticsProvider {
19750 fn hover(
19751 &self,
19752 buffer: &Entity<Buffer>,
19753 position: text::Anchor,
19754 cx: &mut App,
19755 ) -> Option<Task<Vec<project::Hover>>>;
19756
19757 fn inline_values(
19758 &self,
19759 buffer_handle: Entity<Buffer>,
19760 range: Range<text::Anchor>,
19761 cx: &mut App,
19762 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19763
19764 fn inlay_hints(
19765 &self,
19766 buffer_handle: Entity<Buffer>,
19767 range: Range<text::Anchor>,
19768 cx: &mut App,
19769 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19770
19771 fn resolve_inlay_hint(
19772 &self,
19773 hint: InlayHint,
19774 buffer_handle: Entity<Buffer>,
19775 server_id: LanguageServerId,
19776 cx: &mut App,
19777 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19778
19779 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19780
19781 fn document_highlights(
19782 &self,
19783 buffer: &Entity<Buffer>,
19784 position: text::Anchor,
19785 cx: &mut App,
19786 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19787
19788 fn definitions(
19789 &self,
19790 buffer: &Entity<Buffer>,
19791 position: text::Anchor,
19792 kind: GotoDefinitionKind,
19793 cx: &mut App,
19794 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19795
19796 fn range_for_rename(
19797 &self,
19798 buffer: &Entity<Buffer>,
19799 position: text::Anchor,
19800 cx: &mut App,
19801 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19802
19803 fn perform_rename(
19804 &self,
19805 buffer: &Entity<Buffer>,
19806 position: text::Anchor,
19807 new_name: String,
19808 cx: &mut App,
19809 ) -> Option<Task<Result<ProjectTransaction>>>;
19810}
19811
19812pub trait CompletionProvider {
19813 fn completions(
19814 &self,
19815 excerpt_id: ExcerptId,
19816 buffer: &Entity<Buffer>,
19817 buffer_position: text::Anchor,
19818 trigger: CompletionContext,
19819 window: &mut Window,
19820 cx: &mut Context<Editor>,
19821 ) -> Task<Result<Option<Vec<Completion>>>>;
19822
19823 fn resolve_completions(
19824 &self,
19825 buffer: Entity<Buffer>,
19826 completion_indices: Vec<usize>,
19827 completions: Rc<RefCell<Box<[Completion]>>>,
19828 cx: &mut Context<Editor>,
19829 ) -> Task<Result<bool>>;
19830
19831 fn apply_additional_edits_for_completion(
19832 &self,
19833 _buffer: Entity<Buffer>,
19834 _completions: Rc<RefCell<Box<[Completion]>>>,
19835 _completion_index: usize,
19836 _push_to_history: bool,
19837 _cx: &mut Context<Editor>,
19838 ) -> Task<Result<Option<language::Transaction>>> {
19839 Task::ready(Ok(None))
19840 }
19841
19842 fn is_completion_trigger(
19843 &self,
19844 buffer: &Entity<Buffer>,
19845 position: language::Anchor,
19846 text: &str,
19847 trigger_in_words: bool,
19848 cx: &mut Context<Editor>,
19849 ) -> bool;
19850
19851 fn sort_completions(&self) -> bool {
19852 true
19853 }
19854
19855 fn filter_completions(&self) -> bool {
19856 true
19857 }
19858}
19859
19860pub trait CodeActionProvider {
19861 fn id(&self) -> Arc<str>;
19862
19863 fn code_actions(
19864 &self,
19865 buffer: &Entity<Buffer>,
19866 range: Range<text::Anchor>,
19867 window: &mut Window,
19868 cx: &mut App,
19869 ) -> Task<Result<Vec<CodeAction>>>;
19870
19871 fn apply_code_action(
19872 &self,
19873 buffer_handle: Entity<Buffer>,
19874 action: CodeAction,
19875 excerpt_id: ExcerptId,
19876 push_to_history: bool,
19877 window: &mut Window,
19878 cx: &mut App,
19879 ) -> Task<Result<ProjectTransaction>>;
19880}
19881
19882impl CodeActionProvider for Entity<Project> {
19883 fn id(&self) -> Arc<str> {
19884 "project".into()
19885 }
19886
19887 fn code_actions(
19888 &self,
19889 buffer: &Entity<Buffer>,
19890 range: Range<text::Anchor>,
19891 _window: &mut Window,
19892 cx: &mut App,
19893 ) -> Task<Result<Vec<CodeAction>>> {
19894 self.update(cx, |project, cx| {
19895 let code_lens = project.code_lens(buffer, range.clone(), cx);
19896 let code_actions = project.code_actions(buffer, range, None, cx);
19897 cx.background_spawn(async move {
19898 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19899 Ok(code_lens
19900 .context("code lens fetch")?
19901 .into_iter()
19902 .chain(code_actions.context("code action fetch")?)
19903 .collect())
19904 })
19905 })
19906 }
19907
19908 fn apply_code_action(
19909 &self,
19910 buffer_handle: Entity<Buffer>,
19911 action: CodeAction,
19912 _excerpt_id: ExcerptId,
19913 push_to_history: bool,
19914 _window: &mut Window,
19915 cx: &mut App,
19916 ) -> Task<Result<ProjectTransaction>> {
19917 self.update(cx, |project, cx| {
19918 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19919 })
19920 }
19921}
19922
19923fn snippet_completions(
19924 project: &Project,
19925 buffer: &Entity<Buffer>,
19926 buffer_position: text::Anchor,
19927 cx: &mut App,
19928) -> Task<Result<Vec<Completion>>> {
19929 let languages = buffer.read(cx).languages_at(buffer_position);
19930 let snippet_store = project.snippets().read(cx);
19931
19932 let scopes: Vec<_> = languages
19933 .iter()
19934 .filter_map(|language| {
19935 let language_name = language.lsp_id();
19936 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19937
19938 if snippets.is_empty() {
19939 None
19940 } else {
19941 Some((language.default_scope(), snippets))
19942 }
19943 })
19944 .collect();
19945
19946 if scopes.is_empty() {
19947 return Task::ready(Ok(vec![]));
19948 }
19949
19950 let snapshot = buffer.read(cx).text_snapshot();
19951 let chars: String = snapshot
19952 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19953 .collect();
19954 let executor = cx.background_executor().clone();
19955
19956 cx.background_spawn(async move {
19957 let mut all_results: Vec<Completion> = Vec::new();
19958 for (scope, snippets) in scopes.into_iter() {
19959 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19960 let mut last_word = chars
19961 .chars()
19962 .take_while(|c| classifier.is_word(*c))
19963 .collect::<String>();
19964 last_word = last_word.chars().rev().collect();
19965
19966 if last_word.is_empty() {
19967 return Ok(vec![]);
19968 }
19969
19970 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19971 let to_lsp = |point: &text::Anchor| {
19972 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19973 point_to_lsp(end)
19974 };
19975 let lsp_end = to_lsp(&buffer_position);
19976
19977 let candidates = snippets
19978 .iter()
19979 .enumerate()
19980 .flat_map(|(ix, snippet)| {
19981 snippet
19982 .prefix
19983 .iter()
19984 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19985 })
19986 .collect::<Vec<StringMatchCandidate>>();
19987
19988 let mut matches = fuzzy::match_strings(
19989 &candidates,
19990 &last_word,
19991 last_word.chars().any(|c| c.is_uppercase()),
19992 100,
19993 &Default::default(),
19994 executor.clone(),
19995 )
19996 .await;
19997
19998 // Remove all candidates where the query's start does not match the start of any word in the candidate
19999 if let Some(query_start) = last_word.chars().next() {
20000 matches.retain(|string_match| {
20001 split_words(&string_match.string).any(|word| {
20002 // Check that the first codepoint of the word as lowercase matches the first
20003 // codepoint of the query as lowercase
20004 word.chars()
20005 .flat_map(|codepoint| codepoint.to_lowercase())
20006 .zip(query_start.to_lowercase())
20007 .all(|(word_cp, query_cp)| word_cp == query_cp)
20008 })
20009 });
20010 }
20011
20012 let matched_strings = matches
20013 .into_iter()
20014 .map(|m| m.string)
20015 .collect::<HashSet<_>>();
20016
20017 let mut result: Vec<Completion> = snippets
20018 .iter()
20019 .filter_map(|snippet| {
20020 let matching_prefix = snippet
20021 .prefix
20022 .iter()
20023 .find(|prefix| matched_strings.contains(*prefix))?;
20024 let start = as_offset - last_word.len();
20025 let start = snapshot.anchor_before(start);
20026 let range = start..buffer_position;
20027 let lsp_start = to_lsp(&start);
20028 let lsp_range = lsp::Range {
20029 start: lsp_start,
20030 end: lsp_end,
20031 };
20032 Some(Completion {
20033 replace_range: range,
20034 new_text: snippet.body.clone(),
20035 source: CompletionSource::Lsp {
20036 insert_range: None,
20037 server_id: LanguageServerId(usize::MAX),
20038 resolved: true,
20039 lsp_completion: Box::new(lsp::CompletionItem {
20040 label: snippet.prefix.first().unwrap().clone(),
20041 kind: Some(CompletionItemKind::SNIPPET),
20042 label_details: snippet.description.as_ref().map(|description| {
20043 lsp::CompletionItemLabelDetails {
20044 detail: Some(description.clone()),
20045 description: None,
20046 }
20047 }),
20048 insert_text_format: Some(InsertTextFormat::SNIPPET),
20049 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20050 lsp::InsertReplaceEdit {
20051 new_text: snippet.body.clone(),
20052 insert: lsp_range,
20053 replace: lsp_range,
20054 },
20055 )),
20056 filter_text: Some(snippet.body.clone()),
20057 sort_text: Some(char::MAX.to_string()),
20058 ..lsp::CompletionItem::default()
20059 }),
20060 lsp_defaults: None,
20061 },
20062 label: CodeLabel {
20063 text: matching_prefix.clone(),
20064 runs: Vec::new(),
20065 filter_range: 0..matching_prefix.len(),
20066 },
20067 icon_path: None,
20068 documentation: Some(
20069 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20070 single_line: snippet.name.clone().into(),
20071 plain_text: snippet
20072 .description
20073 .clone()
20074 .map(|description| description.into()),
20075 },
20076 ),
20077 insert_text_mode: None,
20078 confirm: None,
20079 })
20080 })
20081 .collect();
20082
20083 all_results.append(&mut result);
20084 }
20085
20086 Ok(all_results)
20087 })
20088}
20089
20090impl CompletionProvider for Entity<Project> {
20091 fn completions(
20092 &self,
20093 _excerpt_id: ExcerptId,
20094 buffer: &Entity<Buffer>,
20095 buffer_position: text::Anchor,
20096 options: CompletionContext,
20097 _window: &mut Window,
20098 cx: &mut Context<Editor>,
20099 ) -> Task<Result<Option<Vec<Completion>>>> {
20100 self.update(cx, |project, cx| {
20101 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20102 let project_completions = project.completions(buffer, buffer_position, options, cx);
20103 cx.background_spawn(async move {
20104 let snippets_completions = snippets.await?;
20105 match project_completions.await? {
20106 Some(mut completions) => {
20107 completions.extend(snippets_completions);
20108 Ok(Some(completions))
20109 }
20110 None => {
20111 if snippets_completions.is_empty() {
20112 Ok(None)
20113 } else {
20114 Ok(Some(snippets_completions))
20115 }
20116 }
20117 }
20118 })
20119 })
20120 }
20121
20122 fn resolve_completions(
20123 &self,
20124 buffer: Entity<Buffer>,
20125 completion_indices: Vec<usize>,
20126 completions: Rc<RefCell<Box<[Completion]>>>,
20127 cx: &mut Context<Editor>,
20128 ) -> Task<Result<bool>> {
20129 self.update(cx, |project, cx| {
20130 project.lsp_store().update(cx, |lsp_store, cx| {
20131 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20132 })
20133 })
20134 }
20135
20136 fn apply_additional_edits_for_completion(
20137 &self,
20138 buffer: Entity<Buffer>,
20139 completions: Rc<RefCell<Box<[Completion]>>>,
20140 completion_index: usize,
20141 push_to_history: bool,
20142 cx: &mut Context<Editor>,
20143 ) -> Task<Result<Option<language::Transaction>>> {
20144 self.update(cx, |project, cx| {
20145 project.lsp_store().update(cx, |lsp_store, cx| {
20146 lsp_store.apply_additional_edits_for_completion(
20147 buffer,
20148 completions,
20149 completion_index,
20150 push_to_history,
20151 cx,
20152 )
20153 })
20154 })
20155 }
20156
20157 fn is_completion_trigger(
20158 &self,
20159 buffer: &Entity<Buffer>,
20160 position: language::Anchor,
20161 text: &str,
20162 trigger_in_words: bool,
20163 cx: &mut Context<Editor>,
20164 ) -> bool {
20165 let mut chars = text.chars();
20166 let char = if let Some(char) = chars.next() {
20167 char
20168 } else {
20169 return false;
20170 };
20171 if chars.next().is_some() {
20172 return false;
20173 }
20174
20175 let buffer = buffer.read(cx);
20176 let snapshot = buffer.snapshot();
20177 if !snapshot.settings_at(position, cx).show_completions_on_input {
20178 return false;
20179 }
20180 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20181 if trigger_in_words && classifier.is_word(char) {
20182 return true;
20183 }
20184
20185 buffer.completion_triggers().contains(text)
20186 }
20187}
20188
20189impl SemanticsProvider for Entity<Project> {
20190 fn hover(
20191 &self,
20192 buffer: &Entity<Buffer>,
20193 position: text::Anchor,
20194 cx: &mut App,
20195 ) -> Option<Task<Vec<project::Hover>>> {
20196 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20197 }
20198
20199 fn document_highlights(
20200 &self,
20201 buffer: &Entity<Buffer>,
20202 position: text::Anchor,
20203 cx: &mut App,
20204 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20205 Some(self.update(cx, |project, cx| {
20206 project.document_highlights(buffer, position, cx)
20207 }))
20208 }
20209
20210 fn definitions(
20211 &self,
20212 buffer: &Entity<Buffer>,
20213 position: text::Anchor,
20214 kind: GotoDefinitionKind,
20215 cx: &mut App,
20216 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20217 Some(self.update(cx, |project, cx| match kind {
20218 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20219 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20220 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20221 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20222 }))
20223 }
20224
20225 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20226 // TODO: make this work for remote projects
20227 self.update(cx, |project, cx| {
20228 if project
20229 .active_debug_session(cx)
20230 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20231 {
20232 return true;
20233 }
20234
20235 buffer.update(cx, |buffer, cx| {
20236 project.any_language_server_supports_inlay_hints(buffer, cx)
20237 })
20238 })
20239 }
20240
20241 fn inline_values(
20242 &self,
20243 buffer_handle: Entity<Buffer>,
20244
20245 range: Range<text::Anchor>,
20246 cx: &mut App,
20247 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20248 self.update(cx, |project, cx| {
20249 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20250
20251 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20252 })
20253 }
20254
20255 fn inlay_hints(
20256 &self,
20257 buffer_handle: Entity<Buffer>,
20258 range: Range<text::Anchor>,
20259 cx: &mut App,
20260 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20261 Some(self.update(cx, |project, cx| {
20262 project.inlay_hints(buffer_handle, range, cx)
20263 }))
20264 }
20265
20266 fn resolve_inlay_hint(
20267 &self,
20268 hint: InlayHint,
20269 buffer_handle: Entity<Buffer>,
20270 server_id: LanguageServerId,
20271 cx: &mut App,
20272 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20273 Some(self.update(cx, |project, cx| {
20274 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20275 }))
20276 }
20277
20278 fn range_for_rename(
20279 &self,
20280 buffer: &Entity<Buffer>,
20281 position: text::Anchor,
20282 cx: &mut App,
20283 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20284 Some(self.update(cx, |project, cx| {
20285 let buffer = buffer.clone();
20286 let task = project.prepare_rename(buffer.clone(), position, cx);
20287 cx.spawn(async move |_, cx| {
20288 Ok(match task.await? {
20289 PrepareRenameResponse::Success(range) => Some(range),
20290 PrepareRenameResponse::InvalidPosition => None,
20291 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20292 // Fallback on using TreeSitter info to determine identifier range
20293 buffer.update(cx, |buffer, _| {
20294 let snapshot = buffer.snapshot();
20295 let (range, kind) = snapshot.surrounding_word(position);
20296 if kind != Some(CharKind::Word) {
20297 return None;
20298 }
20299 Some(
20300 snapshot.anchor_before(range.start)
20301 ..snapshot.anchor_after(range.end),
20302 )
20303 })?
20304 }
20305 })
20306 })
20307 }))
20308 }
20309
20310 fn perform_rename(
20311 &self,
20312 buffer: &Entity<Buffer>,
20313 position: text::Anchor,
20314 new_name: String,
20315 cx: &mut App,
20316 ) -> Option<Task<Result<ProjectTransaction>>> {
20317 Some(self.update(cx, |project, cx| {
20318 project.perform_rename(buffer.clone(), position, new_name, cx)
20319 }))
20320 }
20321}
20322
20323fn inlay_hint_settings(
20324 location: Anchor,
20325 snapshot: &MultiBufferSnapshot,
20326 cx: &mut Context<Editor>,
20327) -> InlayHintSettings {
20328 let file = snapshot.file_at(location);
20329 let language = snapshot.language_at(location).map(|l| l.name());
20330 language_settings(language, file, cx).inlay_hints
20331}
20332
20333fn consume_contiguous_rows(
20334 contiguous_row_selections: &mut Vec<Selection<Point>>,
20335 selection: &Selection<Point>,
20336 display_map: &DisplaySnapshot,
20337 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20338) -> (MultiBufferRow, MultiBufferRow) {
20339 contiguous_row_selections.push(selection.clone());
20340 let start_row = MultiBufferRow(selection.start.row);
20341 let mut end_row = ending_row(selection, display_map);
20342
20343 while let Some(next_selection) = selections.peek() {
20344 if next_selection.start.row <= end_row.0 {
20345 end_row = ending_row(next_selection, display_map);
20346 contiguous_row_selections.push(selections.next().unwrap().clone());
20347 } else {
20348 break;
20349 }
20350 }
20351 (start_row, end_row)
20352}
20353
20354fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20355 if next_selection.end.column > 0 || next_selection.is_empty() {
20356 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20357 } else {
20358 MultiBufferRow(next_selection.end.row)
20359 }
20360}
20361
20362impl EditorSnapshot {
20363 pub fn remote_selections_in_range<'a>(
20364 &'a self,
20365 range: &'a Range<Anchor>,
20366 collaboration_hub: &dyn CollaborationHub,
20367 cx: &'a App,
20368 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20369 let participant_names = collaboration_hub.user_names(cx);
20370 let participant_indices = collaboration_hub.user_participant_indices(cx);
20371 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20372 let collaborators_by_replica_id = collaborators_by_peer_id
20373 .values()
20374 .map(|collaborator| (collaborator.replica_id, collaborator))
20375 .collect::<HashMap<_, _>>();
20376 self.buffer_snapshot
20377 .selections_in_range(range, false)
20378 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20379 if replica_id == AGENT_REPLICA_ID {
20380 Some(RemoteSelection {
20381 replica_id,
20382 selection,
20383 cursor_shape,
20384 line_mode,
20385 collaborator_id: CollaboratorId::Agent,
20386 user_name: Some("Agent".into()),
20387 color: cx.theme().players().agent(),
20388 })
20389 } else {
20390 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20391 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20392 let user_name = participant_names.get(&collaborator.user_id).cloned();
20393 Some(RemoteSelection {
20394 replica_id,
20395 selection,
20396 cursor_shape,
20397 line_mode,
20398 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20399 user_name,
20400 color: if let Some(index) = participant_index {
20401 cx.theme().players().color_for_participant(index.0)
20402 } else {
20403 cx.theme().players().absent()
20404 },
20405 })
20406 }
20407 })
20408 }
20409
20410 pub fn hunks_for_ranges(
20411 &self,
20412 ranges: impl IntoIterator<Item = Range<Point>>,
20413 ) -> Vec<MultiBufferDiffHunk> {
20414 let mut hunks = Vec::new();
20415 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20416 HashMap::default();
20417 for query_range in ranges {
20418 let query_rows =
20419 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20420 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20421 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20422 ) {
20423 // Include deleted hunks that are adjacent to the query range, because
20424 // otherwise they would be missed.
20425 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20426 if hunk.status().is_deleted() {
20427 intersects_range |= hunk.row_range.start == query_rows.end;
20428 intersects_range |= hunk.row_range.end == query_rows.start;
20429 }
20430 if intersects_range {
20431 if !processed_buffer_rows
20432 .entry(hunk.buffer_id)
20433 .or_default()
20434 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20435 {
20436 continue;
20437 }
20438 hunks.push(hunk);
20439 }
20440 }
20441 }
20442
20443 hunks
20444 }
20445
20446 fn display_diff_hunks_for_rows<'a>(
20447 &'a self,
20448 display_rows: Range<DisplayRow>,
20449 folded_buffers: &'a HashSet<BufferId>,
20450 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20451 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20452 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20453
20454 self.buffer_snapshot
20455 .diff_hunks_in_range(buffer_start..buffer_end)
20456 .filter_map(|hunk| {
20457 if folded_buffers.contains(&hunk.buffer_id) {
20458 return None;
20459 }
20460
20461 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20462 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20463
20464 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20465 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20466
20467 let display_hunk = if hunk_display_start.column() != 0 {
20468 DisplayDiffHunk::Folded {
20469 display_row: hunk_display_start.row(),
20470 }
20471 } else {
20472 let mut end_row = hunk_display_end.row();
20473 if hunk_display_end.column() > 0 {
20474 end_row.0 += 1;
20475 }
20476 let is_created_file = hunk.is_created_file();
20477 DisplayDiffHunk::Unfolded {
20478 status: hunk.status(),
20479 diff_base_byte_range: hunk.diff_base_byte_range,
20480 display_row_range: hunk_display_start.row()..end_row,
20481 multi_buffer_range: Anchor::range_in_buffer(
20482 hunk.excerpt_id,
20483 hunk.buffer_id,
20484 hunk.buffer_range,
20485 ),
20486 is_created_file,
20487 }
20488 };
20489
20490 Some(display_hunk)
20491 })
20492 }
20493
20494 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20495 self.display_snapshot.buffer_snapshot.language_at(position)
20496 }
20497
20498 pub fn is_focused(&self) -> bool {
20499 self.is_focused
20500 }
20501
20502 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20503 self.placeholder_text.as_ref()
20504 }
20505
20506 pub fn scroll_position(&self) -> gpui::Point<f32> {
20507 self.scroll_anchor.scroll_position(&self.display_snapshot)
20508 }
20509
20510 fn gutter_dimensions(
20511 &self,
20512 font_id: FontId,
20513 font_size: Pixels,
20514 max_line_number_width: Pixels,
20515 cx: &App,
20516 ) -> Option<GutterDimensions> {
20517 if !self.show_gutter {
20518 return None;
20519 }
20520
20521 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20522 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20523
20524 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20525 matches!(
20526 ProjectSettings::get_global(cx).git.git_gutter,
20527 Some(GitGutterSetting::TrackedFiles)
20528 )
20529 });
20530 let gutter_settings = EditorSettings::get_global(cx).gutter;
20531 let show_line_numbers = self
20532 .show_line_numbers
20533 .unwrap_or(gutter_settings.line_numbers);
20534 let line_gutter_width = if show_line_numbers {
20535 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20536 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20537 max_line_number_width.max(min_width_for_number_on_gutter)
20538 } else {
20539 0.0.into()
20540 };
20541
20542 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20543 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20544
20545 let git_blame_entries_width =
20546 self.git_blame_gutter_max_author_length
20547 .map(|max_author_length| {
20548 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20549 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20550
20551 /// The number of characters to dedicate to gaps and margins.
20552 const SPACING_WIDTH: usize = 4;
20553
20554 let max_char_count = max_author_length.min(renderer.max_author_length())
20555 + ::git::SHORT_SHA_LENGTH
20556 + MAX_RELATIVE_TIMESTAMP.len()
20557 + SPACING_WIDTH;
20558
20559 em_advance * max_char_count
20560 });
20561
20562 let is_singleton = self.buffer_snapshot.is_singleton();
20563
20564 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20565 left_padding += if !is_singleton {
20566 em_width * 4.0
20567 } else if show_runnables || show_breakpoints {
20568 em_width * 3.0
20569 } else if show_git_gutter && show_line_numbers {
20570 em_width * 2.0
20571 } else if show_git_gutter || show_line_numbers {
20572 em_width
20573 } else {
20574 px(0.)
20575 };
20576
20577 let shows_folds = is_singleton && gutter_settings.folds;
20578
20579 let right_padding = if shows_folds && show_line_numbers {
20580 em_width * 4.0
20581 } else if shows_folds || (!is_singleton && show_line_numbers) {
20582 em_width * 3.0
20583 } else if show_line_numbers {
20584 em_width
20585 } else {
20586 px(0.)
20587 };
20588
20589 Some(GutterDimensions {
20590 left_padding,
20591 right_padding,
20592 width: line_gutter_width + left_padding + right_padding,
20593 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20594 git_blame_entries_width,
20595 })
20596 }
20597
20598 pub fn render_crease_toggle(
20599 &self,
20600 buffer_row: MultiBufferRow,
20601 row_contains_cursor: bool,
20602 editor: Entity<Editor>,
20603 window: &mut Window,
20604 cx: &mut App,
20605 ) -> Option<AnyElement> {
20606 let folded = self.is_line_folded(buffer_row);
20607 let mut is_foldable = false;
20608
20609 if let Some(crease) = self
20610 .crease_snapshot
20611 .query_row(buffer_row, &self.buffer_snapshot)
20612 {
20613 is_foldable = true;
20614 match crease {
20615 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20616 if let Some(render_toggle) = render_toggle {
20617 let toggle_callback =
20618 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20619 if folded {
20620 editor.update(cx, |editor, cx| {
20621 editor.fold_at(buffer_row, window, cx)
20622 });
20623 } else {
20624 editor.update(cx, |editor, cx| {
20625 editor.unfold_at(buffer_row, window, cx)
20626 });
20627 }
20628 });
20629 return Some((render_toggle)(
20630 buffer_row,
20631 folded,
20632 toggle_callback,
20633 window,
20634 cx,
20635 ));
20636 }
20637 }
20638 }
20639 }
20640
20641 is_foldable |= self.starts_indent(buffer_row);
20642
20643 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20644 Some(
20645 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20646 .toggle_state(folded)
20647 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20648 if folded {
20649 this.unfold_at(buffer_row, window, cx);
20650 } else {
20651 this.fold_at(buffer_row, window, cx);
20652 }
20653 }))
20654 .into_any_element(),
20655 )
20656 } else {
20657 None
20658 }
20659 }
20660
20661 pub fn render_crease_trailer(
20662 &self,
20663 buffer_row: MultiBufferRow,
20664 window: &mut Window,
20665 cx: &mut App,
20666 ) -> Option<AnyElement> {
20667 let folded = self.is_line_folded(buffer_row);
20668 if let Crease::Inline { render_trailer, .. } = self
20669 .crease_snapshot
20670 .query_row(buffer_row, &self.buffer_snapshot)?
20671 {
20672 let render_trailer = render_trailer.as_ref()?;
20673 Some(render_trailer(buffer_row, folded, window, cx))
20674 } else {
20675 None
20676 }
20677 }
20678}
20679
20680impl Deref for EditorSnapshot {
20681 type Target = DisplaySnapshot;
20682
20683 fn deref(&self) -> &Self::Target {
20684 &self.display_snapshot
20685 }
20686}
20687
20688#[derive(Clone, Debug, PartialEq, Eq)]
20689pub enum EditorEvent {
20690 InputIgnored {
20691 text: Arc<str>,
20692 },
20693 InputHandled {
20694 utf16_range_to_replace: Option<Range<isize>>,
20695 text: Arc<str>,
20696 },
20697 ExcerptsAdded {
20698 buffer: Entity<Buffer>,
20699 predecessor: ExcerptId,
20700 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20701 },
20702 ExcerptsRemoved {
20703 ids: Vec<ExcerptId>,
20704 removed_buffer_ids: Vec<BufferId>,
20705 },
20706 BufferFoldToggled {
20707 ids: Vec<ExcerptId>,
20708 folded: bool,
20709 },
20710 ExcerptsEdited {
20711 ids: Vec<ExcerptId>,
20712 },
20713 ExcerptsExpanded {
20714 ids: Vec<ExcerptId>,
20715 },
20716 BufferEdited,
20717 Edited {
20718 transaction_id: clock::Lamport,
20719 },
20720 Reparsed(BufferId),
20721 Focused,
20722 FocusedIn,
20723 Blurred,
20724 DirtyChanged,
20725 Saved,
20726 TitleChanged,
20727 DiffBaseChanged,
20728 SelectionsChanged {
20729 local: bool,
20730 },
20731 ScrollPositionChanged {
20732 local: bool,
20733 autoscroll: bool,
20734 },
20735 Closed,
20736 TransactionUndone {
20737 transaction_id: clock::Lamport,
20738 },
20739 TransactionBegun {
20740 transaction_id: clock::Lamport,
20741 },
20742 Reloaded,
20743 CursorShapeChanged,
20744 PushedToNavHistory {
20745 anchor: Anchor,
20746 is_deactivate: bool,
20747 },
20748}
20749
20750impl EventEmitter<EditorEvent> for Editor {}
20751
20752impl Focusable for Editor {
20753 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20754 self.focus_handle.clone()
20755 }
20756}
20757
20758impl Render for Editor {
20759 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20760 let settings = ThemeSettings::get_global(cx);
20761
20762 let mut text_style = match self.mode {
20763 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20764 color: cx.theme().colors().editor_foreground,
20765 font_family: settings.ui_font.family.clone(),
20766 font_features: settings.ui_font.features.clone(),
20767 font_fallbacks: settings.ui_font.fallbacks.clone(),
20768 font_size: rems(0.875).into(),
20769 font_weight: settings.ui_font.weight,
20770 line_height: relative(settings.buffer_line_height.value()),
20771 ..Default::default()
20772 },
20773 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20774 color: cx.theme().colors().editor_foreground,
20775 font_family: settings.buffer_font.family.clone(),
20776 font_features: settings.buffer_font.features.clone(),
20777 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20778 font_size: settings.buffer_font_size(cx).into(),
20779 font_weight: settings.buffer_font.weight,
20780 line_height: relative(settings.buffer_line_height.value()),
20781 ..Default::default()
20782 },
20783 };
20784 if let Some(text_style_refinement) = &self.text_style_refinement {
20785 text_style.refine(text_style_refinement)
20786 }
20787
20788 let background = match self.mode {
20789 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20790 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20791 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20792 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20793 };
20794
20795 EditorElement::new(
20796 &cx.entity(),
20797 EditorStyle {
20798 background,
20799 local_player: cx.theme().players().local(),
20800 text: text_style,
20801 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20802 syntax: cx.theme().syntax().clone(),
20803 status: cx.theme().status().clone(),
20804 inlay_hints_style: make_inlay_hints_style(cx),
20805 inline_completion_styles: make_suggestion_styles(cx),
20806 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20807 show_underlines: !self.mode.is_minimap(),
20808 },
20809 )
20810 }
20811}
20812
20813impl EntityInputHandler for Editor {
20814 fn text_for_range(
20815 &mut self,
20816 range_utf16: Range<usize>,
20817 adjusted_range: &mut Option<Range<usize>>,
20818 _: &mut Window,
20819 cx: &mut Context<Self>,
20820 ) -> Option<String> {
20821 let snapshot = self.buffer.read(cx).read(cx);
20822 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20823 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20824 if (start.0..end.0) != range_utf16 {
20825 adjusted_range.replace(start.0..end.0);
20826 }
20827 Some(snapshot.text_for_range(start..end).collect())
20828 }
20829
20830 fn selected_text_range(
20831 &mut self,
20832 ignore_disabled_input: bool,
20833 _: &mut Window,
20834 cx: &mut Context<Self>,
20835 ) -> Option<UTF16Selection> {
20836 // Prevent the IME menu from appearing when holding down an alphabetic key
20837 // while input is disabled.
20838 if !ignore_disabled_input && !self.input_enabled {
20839 return None;
20840 }
20841
20842 let selection = self.selections.newest::<OffsetUtf16>(cx);
20843 let range = selection.range();
20844
20845 Some(UTF16Selection {
20846 range: range.start.0..range.end.0,
20847 reversed: selection.reversed,
20848 })
20849 }
20850
20851 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20852 let snapshot = self.buffer.read(cx).read(cx);
20853 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20854 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20855 }
20856
20857 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20858 self.clear_highlights::<InputComposition>(cx);
20859 self.ime_transaction.take();
20860 }
20861
20862 fn replace_text_in_range(
20863 &mut self,
20864 range_utf16: Option<Range<usize>>,
20865 text: &str,
20866 window: &mut Window,
20867 cx: &mut Context<Self>,
20868 ) {
20869 if !self.input_enabled {
20870 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20871 return;
20872 }
20873
20874 self.transact(window, cx, |this, window, cx| {
20875 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20876 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20877 Some(this.selection_replacement_ranges(range_utf16, cx))
20878 } else {
20879 this.marked_text_ranges(cx)
20880 };
20881
20882 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20883 let newest_selection_id = this.selections.newest_anchor().id;
20884 this.selections
20885 .all::<OffsetUtf16>(cx)
20886 .iter()
20887 .zip(ranges_to_replace.iter())
20888 .find_map(|(selection, range)| {
20889 if selection.id == newest_selection_id {
20890 Some(
20891 (range.start.0 as isize - selection.head().0 as isize)
20892 ..(range.end.0 as isize - selection.head().0 as isize),
20893 )
20894 } else {
20895 None
20896 }
20897 })
20898 });
20899
20900 cx.emit(EditorEvent::InputHandled {
20901 utf16_range_to_replace: range_to_replace,
20902 text: text.into(),
20903 });
20904
20905 if let Some(new_selected_ranges) = new_selected_ranges {
20906 this.change_selections(None, window, cx, |selections| {
20907 selections.select_ranges(new_selected_ranges)
20908 });
20909 this.backspace(&Default::default(), window, cx);
20910 }
20911
20912 this.handle_input(text, window, cx);
20913 });
20914
20915 if let Some(transaction) = self.ime_transaction {
20916 self.buffer.update(cx, |buffer, cx| {
20917 buffer.group_until_transaction(transaction, cx);
20918 });
20919 }
20920
20921 self.unmark_text(window, cx);
20922 }
20923
20924 fn replace_and_mark_text_in_range(
20925 &mut self,
20926 range_utf16: Option<Range<usize>>,
20927 text: &str,
20928 new_selected_range_utf16: Option<Range<usize>>,
20929 window: &mut Window,
20930 cx: &mut Context<Self>,
20931 ) {
20932 if !self.input_enabled {
20933 return;
20934 }
20935
20936 let transaction = self.transact(window, cx, |this, window, cx| {
20937 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20938 let snapshot = this.buffer.read(cx).read(cx);
20939 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20940 for marked_range in &mut marked_ranges {
20941 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20942 marked_range.start.0 += relative_range_utf16.start;
20943 marked_range.start =
20944 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20945 marked_range.end =
20946 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20947 }
20948 }
20949 Some(marked_ranges)
20950 } else if let Some(range_utf16) = range_utf16 {
20951 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20952 Some(this.selection_replacement_ranges(range_utf16, cx))
20953 } else {
20954 None
20955 };
20956
20957 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20958 let newest_selection_id = this.selections.newest_anchor().id;
20959 this.selections
20960 .all::<OffsetUtf16>(cx)
20961 .iter()
20962 .zip(ranges_to_replace.iter())
20963 .find_map(|(selection, range)| {
20964 if selection.id == newest_selection_id {
20965 Some(
20966 (range.start.0 as isize - selection.head().0 as isize)
20967 ..(range.end.0 as isize - selection.head().0 as isize),
20968 )
20969 } else {
20970 None
20971 }
20972 })
20973 });
20974
20975 cx.emit(EditorEvent::InputHandled {
20976 utf16_range_to_replace: range_to_replace,
20977 text: text.into(),
20978 });
20979
20980 if let Some(ranges) = ranges_to_replace {
20981 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20982 }
20983
20984 let marked_ranges = {
20985 let snapshot = this.buffer.read(cx).read(cx);
20986 this.selections
20987 .disjoint_anchors()
20988 .iter()
20989 .map(|selection| {
20990 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20991 })
20992 .collect::<Vec<_>>()
20993 };
20994
20995 if text.is_empty() {
20996 this.unmark_text(window, cx);
20997 } else {
20998 this.highlight_text::<InputComposition>(
20999 marked_ranges.clone(),
21000 HighlightStyle {
21001 underline: Some(UnderlineStyle {
21002 thickness: px(1.),
21003 color: None,
21004 wavy: false,
21005 }),
21006 ..Default::default()
21007 },
21008 cx,
21009 );
21010 }
21011
21012 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21013 let use_autoclose = this.use_autoclose;
21014 let use_auto_surround = this.use_auto_surround;
21015 this.set_use_autoclose(false);
21016 this.set_use_auto_surround(false);
21017 this.handle_input(text, window, cx);
21018 this.set_use_autoclose(use_autoclose);
21019 this.set_use_auto_surround(use_auto_surround);
21020
21021 if let Some(new_selected_range) = new_selected_range_utf16 {
21022 let snapshot = this.buffer.read(cx).read(cx);
21023 let new_selected_ranges = marked_ranges
21024 .into_iter()
21025 .map(|marked_range| {
21026 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21027 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21028 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21029 snapshot.clip_offset_utf16(new_start, Bias::Left)
21030 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21031 })
21032 .collect::<Vec<_>>();
21033
21034 drop(snapshot);
21035 this.change_selections(None, window, cx, |selections| {
21036 selections.select_ranges(new_selected_ranges)
21037 });
21038 }
21039 });
21040
21041 self.ime_transaction = self.ime_transaction.or(transaction);
21042 if let Some(transaction) = self.ime_transaction {
21043 self.buffer.update(cx, |buffer, cx| {
21044 buffer.group_until_transaction(transaction, cx);
21045 });
21046 }
21047
21048 if self.text_highlights::<InputComposition>(cx).is_none() {
21049 self.ime_transaction.take();
21050 }
21051 }
21052
21053 fn bounds_for_range(
21054 &mut self,
21055 range_utf16: Range<usize>,
21056 element_bounds: gpui::Bounds<Pixels>,
21057 window: &mut Window,
21058 cx: &mut Context<Self>,
21059 ) -> Option<gpui::Bounds<Pixels>> {
21060 let text_layout_details = self.text_layout_details(window);
21061 let gpui::Size {
21062 width: em_width,
21063 height: line_height,
21064 } = self.character_size(window);
21065
21066 let snapshot = self.snapshot(window, cx);
21067 let scroll_position = snapshot.scroll_position();
21068 let scroll_left = scroll_position.x * em_width;
21069
21070 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21071 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21072 + self.gutter_dimensions.width
21073 + self.gutter_dimensions.margin;
21074 let y = line_height * (start.row().as_f32() - scroll_position.y);
21075
21076 Some(Bounds {
21077 origin: element_bounds.origin + point(x, y),
21078 size: size(em_width, line_height),
21079 })
21080 }
21081
21082 fn character_index_for_point(
21083 &mut self,
21084 point: gpui::Point<Pixels>,
21085 _window: &mut Window,
21086 _cx: &mut Context<Self>,
21087 ) -> Option<usize> {
21088 let position_map = self.last_position_map.as_ref()?;
21089 if !position_map.text_hitbox.contains(&point) {
21090 return None;
21091 }
21092 let display_point = position_map.point_for_position(point).previous_valid;
21093 let anchor = position_map
21094 .snapshot
21095 .display_point_to_anchor(display_point, Bias::Left);
21096 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21097 Some(utf16_offset.0)
21098 }
21099}
21100
21101trait SelectionExt {
21102 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21103 fn spanned_rows(
21104 &self,
21105 include_end_if_at_line_start: bool,
21106 map: &DisplaySnapshot,
21107 ) -> Range<MultiBufferRow>;
21108}
21109
21110impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21111 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21112 let start = self
21113 .start
21114 .to_point(&map.buffer_snapshot)
21115 .to_display_point(map);
21116 let end = self
21117 .end
21118 .to_point(&map.buffer_snapshot)
21119 .to_display_point(map);
21120 if self.reversed {
21121 end..start
21122 } else {
21123 start..end
21124 }
21125 }
21126
21127 fn spanned_rows(
21128 &self,
21129 include_end_if_at_line_start: bool,
21130 map: &DisplaySnapshot,
21131 ) -> Range<MultiBufferRow> {
21132 let start = self.start.to_point(&map.buffer_snapshot);
21133 let mut end = self.end.to_point(&map.buffer_snapshot);
21134 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21135 end.row -= 1;
21136 }
21137
21138 let buffer_start = map.prev_line_boundary(start).0;
21139 let buffer_end = map.next_line_boundary(end).0;
21140 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21141 }
21142}
21143
21144impl<T: InvalidationRegion> InvalidationStack<T> {
21145 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21146 where
21147 S: Clone + ToOffset,
21148 {
21149 while let Some(region) = self.last() {
21150 let all_selections_inside_invalidation_ranges =
21151 if selections.len() == region.ranges().len() {
21152 selections
21153 .iter()
21154 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21155 .all(|(selection, invalidation_range)| {
21156 let head = selection.head().to_offset(buffer);
21157 invalidation_range.start <= head && invalidation_range.end >= head
21158 })
21159 } else {
21160 false
21161 };
21162
21163 if all_selections_inside_invalidation_ranges {
21164 break;
21165 } else {
21166 self.pop();
21167 }
21168 }
21169 }
21170}
21171
21172impl<T> Default for InvalidationStack<T> {
21173 fn default() -> Self {
21174 Self(Default::default())
21175 }
21176}
21177
21178impl<T> Deref for InvalidationStack<T> {
21179 type Target = Vec<T>;
21180
21181 fn deref(&self) -> &Self::Target {
21182 &self.0
21183 }
21184}
21185
21186impl<T> DerefMut for InvalidationStack<T> {
21187 fn deref_mut(&mut self) -> &mut Self::Target {
21188 &mut self.0
21189 }
21190}
21191
21192impl InvalidationRegion for SnippetState {
21193 fn ranges(&self) -> &[Range<Anchor>] {
21194 &self.ranges[self.active_index]
21195 }
21196}
21197
21198fn inline_completion_edit_text(
21199 current_snapshot: &BufferSnapshot,
21200 edits: &[(Range<Anchor>, String)],
21201 edit_preview: &EditPreview,
21202 include_deletions: bool,
21203 cx: &App,
21204) -> HighlightedText {
21205 let edits = edits
21206 .iter()
21207 .map(|(anchor, text)| {
21208 (
21209 anchor.start.text_anchor..anchor.end.text_anchor,
21210 text.clone(),
21211 )
21212 })
21213 .collect::<Vec<_>>();
21214
21215 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21216}
21217
21218pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21219 match severity {
21220 lsp::DiagnosticSeverity::ERROR => colors.error,
21221 lsp::DiagnosticSeverity::WARNING => colors.warning,
21222 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21223 lsp::DiagnosticSeverity::HINT => colors.info,
21224 _ => colors.ignored,
21225 }
21226}
21227
21228pub fn styled_runs_for_code_label<'a>(
21229 label: &'a CodeLabel,
21230 syntax_theme: &'a theme::SyntaxTheme,
21231) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21232 let fade_out = HighlightStyle {
21233 fade_out: Some(0.35),
21234 ..Default::default()
21235 };
21236
21237 let mut prev_end = label.filter_range.end;
21238 label
21239 .runs
21240 .iter()
21241 .enumerate()
21242 .flat_map(move |(ix, (range, highlight_id))| {
21243 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21244 style
21245 } else {
21246 return Default::default();
21247 };
21248 let mut muted_style = style;
21249 muted_style.highlight(fade_out);
21250
21251 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21252 if range.start >= label.filter_range.end {
21253 if range.start > prev_end {
21254 runs.push((prev_end..range.start, fade_out));
21255 }
21256 runs.push((range.clone(), muted_style));
21257 } else if range.end <= label.filter_range.end {
21258 runs.push((range.clone(), style));
21259 } else {
21260 runs.push((range.start..label.filter_range.end, style));
21261 runs.push((label.filter_range.end..range.end, muted_style));
21262 }
21263 prev_end = cmp::max(prev_end, range.end);
21264
21265 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21266 runs.push((prev_end..label.text.len(), fade_out));
21267 }
21268
21269 runs
21270 })
21271}
21272
21273pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21274 let mut prev_index = 0;
21275 let mut prev_codepoint: Option<char> = None;
21276 text.char_indices()
21277 .chain([(text.len(), '\0')])
21278 .filter_map(move |(index, codepoint)| {
21279 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21280 let is_boundary = index == text.len()
21281 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21282 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21283 if is_boundary {
21284 let chunk = &text[prev_index..index];
21285 prev_index = index;
21286 Some(chunk)
21287 } else {
21288 None
21289 }
21290 })
21291}
21292
21293pub trait RangeToAnchorExt: Sized {
21294 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21295
21296 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21297 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21298 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21299 }
21300}
21301
21302impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21303 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21304 let start_offset = self.start.to_offset(snapshot);
21305 let end_offset = self.end.to_offset(snapshot);
21306 if start_offset == end_offset {
21307 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21308 } else {
21309 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21310 }
21311 }
21312}
21313
21314pub trait RowExt {
21315 fn as_f32(&self) -> f32;
21316
21317 fn next_row(&self) -> Self;
21318
21319 fn previous_row(&self) -> Self;
21320
21321 fn minus(&self, other: Self) -> u32;
21322}
21323
21324impl RowExt for DisplayRow {
21325 fn as_f32(&self) -> f32 {
21326 self.0 as f32
21327 }
21328
21329 fn next_row(&self) -> Self {
21330 Self(self.0 + 1)
21331 }
21332
21333 fn previous_row(&self) -> Self {
21334 Self(self.0.saturating_sub(1))
21335 }
21336
21337 fn minus(&self, other: Self) -> u32 {
21338 self.0 - other.0
21339 }
21340}
21341
21342impl RowExt for MultiBufferRow {
21343 fn as_f32(&self) -> f32 {
21344 self.0 as f32
21345 }
21346
21347 fn next_row(&self) -> Self {
21348 Self(self.0 + 1)
21349 }
21350
21351 fn previous_row(&self) -> Self {
21352 Self(self.0.saturating_sub(1))
21353 }
21354
21355 fn minus(&self, other: Self) -> u32 {
21356 self.0 - other.0
21357 }
21358}
21359
21360trait RowRangeExt {
21361 type Row;
21362
21363 fn len(&self) -> usize;
21364
21365 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21366}
21367
21368impl RowRangeExt for Range<MultiBufferRow> {
21369 type Row = MultiBufferRow;
21370
21371 fn len(&self) -> usize {
21372 (self.end.0 - self.start.0) as usize
21373 }
21374
21375 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21376 (self.start.0..self.end.0).map(MultiBufferRow)
21377 }
21378}
21379
21380impl RowRangeExt for Range<DisplayRow> {
21381 type Row = DisplayRow;
21382
21383 fn len(&self) -> usize {
21384 (self.end.0 - self.start.0) as usize
21385 }
21386
21387 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21388 (self.start.0..self.end.0).map(DisplayRow)
21389 }
21390}
21391
21392/// If select range has more than one line, we
21393/// just point the cursor to range.start.
21394fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21395 if range.start.row == range.end.row {
21396 range
21397 } else {
21398 range.start..range.start
21399 }
21400}
21401pub struct KillRing(ClipboardItem);
21402impl Global for KillRing {}
21403
21404const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21405
21406enum BreakpointPromptEditAction {
21407 Log,
21408 Condition,
21409 HitCondition,
21410}
21411
21412struct BreakpointPromptEditor {
21413 pub(crate) prompt: Entity<Editor>,
21414 editor: WeakEntity<Editor>,
21415 breakpoint_anchor: Anchor,
21416 breakpoint: Breakpoint,
21417 edit_action: BreakpointPromptEditAction,
21418 block_ids: HashSet<CustomBlockId>,
21419 editor_margins: Arc<Mutex<EditorMargins>>,
21420 _subscriptions: Vec<Subscription>,
21421}
21422
21423impl BreakpointPromptEditor {
21424 const MAX_LINES: u8 = 4;
21425
21426 fn new(
21427 editor: WeakEntity<Editor>,
21428 breakpoint_anchor: Anchor,
21429 breakpoint: Breakpoint,
21430 edit_action: BreakpointPromptEditAction,
21431 window: &mut Window,
21432 cx: &mut Context<Self>,
21433 ) -> Self {
21434 let base_text = match edit_action {
21435 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21436 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21437 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21438 }
21439 .map(|msg| msg.to_string())
21440 .unwrap_or_default();
21441
21442 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21443 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21444
21445 let prompt = cx.new(|cx| {
21446 let mut prompt = Editor::new(
21447 EditorMode::AutoHeight {
21448 max_lines: Self::MAX_LINES as usize,
21449 },
21450 buffer,
21451 None,
21452 window,
21453 cx,
21454 );
21455 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21456 prompt.set_show_cursor_when_unfocused(false, cx);
21457 prompt.set_placeholder_text(
21458 match edit_action {
21459 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21460 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21461 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21462 },
21463 cx,
21464 );
21465
21466 prompt
21467 });
21468
21469 Self {
21470 prompt,
21471 editor,
21472 breakpoint_anchor,
21473 breakpoint,
21474 edit_action,
21475 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21476 block_ids: Default::default(),
21477 _subscriptions: vec![],
21478 }
21479 }
21480
21481 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21482 self.block_ids.extend(block_ids)
21483 }
21484
21485 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21486 if let Some(editor) = self.editor.upgrade() {
21487 let message = self
21488 .prompt
21489 .read(cx)
21490 .buffer
21491 .read(cx)
21492 .as_singleton()
21493 .expect("A multi buffer in breakpoint prompt isn't possible")
21494 .read(cx)
21495 .as_rope()
21496 .to_string();
21497
21498 editor.update(cx, |editor, cx| {
21499 editor.edit_breakpoint_at_anchor(
21500 self.breakpoint_anchor,
21501 self.breakpoint.clone(),
21502 match self.edit_action {
21503 BreakpointPromptEditAction::Log => {
21504 BreakpointEditAction::EditLogMessage(message.into())
21505 }
21506 BreakpointPromptEditAction::Condition => {
21507 BreakpointEditAction::EditCondition(message.into())
21508 }
21509 BreakpointPromptEditAction::HitCondition => {
21510 BreakpointEditAction::EditHitCondition(message.into())
21511 }
21512 },
21513 cx,
21514 );
21515
21516 editor.remove_blocks(self.block_ids.clone(), None, cx);
21517 cx.focus_self(window);
21518 });
21519 }
21520 }
21521
21522 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21523 self.editor
21524 .update(cx, |editor, cx| {
21525 editor.remove_blocks(self.block_ids.clone(), None, cx);
21526 window.focus(&editor.focus_handle);
21527 })
21528 .log_err();
21529 }
21530
21531 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21532 let settings = ThemeSettings::get_global(cx);
21533 let text_style = TextStyle {
21534 color: if self.prompt.read(cx).read_only(cx) {
21535 cx.theme().colors().text_disabled
21536 } else {
21537 cx.theme().colors().text
21538 },
21539 font_family: settings.buffer_font.family.clone(),
21540 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21541 font_size: settings.buffer_font_size(cx).into(),
21542 font_weight: settings.buffer_font.weight,
21543 line_height: relative(settings.buffer_line_height.value()),
21544 ..Default::default()
21545 };
21546 EditorElement::new(
21547 &self.prompt,
21548 EditorStyle {
21549 background: cx.theme().colors().editor_background,
21550 local_player: cx.theme().players().local(),
21551 text: text_style,
21552 ..Default::default()
21553 },
21554 )
21555 }
21556}
21557
21558impl Render for BreakpointPromptEditor {
21559 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21560 let editor_margins = *self.editor_margins.lock();
21561 let gutter_dimensions = editor_margins.gutter;
21562 h_flex()
21563 .key_context("Editor")
21564 .bg(cx.theme().colors().editor_background)
21565 .border_y_1()
21566 .border_color(cx.theme().status().info_border)
21567 .size_full()
21568 .py(window.line_height() / 2.5)
21569 .on_action(cx.listener(Self::confirm))
21570 .on_action(cx.listener(Self::cancel))
21571 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21572 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21573 }
21574}
21575
21576impl Focusable for BreakpointPromptEditor {
21577 fn focus_handle(&self, cx: &App) -> FocusHandle {
21578 self.prompt.focus_handle(cx)
21579 }
21580}
21581
21582fn all_edits_insertions_or_deletions(
21583 edits: &Vec<(Range<Anchor>, String)>,
21584 snapshot: &MultiBufferSnapshot,
21585) -> bool {
21586 let mut all_insertions = true;
21587 let mut all_deletions = true;
21588
21589 for (range, new_text) in edits.iter() {
21590 let range_is_empty = range.to_offset(&snapshot).is_empty();
21591 let text_is_empty = new_text.is_empty();
21592
21593 if range_is_empty != text_is_empty {
21594 if range_is_empty {
21595 all_deletions = false;
21596 } else {
21597 all_insertions = false;
21598 }
21599 } else {
21600 return false;
21601 }
21602
21603 if !all_insertions && !all_deletions {
21604 return false;
21605 }
21606 }
21607 all_insertions || all_deletions
21608}
21609
21610struct MissingEditPredictionKeybindingTooltip;
21611
21612impl Render for MissingEditPredictionKeybindingTooltip {
21613 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21614 ui::tooltip_container(window, cx, |container, _, cx| {
21615 container
21616 .flex_shrink_0()
21617 .max_w_80()
21618 .min_h(rems_from_px(124.))
21619 .justify_between()
21620 .child(
21621 v_flex()
21622 .flex_1()
21623 .text_ui_sm(cx)
21624 .child(Label::new("Conflict with Accept Keybinding"))
21625 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21626 )
21627 .child(
21628 h_flex()
21629 .pb_1()
21630 .gap_1()
21631 .items_end()
21632 .w_full()
21633 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21634 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21635 }))
21636 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21637 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21638 })),
21639 )
21640 })
21641 }
21642}
21643
21644#[derive(Debug, Clone, Copy, PartialEq)]
21645pub struct LineHighlight {
21646 pub background: Background,
21647 pub border: Option<gpui::Hsla>,
21648 pub include_gutter: bool,
21649 pub type_id: Option<TypeId>,
21650}
21651
21652fn render_diff_hunk_controls(
21653 row: u32,
21654 status: &DiffHunkStatus,
21655 hunk_range: Range<Anchor>,
21656 is_created_file: bool,
21657 line_height: Pixels,
21658 editor: &Entity<Editor>,
21659 _window: &mut Window,
21660 cx: &mut App,
21661) -> AnyElement {
21662 h_flex()
21663 .h(line_height)
21664 .mr_1()
21665 .gap_1()
21666 .px_0p5()
21667 .pb_1()
21668 .border_x_1()
21669 .border_b_1()
21670 .border_color(cx.theme().colors().border_variant)
21671 .rounded_b_lg()
21672 .bg(cx.theme().colors().editor_background)
21673 .gap_1()
21674 .occlude()
21675 .shadow_md()
21676 .child(if status.has_secondary_hunk() {
21677 Button::new(("stage", row as u64), "Stage")
21678 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21679 .tooltip({
21680 let focus_handle = editor.focus_handle(cx);
21681 move |window, cx| {
21682 Tooltip::for_action_in(
21683 "Stage Hunk",
21684 &::git::ToggleStaged,
21685 &focus_handle,
21686 window,
21687 cx,
21688 )
21689 }
21690 })
21691 .on_click({
21692 let editor = editor.clone();
21693 move |_event, _window, cx| {
21694 editor.update(cx, |editor, cx| {
21695 editor.stage_or_unstage_diff_hunks(
21696 true,
21697 vec![hunk_range.start..hunk_range.start],
21698 cx,
21699 );
21700 });
21701 }
21702 })
21703 } else {
21704 Button::new(("unstage", row as u64), "Unstage")
21705 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21706 .tooltip({
21707 let focus_handle = editor.focus_handle(cx);
21708 move |window, cx| {
21709 Tooltip::for_action_in(
21710 "Unstage Hunk",
21711 &::git::ToggleStaged,
21712 &focus_handle,
21713 window,
21714 cx,
21715 )
21716 }
21717 })
21718 .on_click({
21719 let editor = editor.clone();
21720 move |_event, _window, cx| {
21721 editor.update(cx, |editor, cx| {
21722 editor.stage_or_unstage_diff_hunks(
21723 false,
21724 vec![hunk_range.start..hunk_range.start],
21725 cx,
21726 );
21727 });
21728 }
21729 })
21730 })
21731 .child(
21732 Button::new(("restore", row as u64), "Restore")
21733 .tooltip({
21734 let focus_handle = editor.focus_handle(cx);
21735 move |window, cx| {
21736 Tooltip::for_action_in(
21737 "Restore Hunk",
21738 &::git::Restore,
21739 &focus_handle,
21740 window,
21741 cx,
21742 )
21743 }
21744 })
21745 .on_click({
21746 let editor = editor.clone();
21747 move |_event, window, cx| {
21748 editor.update(cx, |editor, cx| {
21749 let snapshot = editor.snapshot(window, cx);
21750 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21751 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21752 });
21753 }
21754 })
21755 .disabled(is_created_file),
21756 )
21757 .when(
21758 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21759 |el| {
21760 el.child(
21761 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21762 .shape(IconButtonShape::Square)
21763 .icon_size(IconSize::Small)
21764 // .disabled(!has_multiple_hunks)
21765 .tooltip({
21766 let focus_handle = editor.focus_handle(cx);
21767 move |window, cx| {
21768 Tooltip::for_action_in(
21769 "Next Hunk",
21770 &GoToHunk,
21771 &focus_handle,
21772 window,
21773 cx,
21774 )
21775 }
21776 })
21777 .on_click({
21778 let editor = editor.clone();
21779 move |_event, window, cx| {
21780 editor.update(cx, |editor, cx| {
21781 let snapshot = editor.snapshot(window, cx);
21782 let position =
21783 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21784 editor.go_to_hunk_before_or_after_position(
21785 &snapshot,
21786 position,
21787 Direction::Next,
21788 window,
21789 cx,
21790 );
21791 editor.expand_selected_diff_hunks(cx);
21792 });
21793 }
21794 }),
21795 )
21796 .child(
21797 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21798 .shape(IconButtonShape::Square)
21799 .icon_size(IconSize::Small)
21800 // .disabled(!has_multiple_hunks)
21801 .tooltip({
21802 let focus_handle = editor.focus_handle(cx);
21803 move |window, cx| {
21804 Tooltip::for_action_in(
21805 "Previous Hunk",
21806 &GoToPreviousHunk,
21807 &focus_handle,
21808 window,
21809 cx,
21810 )
21811 }
21812 })
21813 .on_click({
21814 let editor = editor.clone();
21815 move |_event, window, cx| {
21816 editor.update(cx, |editor, cx| {
21817 let snapshot = editor.snapshot(window, cx);
21818 let point =
21819 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21820 editor.go_to_hunk_before_or_after_position(
21821 &snapshot,
21822 point,
21823 Direction::Prev,
21824 window,
21825 cx,
21826 );
21827 editor.expand_selected_diff_hunks(cx);
21828 });
21829 }
21830 }),
21831 )
21832 },
21833 )
21834 .into_any_element()
21835}