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
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 *editor.context_menu.borrow_mut() =
5516 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5517 buffer,
5518 actions: CodeActionContents::new(
5519 resolved_tasks,
5520 code_actions,
5521 debug_scenarios,
5522 task_context.unwrap_or_default(),
5523 ),
5524 selected_item: Default::default(),
5525 scroll_handle: UniformListScrollHandle::default(),
5526 deployed_from_indicator,
5527 }));
5528 if spawn_straight_away {
5529 if let Some(task) = editor.confirm_code_action(
5530 &ConfirmCodeAction { item_ix: Some(0) },
5531 window,
5532 cx,
5533 ) {
5534 cx.notify();
5535 return task;
5536 }
5537 }
5538 cx.notify();
5539 Task::ready(Ok(()))
5540 }) {
5541 task.await
5542 } else {
5543 Ok(())
5544 }
5545 }))
5546 } else {
5547 Some(Task::ready(Ok(())))
5548 }
5549 })?;
5550 if let Some(task) = spawned_test_task {
5551 task.await?;
5552 }
5553
5554 Ok::<_, anyhow::Error>(())
5555 })
5556 .detach_and_log_err(cx);
5557 }
5558
5559 pub fn confirm_code_action(
5560 &mut self,
5561 action: &ConfirmCodeAction,
5562 window: &mut Window,
5563 cx: &mut Context<Self>,
5564 ) -> Option<Task<Result<()>>> {
5565 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5566
5567 let actions_menu =
5568 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5569 menu
5570 } else {
5571 return None;
5572 };
5573
5574 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5575 let action = actions_menu.actions.get(action_ix)?;
5576 let title = action.label();
5577 let buffer = actions_menu.buffer;
5578 let workspace = self.workspace()?;
5579
5580 match action {
5581 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5582 workspace.update(cx, |workspace, cx| {
5583 workspace.schedule_resolved_task(
5584 task_source_kind,
5585 resolved_task,
5586 false,
5587 window,
5588 cx,
5589 );
5590
5591 Some(Task::ready(Ok(())))
5592 })
5593 }
5594 CodeActionsItem::CodeAction {
5595 excerpt_id,
5596 action,
5597 provider,
5598 } => {
5599 let apply_code_action =
5600 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5601 let workspace = workspace.downgrade();
5602 Some(cx.spawn_in(window, async move |editor, cx| {
5603 let project_transaction = apply_code_action.await?;
5604 Self::open_project_transaction(
5605 &editor,
5606 workspace,
5607 project_transaction,
5608 title,
5609 cx,
5610 )
5611 .await
5612 }))
5613 }
5614 CodeActionsItem::DebugScenario(scenario) => {
5615 let context = actions_menu.actions.context.clone();
5616
5617 workspace.update(cx, |workspace, cx| {
5618 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5619 });
5620 Some(Task::ready(Ok(())))
5621 }
5622 }
5623 }
5624
5625 pub async fn open_project_transaction(
5626 this: &WeakEntity<Editor>,
5627 workspace: WeakEntity<Workspace>,
5628 transaction: ProjectTransaction,
5629 title: String,
5630 cx: &mut AsyncWindowContext,
5631 ) -> Result<()> {
5632 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5633 cx.update(|_, cx| {
5634 entries.sort_unstable_by_key(|(buffer, _)| {
5635 buffer.read(cx).file().map(|f| f.path().clone())
5636 });
5637 })?;
5638
5639 // If the project transaction's edits are all contained within this editor, then
5640 // avoid opening a new editor to display them.
5641
5642 if let Some((buffer, transaction)) = entries.first() {
5643 if entries.len() == 1 {
5644 let excerpt = this.update(cx, |editor, cx| {
5645 editor
5646 .buffer()
5647 .read(cx)
5648 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5649 })?;
5650 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5651 if excerpted_buffer == *buffer {
5652 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5653 let excerpt_range = excerpt_range.to_offset(buffer);
5654 buffer
5655 .edited_ranges_for_transaction::<usize>(transaction)
5656 .all(|range| {
5657 excerpt_range.start <= range.start
5658 && excerpt_range.end >= range.end
5659 })
5660 })?;
5661
5662 if all_edits_within_excerpt {
5663 return Ok(());
5664 }
5665 }
5666 }
5667 }
5668 } else {
5669 return Ok(());
5670 }
5671
5672 let mut ranges_to_highlight = Vec::new();
5673 let excerpt_buffer = cx.new(|cx| {
5674 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5675 for (buffer_handle, transaction) in &entries {
5676 let edited_ranges = buffer_handle
5677 .read(cx)
5678 .edited_ranges_for_transaction::<Point>(transaction)
5679 .collect::<Vec<_>>();
5680 let (ranges, _) = multibuffer.set_excerpts_for_path(
5681 PathKey::for_buffer(buffer_handle, cx),
5682 buffer_handle.clone(),
5683 edited_ranges,
5684 DEFAULT_MULTIBUFFER_CONTEXT,
5685 cx,
5686 );
5687
5688 ranges_to_highlight.extend(ranges);
5689 }
5690 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5691 multibuffer
5692 })?;
5693
5694 workspace.update_in(cx, |workspace, window, cx| {
5695 let project = workspace.project().clone();
5696 let editor =
5697 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5698 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5699 editor.update(cx, |editor, cx| {
5700 editor.highlight_background::<Self>(
5701 &ranges_to_highlight,
5702 |theme| theme.editor_highlighted_line_background,
5703 cx,
5704 );
5705 });
5706 })?;
5707
5708 Ok(())
5709 }
5710
5711 pub fn clear_code_action_providers(&mut self) {
5712 self.code_action_providers.clear();
5713 self.available_code_actions.take();
5714 }
5715
5716 pub fn add_code_action_provider(
5717 &mut self,
5718 provider: Rc<dyn CodeActionProvider>,
5719 window: &mut Window,
5720 cx: &mut Context<Self>,
5721 ) {
5722 if self
5723 .code_action_providers
5724 .iter()
5725 .any(|existing_provider| existing_provider.id() == provider.id())
5726 {
5727 return;
5728 }
5729
5730 self.code_action_providers.push(provider);
5731 self.refresh_code_actions(window, cx);
5732 }
5733
5734 pub fn remove_code_action_provider(
5735 &mut self,
5736 id: Arc<str>,
5737 window: &mut Window,
5738 cx: &mut Context<Self>,
5739 ) {
5740 self.code_action_providers
5741 .retain(|provider| provider.id() != id);
5742 self.refresh_code_actions(window, cx);
5743 }
5744
5745 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5746 let newest_selection = self.selections.newest_anchor().clone();
5747 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5748 let buffer = self.buffer.read(cx);
5749 if newest_selection.head().diff_base_anchor.is_some() {
5750 return None;
5751 }
5752 let (start_buffer, start) =
5753 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5754 let (end_buffer, end) =
5755 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5756 if start_buffer != end_buffer {
5757 return None;
5758 }
5759
5760 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5761 cx.background_executor()
5762 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5763 .await;
5764
5765 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5766 let providers = this.code_action_providers.clone();
5767 let tasks = this
5768 .code_action_providers
5769 .iter()
5770 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5771 .collect::<Vec<_>>();
5772 (providers, tasks)
5773 })?;
5774
5775 let mut actions = Vec::new();
5776 for (provider, provider_actions) in
5777 providers.into_iter().zip(future::join_all(tasks).await)
5778 {
5779 if let Some(provider_actions) = provider_actions.log_err() {
5780 actions.extend(provider_actions.into_iter().map(|action| {
5781 AvailableCodeAction {
5782 excerpt_id: newest_selection.start.excerpt_id,
5783 action,
5784 provider: provider.clone(),
5785 }
5786 }));
5787 }
5788 }
5789
5790 this.update(cx, |this, cx| {
5791 this.available_code_actions = if actions.is_empty() {
5792 None
5793 } else {
5794 Some((
5795 Location {
5796 buffer: start_buffer,
5797 range: start..end,
5798 },
5799 actions.into(),
5800 ))
5801 };
5802 cx.notify();
5803 })
5804 }));
5805 None
5806 }
5807
5808 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5809 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5810 self.show_git_blame_inline = false;
5811
5812 self.show_git_blame_inline_delay_task =
5813 Some(cx.spawn_in(window, async move |this, cx| {
5814 cx.background_executor().timer(delay).await;
5815
5816 this.update(cx, |this, cx| {
5817 this.show_git_blame_inline = true;
5818 cx.notify();
5819 })
5820 .log_err();
5821 }));
5822 }
5823 }
5824
5825 fn show_blame_popover(
5826 &mut self,
5827 blame_entry: &BlameEntry,
5828 position: gpui::Point<Pixels>,
5829 cx: &mut Context<Self>,
5830 ) {
5831 if let Some(state) = &mut self.inline_blame_popover {
5832 state.hide_task.take();
5833 cx.notify();
5834 } else {
5835 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5836 let show_task = cx.spawn(async move |editor, cx| {
5837 cx.background_executor()
5838 .timer(std::time::Duration::from_millis(delay))
5839 .await;
5840 editor
5841 .update(cx, |editor, cx| {
5842 if let Some(state) = &mut editor.inline_blame_popover {
5843 state.show_task = None;
5844 cx.notify();
5845 }
5846 })
5847 .ok();
5848 });
5849 let Some(blame) = self.blame.as_ref() else {
5850 return;
5851 };
5852 let blame = blame.read(cx);
5853 let details = blame.details_for_entry(&blame_entry);
5854 let markdown = cx.new(|cx| {
5855 Markdown::new(
5856 details
5857 .as_ref()
5858 .map(|message| message.message.clone())
5859 .unwrap_or_default(),
5860 None,
5861 None,
5862 cx,
5863 )
5864 });
5865 self.inline_blame_popover = Some(InlineBlamePopover {
5866 position,
5867 show_task: Some(show_task),
5868 hide_task: None,
5869 popover_bounds: None,
5870 popover_state: InlineBlamePopoverState {
5871 scroll_handle: ScrollHandle::new(),
5872 commit_message: details,
5873 markdown,
5874 },
5875 });
5876 }
5877 }
5878
5879 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5880 if let Some(state) = &mut self.inline_blame_popover {
5881 if state.show_task.is_some() {
5882 self.inline_blame_popover.take();
5883 cx.notify();
5884 } else {
5885 let hide_task = cx.spawn(async move |editor, cx| {
5886 cx.background_executor()
5887 .timer(std::time::Duration::from_millis(100))
5888 .await;
5889 editor
5890 .update(cx, |editor, cx| {
5891 editor.inline_blame_popover.take();
5892 cx.notify();
5893 })
5894 .ok();
5895 });
5896 state.hide_task = Some(hide_task);
5897 }
5898 }
5899 }
5900
5901 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5902 if self.pending_rename.is_some() {
5903 return None;
5904 }
5905
5906 let provider = self.semantics_provider.clone()?;
5907 let buffer = self.buffer.read(cx);
5908 let newest_selection = self.selections.newest_anchor().clone();
5909 let cursor_position = newest_selection.head();
5910 let (cursor_buffer, cursor_buffer_position) =
5911 buffer.text_anchor_for_position(cursor_position, cx)?;
5912 let (tail_buffer, tail_buffer_position) =
5913 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5914 if cursor_buffer != tail_buffer {
5915 return None;
5916 }
5917
5918 let snapshot = cursor_buffer.read(cx).snapshot();
5919 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5920 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5921 if start_word_range != end_word_range {
5922 self.document_highlights_task.take();
5923 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5924 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5925 return None;
5926 }
5927
5928 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5929 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5930 cx.background_executor()
5931 .timer(Duration::from_millis(debounce))
5932 .await;
5933
5934 let highlights = if let Some(highlights) = cx
5935 .update(|cx| {
5936 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5937 })
5938 .ok()
5939 .flatten()
5940 {
5941 highlights.await.log_err()
5942 } else {
5943 None
5944 };
5945
5946 if let Some(highlights) = highlights {
5947 this.update(cx, |this, cx| {
5948 if this.pending_rename.is_some() {
5949 return;
5950 }
5951
5952 let buffer_id = cursor_position.buffer_id;
5953 let buffer = this.buffer.read(cx);
5954 if !buffer
5955 .text_anchor_for_position(cursor_position, cx)
5956 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5957 {
5958 return;
5959 }
5960
5961 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5962 let mut write_ranges = Vec::new();
5963 let mut read_ranges = Vec::new();
5964 for highlight in highlights {
5965 for (excerpt_id, excerpt_range) in
5966 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5967 {
5968 let start = highlight
5969 .range
5970 .start
5971 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5972 let end = highlight
5973 .range
5974 .end
5975 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5976 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5977 continue;
5978 }
5979
5980 let range = Anchor {
5981 buffer_id,
5982 excerpt_id,
5983 text_anchor: start,
5984 diff_base_anchor: None,
5985 }..Anchor {
5986 buffer_id,
5987 excerpt_id,
5988 text_anchor: end,
5989 diff_base_anchor: None,
5990 };
5991 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5992 write_ranges.push(range);
5993 } else {
5994 read_ranges.push(range);
5995 }
5996 }
5997 }
5998
5999 this.highlight_background::<DocumentHighlightRead>(
6000 &read_ranges,
6001 |theme| theme.editor_document_highlight_read_background,
6002 cx,
6003 );
6004 this.highlight_background::<DocumentHighlightWrite>(
6005 &write_ranges,
6006 |theme| theme.editor_document_highlight_write_background,
6007 cx,
6008 );
6009 cx.notify();
6010 })
6011 .log_err();
6012 }
6013 }));
6014 None
6015 }
6016
6017 fn prepare_highlight_query_from_selection(
6018 &mut self,
6019 cx: &mut Context<Editor>,
6020 ) -> Option<(String, Range<Anchor>)> {
6021 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6022 return None;
6023 }
6024 if !EditorSettings::get_global(cx).selection_highlight {
6025 return None;
6026 }
6027 if self.selections.count() != 1 || self.selections.line_mode {
6028 return None;
6029 }
6030 let selection = self.selections.newest::<Point>(cx);
6031 if selection.is_empty() || selection.start.row != selection.end.row {
6032 return None;
6033 }
6034 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6035 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6036 let query = multi_buffer_snapshot
6037 .text_for_range(selection_anchor_range.clone())
6038 .collect::<String>();
6039 if query.trim().is_empty() {
6040 return None;
6041 }
6042 Some((query, selection_anchor_range))
6043 }
6044
6045 fn update_selection_occurrence_highlights(
6046 &mut self,
6047 query_text: String,
6048 query_range: Range<Anchor>,
6049 multi_buffer_range_to_query: Range<Point>,
6050 use_debounce: bool,
6051 window: &mut Window,
6052 cx: &mut Context<Editor>,
6053 ) -> Task<()> {
6054 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6055 cx.spawn_in(window, async move |editor, cx| {
6056 if use_debounce {
6057 cx.background_executor()
6058 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6059 .await;
6060 }
6061 let match_task = cx.background_spawn(async move {
6062 let buffer_ranges = multi_buffer_snapshot
6063 .range_to_buffer_ranges(multi_buffer_range_to_query)
6064 .into_iter()
6065 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6066 let mut match_ranges = Vec::new();
6067 let Ok(regex) = project::search::SearchQuery::text(
6068 query_text.clone(),
6069 false,
6070 false,
6071 false,
6072 Default::default(),
6073 Default::default(),
6074 false,
6075 None,
6076 ) else {
6077 return Vec::default();
6078 };
6079 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6080 match_ranges.extend(
6081 regex
6082 .search(&buffer_snapshot, Some(search_range.clone()))
6083 .await
6084 .into_iter()
6085 .filter_map(|match_range| {
6086 let match_start = buffer_snapshot
6087 .anchor_after(search_range.start + match_range.start);
6088 let match_end = buffer_snapshot
6089 .anchor_before(search_range.start + match_range.end);
6090 let match_anchor_range = Anchor::range_in_buffer(
6091 excerpt_id,
6092 buffer_snapshot.remote_id(),
6093 match_start..match_end,
6094 );
6095 (match_anchor_range != query_range).then_some(match_anchor_range)
6096 }),
6097 );
6098 }
6099 match_ranges
6100 });
6101 let match_ranges = match_task.await;
6102 editor
6103 .update_in(cx, |editor, _, cx| {
6104 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6105 if !match_ranges.is_empty() {
6106 editor.highlight_background::<SelectedTextHighlight>(
6107 &match_ranges,
6108 |theme| theme.editor_document_highlight_bracket_background,
6109 cx,
6110 )
6111 }
6112 })
6113 .log_err();
6114 })
6115 }
6116
6117 fn refresh_selected_text_highlights(
6118 &mut self,
6119 on_buffer_edit: bool,
6120 window: &mut Window,
6121 cx: &mut Context<Editor>,
6122 ) {
6123 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6124 else {
6125 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6126 self.quick_selection_highlight_task.take();
6127 self.debounced_selection_highlight_task.take();
6128 return;
6129 };
6130 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6131 if on_buffer_edit
6132 || self
6133 .quick_selection_highlight_task
6134 .as_ref()
6135 .map_or(true, |(prev_anchor_range, _)| {
6136 prev_anchor_range != &query_range
6137 })
6138 {
6139 let multi_buffer_visible_start = self
6140 .scroll_manager
6141 .anchor()
6142 .anchor
6143 .to_point(&multi_buffer_snapshot);
6144 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6145 multi_buffer_visible_start
6146 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6147 Bias::Left,
6148 );
6149 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6150 self.quick_selection_highlight_task = Some((
6151 query_range.clone(),
6152 self.update_selection_occurrence_highlights(
6153 query_text.clone(),
6154 query_range.clone(),
6155 multi_buffer_visible_range,
6156 false,
6157 window,
6158 cx,
6159 ),
6160 ));
6161 }
6162 if on_buffer_edit
6163 || self
6164 .debounced_selection_highlight_task
6165 .as_ref()
6166 .map_or(true, |(prev_anchor_range, _)| {
6167 prev_anchor_range != &query_range
6168 })
6169 {
6170 let multi_buffer_start = multi_buffer_snapshot
6171 .anchor_before(0)
6172 .to_point(&multi_buffer_snapshot);
6173 let multi_buffer_end = multi_buffer_snapshot
6174 .anchor_after(multi_buffer_snapshot.len())
6175 .to_point(&multi_buffer_snapshot);
6176 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6177 self.debounced_selection_highlight_task = Some((
6178 query_range.clone(),
6179 self.update_selection_occurrence_highlights(
6180 query_text,
6181 query_range,
6182 multi_buffer_full_range,
6183 true,
6184 window,
6185 cx,
6186 ),
6187 ));
6188 }
6189 }
6190
6191 pub fn refresh_inline_completion(
6192 &mut self,
6193 debounce: bool,
6194 user_requested: bool,
6195 window: &mut Window,
6196 cx: &mut Context<Self>,
6197 ) -> Option<()> {
6198 let provider = self.edit_prediction_provider()?;
6199 let cursor = self.selections.newest_anchor().head();
6200 let (buffer, cursor_buffer_position) =
6201 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6202
6203 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6204 self.discard_inline_completion(false, cx);
6205 return None;
6206 }
6207
6208 if !user_requested
6209 && (!self.should_show_edit_predictions()
6210 || !self.is_focused(window)
6211 || buffer.read(cx).is_empty())
6212 {
6213 self.discard_inline_completion(false, cx);
6214 return None;
6215 }
6216
6217 self.update_visible_inline_completion(window, cx);
6218 provider.refresh(
6219 self.project.clone(),
6220 buffer,
6221 cursor_buffer_position,
6222 debounce,
6223 cx,
6224 );
6225 Some(())
6226 }
6227
6228 fn show_edit_predictions_in_menu(&self) -> bool {
6229 match self.edit_prediction_settings {
6230 EditPredictionSettings::Disabled => false,
6231 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6232 }
6233 }
6234
6235 pub fn edit_predictions_enabled(&self) -> bool {
6236 match self.edit_prediction_settings {
6237 EditPredictionSettings::Disabled => false,
6238 EditPredictionSettings::Enabled { .. } => true,
6239 }
6240 }
6241
6242 fn edit_prediction_requires_modifier(&self) -> bool {
6243 match self.edit_prediction_settings {
6244 EditPredictionSettings::Disabled => false,
6245 EditPredictionSettings::Enabled {
6246 preview_requires_modifier,
6247 ..
6248 } => preview_requires_modifier,
6249 }
6250 }
6251
6252 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6253 if self.edit_prediction_provider.is_none() {
6254 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6255 } else {
6256 let selection = self.selections.newest_anchor();
6257 let cursor = selection.head();
6258
6259 if let Some((buffer, cursor_buffer_position)) =
6260 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6261 {
6262 self.edit_prediction_settings =
6263 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6264 }
6265 }
6266 }
6267
6268 fn edit_prediction_settings_at_position(
6269 &self,
6270 buffer: &Entity<Buffer>,
6271 buffer_position: language::Anchor,
6272 cx: &App,
6273 ) -> EditPredictionSettings {
6274 if !self.mode.is_full()
6275 || !self.show_inline_completions_override.unwrap_or(true)
6276 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6277 {
6278 return EditPredictionSettings::Disabled;
6279 }
6280
6281 let buffer = buffer.read(cx);
6282
6283 let file = buffer.file();
6284
6285 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6286 return EditPredictionSettings::Disabled;
6287 };
6288
6289 let by_provider = matches!(
6290 self.menu_inline_completions_policy,
6291 MenuInlineCompletionsPolicy::ByProvider
6292 );
6293
6294 let show_in_menu = by_provider
6295 && self
6296 .edit_prediction_provider
6297 .as_ref()
6298 .map_or(false, |provider| {
6299 provider.provider.show_completions_in_menu()
6300 });
6301
6302 let preview_requires_modifier =
6303 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6304
6305 EditPredictionSettings::Enabled {
6306 show_in_menu,
6307 preview_requires_modifier,
6308 }
6309 }
6310
6311 fn should_show_edit_predictions(&self) -> bool {
6312 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6313 }
6314
6315 pub fn edit_prediction_preview_is_active(&self) -> bool {
6316 matches!(
6317 self.edit_prediction_preview,
6318 EditPredictionPreview::Active { .. }
6319 )
6320 }
6321
6322 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6323 let cursor = self.selections.newest_anchor().head();
6324 if let Some((buffer, cursor_position)) =
6325 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6326 {
6327 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6328 } else {
6329 false
6330 }
6331 }
6332
6333 pub fn supports_minimap(&self, cx: &App) -> bool {
6334 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6335 }
6336
6337 fn edit_predictions_enabled_in_buffer(
6338 &self,
6339 buffer: &Entity<Buffer>,
6340 buffer_position: language::Anchor,
6341 cx: &App,
6342 ) -> bool {
6343 maybe!({
6344 if self.read_only(cx) {
6345 return Some(false);
6346 }
6347 let provider = self.edit_prediction_provider()?;
6348 if !provider.is_enabled(&buffer, buffer_position, cx) {
6349 return Some(false);
6350 }
6351 let buffer = buffer.read(cx);
6352 let Some(file) = buffer.file() else {
6353 return Some(true);
6354 };
6355 let settings = all_language_settings(Some(file), cx);
6356 Some(settings.edit_predictions_enabled_for_file(file, cx))
6357 })
6358 .unwrap_or(false)
6359 }
6360
6361 fn cycle_inline_completion(
6362 &mut self,
6363 direction: Direction,
6364 window: &mut Window,
6365 cx: &mut Context<Self>,
6366 ) -> Option<()> {
6367 let provider = self.edit_prediction_provider()?;
6368 let cursor = self.selections.newest_anchor().head();
6369 let (buffer, cursor_buffer_position) =
6370 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6371 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6372 return None;
6373 }
6374
6375 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6376 self.update_visible_inline_completion(window, cx);
6377
6378 Some(())
6379 }
6380
6381 pub fn show_inline_completion(
6382 &mut self,
6383 _: &ShowEditPrediction,
6384 window: &mut Window,
6385 cx: &mut Context<Self>,
6386 ) {
6387 if !self.has_active_inline_completion() {
6388 self.refresh_inline_completion(false, true, window, cx);
6389 return;
6390 }
6391
6392 self.update_visible_inline_completion(window, cx);
6393 }
6394
6395 pub fn display_cursor_names(
6396 &mut self,
6397 _: &DisplayCursorNames,
6398 window: &mut Window,
6399 cx: &mut Context<Self>,
6400 ) {
6401 self.show_cursor_names(window, cx);
6402 }
6403
6404 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6405 self.show_cursor_names = true;
6406 cx.notify();
6407 cx.spawn_in(window, async move |this, cx| {
6408 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6409 this.update(cx, |this, cx| {
6410 this.show_cursor_names = false;
6411 cx.notify()
6412 })
6413 .ok()
6414 })
6415 .detach();
6416 }
6417
6418 pub fn next_edit_prediction(
6419 &mut self,
6420 _: &NextEditPrediction,
6421 window: &mut Window,
6422 cx: &mut Context<Self>,
6423 ) {
6424 if self.has_active_inline_completion() {
6425 self.cycle_inline_completion(Direction::Next, window, cx);
6426 } else {
6427 let is_copilot_disabled = self
6428 .refresh_inline_completion(false, true, window, cx)
6429 .is_none();
6430 if is_copilot_disabled {
6431 cx.propagate();
6432 }
6433 }
6434 }
6435
6436 pub fn previous_edit_prediction(
6437 &mut self,
6438 _: &PreviousEditPrediction,
6439 window: &mut Window,
6440 cx: &mut Context<Self>,
6441 ) {
6442 if self.has_active_inline_completion() {
6443 self.cycle_inline_completion(Direction::Prev, window, cx);
6444 } else {
6445 let is_copilot_disabled = self
6446 .refresh_inline_completion(false, true, window, cx)
6447 .is_none();
6448 if is_copilot_disabled {
6449 cx.propagate();
6450 }
6451 }
6452 }
6453
6454 pub fn accept_edit_prediction(
6455 &mut self,
6456 _: &AcceptEditPrediction,
6457 window: &mut Window,
6458 cx: &mut Context<Self>,
6459 ) {
6460 if self.show_edit_predictions_in_menu() {
6461 self.hide_context_menu(window, cx);
6462 }
6463
6464 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6465 return;
6466 };
6467
6468 self.report_inline_completion_event(
6469 active_inline_completion.completion_id.clone(),
6470 true,
6471 cx,
6472 );
6473
6474 match &active_inline_completion.completion {
6475 InlineCompletion::Move { target, .. } => {
6476 let target = *target;
6477
6478 if let Some(position_map) = &self.last_position_map {
6479 if position_map
6480 .visible_row_range
6481 .contains(&target.to_display_point(&position_map.snapshot).row())
6482 || !self.edit_prediction_requires_modifier()
6483 {
6484 self.unfold_ranges(&[target..target], true, false, cx);
6485 // Note that this is also done in vim's handler of the Tab action.
6486 self.change_selections(
6487 Some(Autoscroll::newest()),
6488 window,
6489 cx,
6490 |selections| {
6491 selections.select_anchor_ranges([target..target]);
6492 },
6493 );
6494 self.clear_row_highlights::<EditPredictionPreview>();
6495
6496 self.edit_prediction_preview
6497 .set_previous_scroll_position(None);
6498 } else {
6499 self.edit_prediction_preview
6500 .set_previous_scroll_position(Some(
6501 position_map.snapshot.scroll_anchor,
6502 ));
6503
6504 self.highlight_rows::<EditPredictionPreview>(
6505 target..target,
6506 cx.theme().colors().editor_highlighted_line_background,
6507 RowHighlightOptions {
6508 autoscroll: true,
6509 ..Default::default()
6510 },
6511 cx,
6512 );
6513 self.request_autoscroll(Autoscroll::fit(), cx);
6514 }
6515 }
6516 }
6517 InlineCompletion::Edit { edits, .. } => {
6518 if let Some(provider) = self.edit_prediction_provider() {
6519 provider.accept(cx);
6520 }
6521
6522 let snapshot = self.buffer.read(cx).snapshot(cx);
6523 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6524
6525 self.buffer.update(cx, |buffer, cx| {
6526 buffer.edit(edits.iter().cloned(), None, cx)
6527 });
6528
6529 self.change_selections(None, window, cx, |s| {
6530 s.select_anchor_ranges([last_edit_end..last_edit_end])
6531 });
6532
6533 self.update_visible_inline_completion(window, cx);
6534 if self.active_inline_completion.is_none() {
6535 self.refresh_inline_completion(true, true, window, cx);
6536 }
6537
6538 cx.notify();
6539 }
6540 }
6541
6542 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6543 }
6544
6545 pub fn accept_partial_inline_completion(
6546 &mut self,
6547 _: &AcceptPartialEditPrediction,
6548 window: &mut Window,
6549 cx: &mut Context<Self>,
6550 ) {
6551 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6552 return;
6553 };
6554 if self.selections.count() != 1 {
6555 return;
6556 }
6557
6558 self.report_inline_completion_event(
6559 active_inline_completion.completion_id.clone(),
6560 true,
6561 cx,
6562 );
6563
6564 match &active_inline_completion.completion {
6565 InlineCompletion::Move { target, .. } => {
6566 let target = *target;
6567 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6568 selections.select_anchor_ranges([target..target]);
6569 });
6570 }
6571 InlineCompletion::Edit { edits, .. } => {
6572 // Find an insertion that starts at the cursor position.
6573 let snapshot = self.buffer.read(cx).snapshot(cx);
6574 let cursor_offset = self.selections.newest::<usize>(cx).head();
6575 let insertion = edits.iter().find_map(|(range, text)| {
6576 let range = range.to_offset(&snapshot);
6577 if range.is_empty() && range.start == cursor_offset {
6578 Some(text)
6579 } else {
6580 None
6581 }
6582 });
6583
6584 if let Some(text) = insertion {
6585 let mut partial_completion = text
6586 .chars()
6587 .by_ref()
6588 .take_while(|c| c.is_alphabetic())
6589 .collect::<String>();
6590 if partial_completion.is_empty() {
6591 partial_completion = text
6592 .chars()
6593 .by_ref()
6594 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6595 .collect::<String>();
6596 }
6597
6598 cx.emit(EditorEvent::InputHandled {
6599 utf16_range_to_replace: None,
6600 text: partial_completion.clone().into(),
6601 });
6602
6603 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6604
6605 self.refresh_inline_completion(true, true, window, cx);
6606 cx.notify();
6607 } else {
6608 self.accept_edit_prediction(&Default::default(), window, cx);
6609 }
6610 }
6611 }
6612 }
6613
6614 fn discard_inline_completion(
6615 &mut self,
6616 should_report_inline_completion_event: bool,
6617 cx: &mut Context<Self>,
6618 ) -> bool {
6619 if should_report_inline_completion_event {
6620 let completion_id = self
6621 .active_inline_completion
6622 .as_ref()
6623 .and_then(|active_completion| active_completion.completion_id.clone());
6624
6625 self.report_inline_completion_event(completion_id, false, cx);
6626 }
6627
6628 if let Some(provider) = self.edit_prediction_provider() {
6629 provider.discard(cx);
6630 }
6631
6632 self.take_active_inline_completion(cx)
6633 }
6634
6635 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6636 let Some(provider) = self.edit_prediction_provider() else {
6637 return;
6638 };
6639
6640 let Some((_, buffer, _)) = self
6641 .buffer
6642 .read(cx)
6643 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6644 else {
6645 return;
6646 };
6647
6648 let extension = buffer
6649 .read(cx)
6650 .file()
6651 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6652
6653 let event_type = match accepted {
6654 true => "Edit Prediction Accepted",
6655 false => "Edit Prediction Discarded",
6656 };
6657 telemetry::event!(
6658 event_type,
6659 provider = provider.name(),
6660 prediction_id = id,
6661 suggestion_accepted = accepted,
6662 file_extension = extension,
6663 );
6664 }
6665
6666 pub fn has_active_inline_completion(&self) -> bool {
6667 self.active_inline_completion.is_some()
6668 }
6669
6670 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6671 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6672 return false;
6673 };
6674
6675 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6676 self.clear_highlights::<InlineCompletionHighlight>(cx);
6677 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6678 true
6679 }
6680
6681 /// Returns true when we're displaying the edit prediction popover below the cursor
6682 /// like we are not previewing and the LSP autocomplete menu is visible
6683 /// or we are in `when_holding_modifier` mode.
6684 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6685 if self.edit_prediction_preview_is_active()
6686 || !self.show_edit_predictions_in_menu()
6687 || !self.edit_predictions_enabled()
6688 {
6689 return false;
6690 }
6691
6692 if self.has_visible_completions_menu() {
6693 return true;
6694 }
6695
6696 has_completion && self.edit_prediction_requires_modifier()
6697 }
6698
6699 fn handle_modifiers_changed(
6700 &mut self,
6701 modifiers: Modifiers,
6702 position_map: &PositionMap,
6703 window: &mut Window,
6704 cx: &mut Context<Self>,
6705 ) {
6706 if self.show_edit_predictions_in_menu() {
6707 self.update_edit_prediction_preview(&modifiers, window, cx);
6708 }
6709
6710 self.update_selection_mode(&modifiers, position_map, window, cx);
6711
6712 let mouse_position = window.mouse_position();
6713 if !position_map.text_hitbox.is_hovered(window) {
6714 return;
6715 }
6716
6717 self.update_hovered_link(
6718 position_map.point_for_position(mouse_position),
6719 &position_map.snapshot,
6720 modifiers,
6721 window,
6722 cx,
6723 )
6724 }
6725
6726 fn update_selection_mode(
6727 &mut self,
6728 modifiers: &Modifiers,
6729 position_map: &PositionMap,
6730 window: &mut Window,
6731 cx: &mut Context<Self>,
6732 ) {
6733 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6734 return;
6735 }
6736
6737 let mouse_position = window.mouse_position();
6738 let point_for_position = position_map.point_for_position(mouse_position);
6739 let position = point_for_position.previous_valid;
6740
6741 self.select(
6742 SelectPhase::BeginColumnar {
6743 position,
6744 reset: false,
6745 goal_column: point_for_position.exact_unclipped.column(),
6746 },
6747 window,
6748 cx,
6749 );
6750 }
6751
6752 fn update_edit_prediction_preview(
6753 &mut self,
6754 modifiers: &Modifiers,
6755 window: &mut Window,
6756 cx: &mut Context<Self>,
6757 ) {
6758 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6759 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6760 return;
6761 };
6762
6763 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6764 if matches!(
6765 self.edit_prediction_preview,
6766 EditPredictionPreview::Inactive { .. }
6767 ) {
6768 self.edit_prediction_preview = EditPredictionPreview::Active {
6769 previous_scroll_position: None,
6770 since: Instant::now(),
6771 };
6772
6773 self.update_visible_inline_completion(window, cx);
6774 cx.notify();
6775 }
6776 } else if let EditPredictionPreview::Active {
6777 previous_scroll_position,
6778 since,
6779 } = self.edit_prediction_preview
6780 {
6781 if let (Some(previous_scroll_position), Some(position_map)) =
6782 (previous_scroll_position, self.last_position_map.as_ref())
6783 {
6784 self.set_scroll_position(
6785 previous_scroll_position
6786 .scroll_position(&position_map.snapshot.display_snapshot),
6787 window,
6788 cx,
6789 );
6790 }
6791
6792 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6793 released_too_fast: since.elapsed() < Duration::from_millis(200),
6794 };
6795 self.clear_row_highlights::<EditPredictionPreview>();
6796 self.update_visible_inline_completion(window, cx);
6797 cx.notify();
6798 }
6799 }
6800
6801 fn update_visible_inline_completion(
6802 &mut self,
6803 _window: &mut Window,
6804 cx: &mut Context<Self>,
6805 ) -> Option<()> {
6806 let selection = self.selections.newest_anchor();
6807 let cursor = selection.head();
6808 let multibuffer = self.buffer.read(cx).snapshot(cx);
6809 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6810 let excerpt_id = cursor.excerpt_id;
6811
6812 let show_in_menu = self.show_edit_predictions_in_menu();
6813 let completions_menu_has_precedence = !show_in_menu
6814 && (self.context_menu.borrow().is_some()
6815 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6816
6817 if completions_menu_has_precedence
6818 || !offset_selection.is_empty()
6819 || self
6820 .active_inline_completion
6821 .as_ref()
6822 .map_or(false, |completion| {
6823 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6824 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6825 !invalidation_range.contains(&offset_selection.head())
6826 })
6827 {
6828 self.discard_inline_completion(false, cx);
6829 return None;
6830 }
6831
6832 self.take_active_inline_completion(cx);
6833 let Some(provider) = self.edit_prediction_provider() else {
6834 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6835 return None;
6836 };
6837
6838 let (buffer, cursor_buffer_position) =
6839 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6840
6841 self.edit_prediction_settings =
6842 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6843
6844 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6845
6846 if self.edit_prediction_indent_conflict {
6847 let cursor_point = cursor.to_point(&multibuffer);
6848
6849 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6850
6851 if let Some((_, indent)) = indents.iter().next() {
6852 if indent.len == cursor_point.column {
6853 self.edit_prediction_indent_conflict = false;
6854 }
6855 }
6856 }
6857
6858 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6859 let edits = inline_completion
6860 .edits
6861 .into_iter()
6862 .flat_map(|(range, new_text)| {
6863 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6864 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6865 Some((start..end, new_text))
6866 })
6867 .collect::<Vec<_>>();
6868 if edits.is_empty() {
6869 return None;
6870 }
6871
6872 let first_edit_start = edits.first().unwrap().0.start;
6873 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6874 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6875
6876 let last_edit_end = edits.last().unwrap().0.end;
6877 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6878 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6879
6880 let cursor_row = cursor.to_point(&multibuffer).row;
6881
6882 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6883
6884 let mut inlay_ids = Vec::new();
6885 let invalidation_row_range;
6886 let move_invalidation_row_range = if cursor_row < edit_start_row {
6887 Some(cursor_row..edit_end_row)
6888 } else if cursor_row > edit_end_row {
6889 Some(edit_start_row..cursor_row)
6890 } else {
6891 None
6892 };
6893 let is_move =
6894 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6895 let completion = if is_move {
6896 invalidation_row_range =
6897 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6898 let target = first_edit_start;
6899 InlineCompletion::Move { target, snapshot }
6900 } else {
6901 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6902 && !self.inline_completions_hidden_for_vim_mode;
6903
6904 if show_completions_in_buffer {
6905 if edits
6906 .iter()
6907 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6908 {
6909 let mut inlays = Vec::new();
6910 for (range, new_text) in &edits {
6911 let inlay = Inlay::inline_completion(
6912 post_inc(&mut self.next_inlay_id),
6913 range.start,
6914 new_text.as_str(),
6915 );
6916 inlay_ids.push(inlay.id);
6917 inlays.push(inlay);
6918 }
6919
6920 self.splice_inlays(&[], inlays, cx);
6921 } else {
6922 let background_color = cx.theme().status().deleted_background;
6923 self.highlight_text::<InlineCompletionHighlight>(
6924 edits.iter().map(|(range, _)| range.clone()).collect(),
6925 HighlightStyle {
6926 background_color: Some(background_color),
6927 ..Default::default()
6928 },
6929 cx,
6930 );
6931 }
6932 }
6933
6934 invalidation_row_range = edit_start_row..edit_end_row;
6935
6936 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6937 if provider.show_tab_accept_marker() {
6938 EditDisplayMode::TabAccept
6939 } else {
6940 EditDisplayMode::Inline
6941 }
6942 } else {
6943 EditDisplayMode::DiffPopover
6944 };
6945
6946 InlineCompletion::Edit {
6947 edits,
6948 edit_preview: inline_completion.edit_preview,
6949 display_mode,
6950 snapshot,
6951 }
6952 };
6953
6954 let invalidation_range = multibuffer
6955 .anchor_before(Point::new(invalidation_row_range.start, 0))
6956 ..multibuffer.anchor_after(Point::new(
6957 invalidation_row_range.end,
6958 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6959 ));
6960
6961 self.stale_inline_completion_in_menu = None;
6962 self.active_inline_completion = Some(InlineCompletionState {
6963 inlay_ids,
6964 completion,
6965 completion_id: inline_completion.id,
6966 invalidation_range,
6967 });
6968
6969 cx.notify();
6970
6971 Some(())
6972 }
6973
6974 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6975 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6976 }
6977
6978 fn clear_tasks(&mut self) {
6979 self.tasks.clear()
6980 }
6981
6982 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6983 if self.tasks.insert(key, value).is_some() {
6984 // This case should hopefully be rare, but just in case...
6985 log::error!(
6986 "multiple different run targets found on a single line, only the last target will be rendered"
6987 )
6988 }
6989 }
6990
6991 /// Get all display points of breakpoints that will be rendered within editor
6992 ///
6993 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6994 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6995 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6996 fn active_breakpoints(
6997 &self,
6998 range: Range<DisplayRow>,
6999 window: &mut Window,
7000 cx: &mut Context<Self>,
7001 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7002 let mut breakpoint_display_points = HashMap::default();
7003
7004 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7005 return breakpoint_display_points;
7006 };
7007
7008 let snapshot = self.snapshot(window, cx);
7009
7010 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7011 let Some(project) = self.project.as_ref() else {
7012 return breakpoint_display_points;
7013 };
7014
7015 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7016 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7017
7018 for (buffer_snapshot, range, excerpt_id) in
7019 multi_buffer_snapshot.range_to_buffer_ranges(range)
7020 {
7021 let Some(buffer) = project.read_with(cx, |this, cx| {
7022 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7023 }) else {
7024 continue;
7025 };
7026 let breakpoints = breakpoint_store.read(cx).breakpoints(
7027 &buffer,
7028 Some(
7029 buffer_snapshot.anchor_before(range.start)
7030 ..buffer_snapshot.anchor_after(range.end),
7031 ),
7032 buffer_snapshot,
7033 cx,
7034 );
7035 for (breakpoint, state) in breakpoints {
7036 let multi_buffer_anchor =
7037 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7038 let position = multi_buffer_anchor
7039 .to_point(&multi_buffer_snapshot)
7040 .to_display_point(&snapshot);
7041
7042 breakpoint_display_points.insert(
7043 position.row(),
7044 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7045 );
7046 }
7047 }
7048
7049 breakpoint_display_points
7050 }
7051
7052 fn breakpoint_context_menu(
7053 &self,
7054 anchor: Anchor,
7055 window: &mut Window,
7056 cx: &mut Context<Self>,
7057 ) -> Entity<ui::ContextMenu> {
7058 let weak_editor = cx.weak_entity();
7059 let focus_handle = self.focus_handle(cx);
7060
7061 let row = self
7062 .buffer
7063 .read(cx)
7064 .snapshot(cx)
7065 .summary_for_anchor::<Point>(&anchor)
7066 .row;
7067
7068 let breakpoint = self
7069 .breakpoint_at_row(row, window, cx)
7070 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7071
7072 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7073 "Edit Log Breakpoint"
7074 } else {
7075 "Set Log Breakpoint"
7076 };
7077
7078 let condition_breakpoint_msg = if breakpoint
7079 .as_ref()
7080 .is_some_and(|bp| bp.1.condition.is_some())
7081 {
7082 "Edit Condition Breakpoint"
7083 } else {
7084 "Set Condition Breakpoint"
7085 };
7086
7087 let hit_condition_breakpoint_msg = if breakpoint
7088 .as_ref()
7089 .is_some_and(|bp| bp.1.hit_condition.is_some())
7090 {
7091 "Edit Hit Condition Breakpoint"
7092 } else {
7093 "Set Hit Condition Breakpoint"
7094 };
7095
7096 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7097 "Unset Breakpoint"
7098 } else {
7099 "Set Breakpoint"
7100 };
7101
7102 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7103 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7104
7105 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7106 BreakpointState::Enabled => Some("Disable"),
7107 BreakpointState::Disabled => Some("Enable"),
7108 });
7109
7110 let (anchor, breakpoint) =
7111 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7112
7113 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7114 menu.on_blur_subscription(Subscription::new(|| {}))
7115 .context(focus_handle)
7116 .when(run_to_cursor, |this| {
7117 let weak_editor = weak_editor.clone();
7118 this.entry("Run to cursor", None, move |window, cx| {
7119 weak_editor
7120 .update(cx, |editor, cx| {
7121 editor.change_selections(None, window, cx, |s| {
7122 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7123 });
7124 })
7125 .ok();
7126
7127 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7128 })
7129 .separator()
7130 })
7131 .when_some(toggle_state_msg, |this, msg| {
7132 this.entry(msg, None, {
7133 let weak_editor = weak_editor.clone();
7134 let breakpoint = breakpoint.clone();
7135 move |_window, cx| {
7136 weak_editor
7137 .update(cx, |this, cx| {
7138 this.edit_breakpoint_at_anchor(
7139 anchor,
7140 breakpoint.as_ref().clone(),
7141 BreakpointEditAction::InvertState,
7142 cx,
7143 );
7144 })
7145 .log_err();
7146 }
7147 })
7148 })
7149 .entry(set_breakpoint_msg, None, {
7150 let weak_editor = weak_editor.clone();
7151 let breakpoint = breakpoint.clone();
7152 move |_window, cx| {
7153 weak_editor
7154 .update(cx, |this, cx| {
7155 this.edit_breakpoint_at_anchor(
7156 anchor,
7157 breakpoint.as_ref().clone(),
7158 BreakpointEditAction::Toggle,
7159 cx,
7160 );
7161 })
7162 .log_err();
7163 }
7164 })
7165 .entry(log_breakpoint_msg, None, {
7166 let breakpoint = breakpoint.clone();
7167 let weak_editor = weak_editor.clone();
7168 move |window, cx| {
7169 weak_editor
7170 .update(cx, |this, cx| {
7171 this.add_edit_breakpoint_block(
7172 anchor,
7173 breakpoint.as_ref(),
7174 BreakpointPromptEditAction::Log,
7175 window,
7176 cx,
7177 );
7178 })
7179 .log_err();
7180 }
7181 })
7182 .entry(condition_breakpoint_msg, None, {
7183 let breakpoint = breakpoint.clone();
7184 let weak_editor = weak_editor.clone();
7185 move |window, cx| {
7186 weak_editor
7187 .update(cx, |this, cx| {
7188 this.add_edit_breakpoint_block(
7189 anchor,
7190 breakpoint.as_ref(),
7191 BreakpointPromptEditAction::Condition,
7192 window,
7193 cx,
7194 );
7195 })
7196 .log_err();
7197 }
7198 })
7199 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7200 weak_editor
7201 .update(cx, |this, cx| {
7202 this.add_edit_breakpoint_block(
7203 anchor,
7204 breakpoint.as_ref(),
7205 BreakpointPromptEditAction::HitCondition,
7206 window,
7207 cx,
7208 );
7209 })
7210 .log_err();
7211 })
7212 })
7213 }
7214
7215 fn render_breakpoint(
7216 &self,
7217 position: Anchor,
7218 row: DisplayRow,
7219 breakpoint: &Breakpoint,
7220 state: Option<BreakpointSessionState>,
7221 cx: &mut Context<Self>,
7222 ) -> IconButton {
7223 let is_rejected = state.is_some_and(|s| !s.verified);
7224 // Is it a breakpoint that shows up when hovering over gutter?
7225 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7226 (false, false),
7227 |PhantomBreakpointIndicator {
7228 is_active,
7229 display_row,
7230 collides_with_existing_breakpoint,
7231 }| {
7232 (
7233 is_active && display_row == row,
7234 collides_with_existing_breakpoint,
7235 )
7236 },
7237 );
7238
7239 let (color, icon) = {
7240 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7241 (false, false) => ui::IconName::DebugBreakpoint,
7242 (true, false) => ui::IconName::DebugLogBreakpoint,
7243 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7244 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7245 };
7246
7247 let color = if is_phantom {
7248 Color::Hint
7249 } else if is_rejected {
7250 Color::Disabled
7251 } else {
7252 Color::Debugger
7253 };
7254
7255 (color, icon)
7256 };
7257
7258 let breakpoint = Arc::from(breakpoint.clone());
7259
7260 let alt_as_text = gpui::Keystroke {
7261 modifiers: Modifiers::secondary_key(),
7262 ..Default::default()
7263 };
7264 let primary_action_text = if breakpoint.is_disabled() {
7265 "enable"
7266 } else if is_phantom && !collides_with_existing {
7267 "set"
7268 } else {
7269 "unset"
7270 };
7271 let mut primary_text = format!("Click to {primary_action_text}");
7272 if collides_with_existing && !breakpoint.is_disabled() {
7273 use std::fmt::Write;
7274 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7275 }
7276 let primary_text = SharedString::from(primary_text);
7277 let focus_handle = self.focus_handle.clone();
7278
7279 let meta = if is_rejected {
7280 "No executable code is associated with this line."
7281 } else {
7282 "Right-click for more options."
7283 };
7284 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7285 .icon_size(IconSize::XSmall)
7286 .size(ui::ButtonSize::None)
7287 .when(is_rejected, |this| {
7288 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7289 })
7290 .icon_color(color)
7291 .style(ButtonStyle::Transparent)
7292 .on_click(cx.listener({
7293 let breakpoint = breakpoint.clone();
7294
7295 move |editor, event: &ClickEvent, window, cx| {
7296 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7297 BreakpointEditAction::InvertState
7298 } else {
7299 BreakpointEditAction::Toggle
7300 };
7301
7302 window.focus(&editor.focus_handle(cx));
7303 editor.edit_breakpoint_at_anchor(
7304 position,
7305 breakpoint.as_ref().clone(),
7306 edit_action,
7307 cx,
7308 );
7309 }
7310 }))
7311 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7312 editor.set_breakpoint_context_menu(
7313 row,
7314 Some(position),
7315 event.down.position,
7316 window,
7317 cx,
7318 );
7319 }))
7320 .tooltip(move |window, cx| {
7321 Tooltip::with_meta_in(primary_text.clone(), None, meta, &focus_handle, window, cx)
7322 })
7323 }
7324
7325 fn build_tasks_context(
7326 project: &Entity<Project>,
7327 buffer: &Entity<Buffer>,
7328 buffer_row: u32,
7329 tasks: &Arc<RunnableTasks>,
7330 cx: &mut Context<Self>,
7331 ) -> Task<Option<task::TaskContext>> {
7332 let position = Point::new(buffer_row, tasks.column);
7333 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7334 let location = Location {
7335 buffer: buffer.clone(),
7336 range: range_start..range_start,
7337 };
7338 // Fill in the environmental variables from the tree-sitter captures
7339 let mut captured_task_variables = TaskVariables::default();
7340 for (capture_name, value) in tasks.extra_variables.clone() {
7341 captured_task_variables.insert(
7342 task::VariableName::Custom(capture_name.into()),
7343 value.clone(),
7344 );
7345 }
7346 project.update(cx, |project, cx| {
7347 project.task_store().update(cx, |task_store, cx| {
7348 task_store.task_context_for_location(captured_task_variables, location, cx)
7349 })
7350 })
7351 }
7352
7353 pub fn spawn_nearest_task(
7354 &mut self,
7355 action: &SpawnNearestTask,
7356 window: &mut Window,
7357 cx: &mut Context<Self>,
7358 ) {
7359 let Some((workspace, _)) = self.workspace.clone() else {
7360 return;
7361 };
7362 let Some(project) = self.project.clone() else {
7363 return;
7364 };
7365
7366 // Try to find a closest, enclosing node using tree-sitter that has a
7367 // task
7368 let Some((buffer, buffer_row, tasks)) = self
7369 .find_enclosing_node_task(cx)
7370 // Or find the task that's closest in row-distance.
7371 .or_else(|| self.find_closest_task(cx))
7372 else {
7373 return;
7374 };
7375
7376 let reveal_strategy = action.reveal;
7377 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7378 cx.spawn_in(window, async move |_, cx| {
7379 let context = task_context.await?;
7380 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7381
7382 let resolved = &mut resolved_task.resolved;
7383 resolved.reveal = reveal_strategy;
7384
7385 workspace
7386 .update_in(cx, |workspace, window, cx| {
7387 workspace.schedule_resolved_task(
7388 task_source_kind,
7389 resolved_task,
7390 false,
7391 window,
7392 cx,
7393 );
7394 })
7395 .ok()
7396 })
7397 .detach();
7398 }
7399
7400 fn find_closest_task(
7401 &mut self,
7402 cx: &mut Context<Self>,
7403 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7404 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7405
7406 let ((buffer_id, row), tasks) = self
7407 .tasks
7408 .iter()
7409 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7410
7411 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7412 let tasks = Arc::new(tasks.to_owned());
7413 Some((buffer, *row, tasks))
7414 }
7415
7416 fn find_enclosing_node_task(
7417 &mut self,
7418 cx: &mut Context<Self>,
7419 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7420 let snapshot = self.buffer.read(cx).snapshot(cx);
7421 let offset = self.selections.newest::<usize>(cx).head();
7422 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7423 let buffer_id = excerpt.buffer().remote_id();
7424
7425 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7426 let mut cursor = layer.node().walk();
7427
7428 while cursor.goto_first_child_for_byte(offset).is_some() {
7429 if cursor.node().end_byte() == offset {
7430 cursor.goto_next_sibling();
7431 }
7432 }
7433
7434 // Ascend to the smallest ancestor that contains the range and has a task.
7435 loop {
7436 let node = cursor.node();
7437 let node_range = node.byte_range();
7438 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7439
7440 // Check if this node contains our offset
7441 if node_range.start <= offset && node_range.end >= offset {
7442 // If it contains offset, check for task
7443 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7444 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7445 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7446 }
7447 }
7448
7449 if !cursor.goto_parent() {
7450 break;
7451 }
7452 }
7453 None
7454 }
7455
7456 fn render_run_indicator(
7457 &self,
7458 _style: &EditorStyle,
7459 is_active: bool,
7460 row: DisplayRow,
7461 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7462 cx: &mut Context<Self>,
7463 ) -> IconButton {
7464 let color = Color::Muted;
7465 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7466
7467 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7468 .shape(ui::IconButtonShape::Square)
7469 .icon_size(IconSize::XSmall)
7470 .icon_color(color)
7471 .toggle_state(is_active)
7472 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7473 let quick_launch = e.down.button == MouseButton::Left;
7474 window.focus(&editor.focus_handle(cx));
7475 editor.toggle_code_actions(
7476 &ToggleCodeActions {
7477 deployed_from_indicator: Some(row),
7478 quick_launch,
7479 },
7480 window,
7481 cx,
7482 );
7483 }))
7484 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7485 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7486 }))
7487 }
7488
7489 pub fn context_menu_visible(&self) -> bool {
7490 !self.edit_prediction_preview_is_active()
7491 && self
7492 .context_menu
7493 .borrow()
7494 .as_ref()
7495 .map_or(false, |menu| menu.visible())
7496 }
7497
7498 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7499 self.context_menu
7500 .borrow()
7501 .as_ref()
7502 .map(|menu| menu.origin())
7503 }
7504
7505 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7506 self.context_menu_options = Some(options);
7507 }
7508
7509 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7510 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7511
7512 fn render_edit_prediction_popover(
7513 &mut self,
7514 text_bounds: &Bounds<Pixels>,
7515 content_origin: gpui::Point<Pixels>,
7516 right_margin: Pixels,
7517 editor_snapshot: &EditorSnapshot,
7518 visible_row_range: Range<DisplayRow>,
7519 scroll_top: f32,
7520 scroll_bottom: f32,
7521 line_layouts: &[LineWithInvisibles],
7522 line_height: Pixels,
7523 scroll_pixel_position: gpui::Point<Pixels>,
7524 newest_selection_head: Option<DisplayPoint>,
7525 editor_width: Pixels,
7526 style: &EditorStyle,
7527 window: &mut Window,
7528 cx: &mut App,
7529 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7530 if self.mode().is_minimap() {
7531 return None;
7532 }
7533 let active_inline_completion = self.active_inline_completion.as_ref()?;
7534
7535 if self.edit_prediction_visible_in_cursor_popover(true) {
7536 return None;
7537 }
7538
7539 match &active_inline_completion.completion {
7540 InlineCompletion::Move { target, .. } => {
7541 let target_display_point = target.to_display_point(editor_snapshot);
7542
7543 if self.edit_prediction_requires_modifier() {
7544 if !self.edit_prediction_preview_is_active() {
7545 return None;
7546 }
7547
7548 self.render_edit_prediction_modifier_jump_popover(
7549 text_bounds,
7550 content_origin,
7551 visible_row_range,
7552 line_layouts,
7553 line_height,
7554 scroll_pixel_position,
7555 newest_selection_head,
7556 target_display_point,
7557 window,
7558 cx,
7559 )
7560 } else {
7561 self.render_edit_prediction_eager_jump_popover(
7562 text_bounds,
7563 content_origin,
7564 editor_snapshot,
7565 visible_row_range,
7566 scroll_top,
7567 scroll_bottom,
7568 line_height,
7569 scroll_pixel_position,
7570 target_display_point,
7571 editor_width,
7572 window,
7573 cx,
7574 )
7575 }
7576 }
7577 InlineCompletion::Edit {
7578 display_mode: EditDisplayMode::Inline,
7579 ..
7580 } => None,
7581 InlineCompletion::Edit {
7582 display_mode: EditDisplayMode::TabAccept,
7583 edits,
7584 ..
7585 } => {
7586 let range = &edits.first()?.0;
7587 let target_display_point = range.end.to_display_point(editor_snapshot);
7588
7589 self.render_edit_prediction_end_of_line_popover(
7590 "Accept",
7591 editor_snapshot,
7592 visible_row_range,
7593 target_display_point,
7594 line_height,
7595 scroll_pixel_position,
7596 content_origin,
7597 editor_width,
7598 window,
7599 cx,
7600 )
7601 }
7602 InlineCompletion::Edit {
7603 edits,
7604 edit_preview,
7605 display_mode: EditDisplayMode::DiffPopover,
7606 snapshot,
7607 } => self.render_edit_prediction_diff_popover(
7608 text_bounds,
7609 content_origin,
7610 right_margin,
7611 editor_snapshot,
7612 visible_row_range,
7613 line_layouts,
7614 line_height,
7615 scroll_pixel_position,
7616 newest_selection_head,
7617 editor_width,
7618 style,
7619 edits,
7620 edit_preview,
7621 snapshot,
7622 window,
7623 cx,
7624 ),
7625 }
7626 }
7627
7628 fn render_edit_prediction_modifier_jump_popover(
7629 &mut self,
7630 text_bounds: &Bounds<Pixels>,
7631 content_origin: gpui::Point<Pixels>,
7632 visible_row_range: Range<DisplayRow>,
7633 line_layouts: &[LineWithInvisibles],
7634 line_height: Pixels,
7635 scroll_pixel_position: gpui::Point<Pixels>,
7636 newest_selection_head: Option<DisplayPoint>,
7637 target_display_point: DisplayPoint,
7638 window: &mut Window,
7639 cx: &mut App,
7640 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7641 let scrolled_content_origin =
7642 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7643
7644 const SCROLL_PADDING_Y: Pixels = px(12.);
7645
7646 if target_display_point.row() < visible_row_range.start {
7647 return self.render_edit_prediction_scroll_popover(
7648 |_| SCROLL_PADDING_Y,
7649 IconName::ArrowUp,
7650 visible_row_range,
7651 line_layouts,
7652 newest_selection_head,
7653 scrolled_content_origin,
7654 window,
7655 cx,
7656 );
7657 } else if target_display_point.row() >= visible_row_range.end {
7658 return self.render_edit_prediction_scroll_popover(
7659 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7660 IconName::ArrowDown,
7661 visible_row_range,
7662 line_layouts,
7663 newest_selection_head,
7664 scrolled_content_origin,
7665 window,
7666 cx,
7667 );
7668 }
7669
7670 const POLE_WIDTH: Pixels = px(2.);
7671
7672 let line_layout =
7673 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7674 let target_column = target_display_point.column() as usize;
7675
7676 let target_x = line_layout.x_for_index(target_column);
7677 let target_y =
7678 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7679
7680 let flag_on_right = target_x < text_bounds.size.width / 2.;
7681
7682 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7683 border_color.l += 0.001;
7684
7685 let mut element = v_flex()
7686 .items_end()
7687 .when(flag_on_right, |el| el.items_start())
7688 .child(if flag_on_right {
7689 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7690 .rounded_bl(px(0.))
7691 .rounded_tl(px(0.))
7692 .border_l_2()
7693 .border_color(border_color)
7694 } else {
7695 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7696 .rounded_br(px(0.))
7697 .rounded_tr(px(0.))
7698 .border_r_2()
7699 .border_color(border_color)
7700 })
7701 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7702 .into_any();
7703
7704 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7705
7706 let mut origin = scrolled_content_origin + point(target_x, target_y)
7707 - point(
7708 if flag_on_right {
7709 POLE_WIDTH
7710 } else {
7711 size.width - POLE_WIDTH
7712 },
7713 size.height - line_height,
7714 );
7715
7716 origin.x = origin.x.max(content_origin.x);
7717
7718 element.prepaint_at(origin, window, cx);
7719
7720 Some((element, origin))
7721 }
7722
7723 fn render_edit_prediction_scroll_popover(
7724 &mut self,
7725 to_y: impl Fn(Size<Pixels>) -> Pixels,
7726 scroll_icon: IconName,
7727 visible_row_range: Range<DisplayRow>,
7728 line_layouts: &[LineWithInvisibles],
7729 newest_selection_head: Option<DisplayPoint>,
7730 scrolled_content_origin: gpui::Point<Pixels>,
7731 window: &mut Window,
7732 cx: &mut App,
7733 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7734 let mut element = self
7735 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7736 .into_any();
7737
7738 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7739
7740 let cursor = newest_selection_head?;
7741 let cursor_row_layout =
7742 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7743 let cursor_column = cursor.column() as usize;
7744
7745 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7746
7747 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7748
7749 element.prepaint_at(origin, window, cx);
7750 Some((element, origin))
7751 }
7752
7753 fn render_edit_prediction_eager_jump_popover(
7754 &mut self,
7755 text_bounds: &Bounds<Pixels>,
7756 content_origin: gpui::Point<Pixels>,
7757 editor_snapshot: &EditorSnapshot,
7758 visible_row_range: Range<DisplayRow>,
7759 scroll_top: f32,
7760 scroll_bottom: f32,
7761 line_height: Pixels,
7762 scroll_pixel_position: gpui::Point<Pixels>,
7763 target_display_point: DisplayPoint,
7764 editor_width: Pixels,
7765 window: &mut Window,
7766 cx: &mut App,
7767 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7768 if target_display_point.row().as_f32() < scroll_top {
7769 let mut element = self
7770 .render_edit_prediction_line_popover(
7771 "Jump to Edit",
7772 Some(IconName::ArrowUp),
7773 window,
7774 cx,
7775 )?
7776 .into_any();
7777
7778 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7779 let offset = point(
7780 (text_bounds.size.width - size.width) / 2.,
7781 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7782 );
7783
7784 let origin = text_bounds.origin + offset;
7785 element.prepaint_at(origin, window, cx);
7786 Some((element, origin))
7787 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7788 let mut element = self
7789 .render_edit_prediction_line_popover(
7790 "Jump to Edit",
7791 Some(IconName::ArrowDown),
7792 window,
7793 cx,
7794 )?
7795 .into_any();
7796
7797 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7798 let offset = point(
7799 (text_bounds.size.width - size.width) / 2.,
7800 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7801 );
7802
7803 let origin = text_bounds.origin + offset;
7804 element.prepaint_at(origin, window, cx);
7805 Some((element, origin))
7806 } else {
7807 self.render_edit_prediction_end_of_line_popover(
7808 "Jump to Edit",
7809 editor_snapshot,
7810 visible_row_range,
7811 target_display_point,
7812 line_height,
7813 scroll_pixel_position,
7814 content_origin,
7815 editor_width,
7816 window,
7817 cx,
7818 )
7819 }
7820 }
7821
7822 fn render_edit_prediction_end_of_line_popover(
7823 self: &mut Editor,
7824 label: &'static str,
7825 editor_snapshot: &EditorSnapshot,
7826 visible_row_range: Range<DisplayRow>,
7827 target_display_point: DisplayPoint,
7828 line_height: Pixels,
7829 scroll_pixel_position: gpui::Point<Pixels>,
7830 content_origin: gpui::Point<Pixels>,
7831 editor_width: Pixels,
7832 window: &mut Window,
7833 cx: &mut App,
7834 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7835 let target_line_end = DisplayPoint::new(
7836 target_display_point.row(),
7837 editor_snapshot.line_len(target_display_point.row()),
7838 );
7839
7840 let mut element = self
7841 .render_edit_prediction_line_popover(label, None, window, cx)?
7842 .into_any();
7843
7844 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7845
7846 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7847
7848 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7849 let mut origin = start_point
7850 + line_origin
7851 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7852 origin.x = origin.x.max(content_origin.x);
7853
7854 let max_x = content_origin.x + editor_width - size.width;
7855
7856 if origin.x > max_x {
7857 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7858
7859 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7860 origin.y += offset;
7861 IconName::ArrowUp
7862 } else {
7863 origin.y -= offset;
7864 IconName::ArrowDown
7865 };
7866
7867 element = self
7868 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7869 .into_any();
7870
7871 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7872
7873 origin.x = content_origin.x + editor_width - size.width - px(2.);
7874 }
7875
7876 element.prepaint_at(origin, window, cx);
7877 Some((element, origin))
7878 }
7879
7880 fn render_edit_prediction_diff_popover(
7881 self: &Editor,
7882 text_bounds: &Bounds<Pixels>,
7883 content_origin: gpui::Point<Pixels>,
7884 right_margin: Pixels,
7885 editor_snapshot: &EditorSnapshot,
7886 visible_row_range: Range<DisplayRow>,
7887 line_layouts: &[LineWithInvisibles],
7888 line_height: Pixels,
7889 scroll_pixel_position: gpui::Point<Pixels>,
7890 newest_selection_head: Option<DisplayPoint>,
7891 editor_width: Pixels,
7892 style: &EditorStyle,
7893 edits: &Vec<(Range<Anchor>, String)>,
7894 edit_preview: &Option<language::EditPreview>,
7895 snapshot: &language::BufferSnapshot,
7896 window: &mut Window,
7897 cx: &mut App,
7898 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7899 let edit_start = edits
7900 .first()
7901 .unwrap()
7902 .0
7903 .start
7904 .to_display_point(editor_snapshot);
7905 let edit_end = edits
7906 .last()
7907 .unwrap()
7908 .0
7909 .end
7910 .to_display_point(editor_snapshot);
7911
7912 let is_visible = visible_row_range.contains(&edit_start.row())
7913 || visible_row_range.contains(&edit_end.row());
7914 if !is_visible {
7915 return None;
7916 }
7917
7918 let highlighted_edits =
7919 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7920
7921 let styled_text = highlighted_edits.to_styled_text(&style.text);
7922 let line_count = highlighted_edits.text.lines().count();
7923
7924 const BORDER_WIDTH: Pixels = px(1.);
7925
7926 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7927 let has_keybind = keybind.is_some();
7928
7929 let mut element = h_flex()
7930 .items_start()
7931 .child(
7932 h_flex()
7933 .bg(cx.theme().colors().editor_background)
7934 .border(BORDER_WIDTH)
7935 .shadow_sm()
7936 .border_color(cx.theme().colors().border)
7937 .rounded_l_lg()
7938 .when(line_count > 1, |el| el.rounded_br_lg())
7939 .pr_1()
7940 .child(styled_text),
7941 )
7942 .child(
7943 h_flex()
7944 .h(line_height + BORDER_WIDTH * 2.)
7945 .px_1p5()
7946 .gap_1()
7947 // Workaround: For some reason, there's a gap if we don't do this
7948 .ml(-BORDER_WIDTH)
7949 .shadow(smallvec![gpui::BoxShadow {
7950 color: gpui::black().opacity(0.05),
7951 offset: point(px(1.), px(1.)),
7952 blur_radius: px(2.),
7953 spread_radius: px(0.),
7954 }])
7955 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7956 .border(BORDER_WIDTH)
7957 .border_color(cx.theme().colors().border)
7958 .rounded_r_lg()
7959 .id("edit_prediction_diff_popover_keybind")
7960 .when(!has_keybind, |el| {
7961 let status_colors = cx.theme().status();
7962
7963 el.bg(status_colors.error_background)
7964 .border_color(status_colors.error.opacity(0.6))
7965 .child(Icon::new(IconName::Info).color(Color::Error))
7966 .cursor_default()
7967 .hoverable_tooltip(move |_window, cx| {
7968 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7969 })
7970 })
7971 .children(keybind),
7972 )
7973 .into_any();
7974
7975 let longest_row =
7976 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7977 let longest_line_width = if visible_row_range.contains(&longest_row) {
7978 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7979 } else {
7980 layout_line(
7981 longest_row,
7982 editor_snapshot,
7983 style,
7984 editor_width,
7985 |_| false,
7986 window,
7987 cx,
7988 )
7989 .width
7990 };
7991
7992 let viewport_bounds =
7993 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7994 right: -right_margin,
7995 ..Default::default()
7996 });
7997
7998 let x_after_longest =
7999 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8000 - scroll_pixel_position.x;
8001
8002 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8003
8004 // Fully visible if it can be displayed within the window (allow overlapping other
8005 // panes). However, this is only allowed if the popover starts within text_bounds.
8006 let can_position_to_the_right = x_after_longest < text_bounds.right()
8007 && x_after_longest + element_bounds.width < viewport_bounds.right();
8008
8009 let mut origin = if can_position_to_the_right {
8010 point(
8011 x_after_longest,
8012 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8013 - scroll_pixel_position.y,
8014 )
8015 } else {
8016 let cursor_row = newest_selection_head.map(|head| head.row());
8017 let above_edit = edit_start
8018 .row()
8019 .0
8020 .checked_sub(line_count as u32)
8021 .map(DisplayRow);
8022 let below_edit = Some(edit_end.row() + 1);
8023 let above_cursor =
8024 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8025 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8026
8027 // Place the edit popover adjacent to the edit if there is a location
8028 // available that is onscreen and does not obscure the cursor. Otherwise,
8029 // place it adjacent to the cursor.
8030 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8031 .into_iter()
8032 .flatten()
8033 .find(|&start_row| {
8034 let end_row = start_row + line_count as u32;
8035 visible_row_range.contains(&start_row)
8036 && visible_row_range.contains(&end_row)
8037 && cursor_row.map_or(true, |cursor_row| {
8038 !((start_row..end_row).contains(&cursor_row))
8039 })
8040 })?;
8041
8042 content_origin
8043 + point(
8044 -scroll_pixel_position.x,
8045 row_target.as_f32() * line_height - scroll_pixel_position.y,
8046 )
8047 };
8048
8049 origin.x -= BORDER_WIDTH;
8050
8051 window.defer_draw(element, origin, 1);
8052
8053 // Do not return an element, since it will already be drawn due to defer_draw.
8054 None
8055 }
8056
8057 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8058 px(30.)
8059 }
8060
8061 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8062 if self.read_only(cx) {
8063 cx.theme().players().read_only()
8064 } else {
8065 self.style.as_ref().unwrap().local_player
8066 }
8067 }
8068
8069 fn render_edit_prediction_accept_keybind(
8070 &self,
8071 window: &mut Window,
8072 cx: &App,
8073 ) -> Option<AnyElement> {
8074 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8075 let accept_keystroke = accept_binding.keystroke()?;
8076
8077 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8078
8079 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8080 Color::Accent
8081 } else {
8082 Color::Muted
8083 };
8084
8085 h_flex()
8086 .px_0p5()
8087 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8088 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8089 .text_size(TextSize::XSmall.rems(cx))
8090 .child(h_flex().children(ui::render_modifiers(
8091 &accept_keystroke.modifiers,
8092 PlatformStyle::platform(),
8093 Some(modifiers_color),
8094 Some(IconSize::XSmall.rems().into()),
8095 true,
8096 )))
8097 .when(is_platform_style_mac, |parent| {
8098 parent.child(accept_keystroke.key.clone())
8099 })
8100 .when(!is_platform_style_mac, |parent| {
8101 parent.child(
8102 Key::new(
8103 util::capitalize(&accept_keystroke.key),
8104 Some(Color::Default),
8105 )
8106 .size(Some(IconSize::XSmall.rems().into())),
8107 )
8108 })
8109 .into_any()
8110 .into()
8111 }
8112
8113 fn render_edit_prediction_line_popover(
8114 &self,
8115 label: impl Into<SharedString>,
8116 icon: Option<IconName>,
8117 window: &mut Window,
8118 cx: &App,
8119 ) -> Option<Stateful<Div>> {
8120 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8121
8122 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8123 let has_keybind = keybind.is_some();
8124
8125 let result = h_flex()
8126 .id("ep-line-popover")
8127 .py_0p5()
8128 .pl_1()
8129 .pr(padding_right)
8130 .gap_1()
8131 .rounded_md()
8132 .border_1()
8133 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8134 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8135 .shadow_sm()
8136 .when(!has_keybind, |el| {
8137 let status_colors = cx.theme().status();
8138
8139 el.bg(status_colors.error_background)
8140 .border_color(status_colors.error.opacity(0.6))
8141 .pl_2()
8142 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8143 .cursor_default()
8144 .hoverable_tooltip(move |_window, cx| {
8145 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8146 })
8147 })
8148 .children(keybind)
8149 .child(
8150 Label::new(label)
8151 .size(LabelSize::Small)
8152 .when(!has_keybind, |el| {
8153 el.color(cx.theme().status().error.into()).strikethrough()
8154 }),
8155 )
8156 .when(!has_keybind, |el| {
8157 el.child(
8158 h_flex().ml_1().child(
8159 Icon::new(IconName::Info)
8160 .size(IconSize::Small)
8161 .color(cx.theme().status().error.into()),
8162 ),
8163 )
8164 })
8165 .when_some(icon, |element, icon| {
8166 element.child(
8167 div()
8168 .mt(px(1.5))
8169 .child(Icon::new(icon).size(IconSize::Small)),
8170 )
8171 });
8172
8173 Some(result)
8174 }
8175
8176 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8177 let accent_color = cx.theme().colors().text_accent;
8178 let editor_bg_color = cx.theme().colors().editor_background;
8179 editor_bg_color.blend(accent_color.opacity(0.1))
8180 }
8181
8182 fn edit_prediction_callout_popover_border_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.6))
8186 }
8187
8188 fn render_edit_prediction_cursor_popover(
8189 &self,
8190 min_width: Pixels,
8191 max_width: Pixels,
8192 cursor_point: Point,
8193 style: &EditorStyle,
8194 accept_keystroke: Option<&gpui::Keystroke>,
8195 _window: &Window,
8196 cx: &mut Context<Editor>,
8197 ) -> Option<AnyElement> {
8198 let provider = self.edit_prediction_provider.as_ref()?;
8199
8200 if provider.provider.needs_terms_acceptance(cx) {
8201 return Some(
8202 h_flex()
8203 .min_w(min_width)
8204 .flex_1()
8205 .px_2()
8206 .py_1()
8207 .gap_3()
8208 .elevation_2(cx)
8209 .hover(|style| style.bg(cx.theme().colors().element_hover))
8210 .id("accept-terms")
8211 .cursor_pointer()
8212 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8213 .on_click(cx.listener(|this, _event, window, cx| {
8214 cx.stop_propagation();
8215 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8216 window.dispatch_action(
8217 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8218 cx,
8219 );
8220 }))
8221 .child(
8222 h_flex()
8223 .flex_1()
8224 .gap_2()
8225 .child(Icon::new(IconName::ZedPredict))
8226 .child(Label::new("Accept Terms of Service"))
8227 .child(div().w_full())
8228 .child(
8229 Icon::new(IconName::ArrowUpRight)
8230 .color(Color::Muted)
8231 .size(IconSize::Small),
8232 )
8233 .into_any_element(),
8234 )
8235 .into_any(),
8236 );
8237 }
8238
8239 let is_refreshing = provider.provider.is_refreshing(cx);
8240
8241 fn pending_completion_container() -> Div {
8242 h_flex()
8243 .h_full()
8244 .flex_1()
8245 .gap_2()
8246 .child(Icon::new(IconName::ZedPredict))
8247 }
8248
8249 let completion = match &self.active_inline_completion {
8250 Some(prediction) => {
8251 if !self.has_visible_completions_menu() {
8252 const RADIUS: Pixels = px(6.);
8253 const BORDER_WIDTH: Pixels = px(1.);
8254
8255 return Some(
8256 h_flex()
8257 .elevation_2(cx)
8258 .border(BORDER_WIDTH)
8259 .border_color(cx.theme().colors().border)
8260 .when(accept_keystroke.is_none(), |el| {
8261 el.border_color(cx.theme().status().error)
8262 })
8263 .rounded(RADIUS)
8264 .rounded_tl(px(0.))
8265 .overflow_hidden()
8266 .child(div().px_1p5().child(match &prediction.completion {
8267 InlineCompletion::Move { target, snapshot } => {
8268 use text::ToPoint as _;
8269 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8270 {
8271 Icon::new(IconName::ZedPredictDown)
8272 } else {
8273 Icon::new(IconName::ZedPredictUp)
8274 }
8275 }
8276 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8277 }))
8278 .child(
8279 h_flex()
8280 .gap_1()
8281 .py_1()
8282 .px_2()
8283 .rounded_r(RADIUS - BORDER_WIDTH)
8284 .border_l_1()
8285 .border_color(cx.theme().colors().border)
8286 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8287 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8288 el.child(
8289 Label::new("Hold")
8290 .size(LabelSize::Small)
8291 .when(accept_keystroke.is_none(), |el| {
8292 el.strikethrough()
8293 })
8294 .line_height_style(LineHeightStyle::UiLabel),
8295 )
8296 })
8297 .id("edit_prediction_cursor_popover_keybind")
8298 .when(accept_keystroke.is_none(), |el| {
8299 let status_colors = cx.theme().status();
8300
8301 el.bg(status_colors.error_background)
8302 .border_color(status_colors.error.opacity(0.6))
8303 .child(Icon::new(IconName::Info).color(Color::Error))
8304 .cursor_default()
8305 .hoverable_tooltip(move |_window, cx| {
8306 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8307 .into()
8308 })
8309 })
8310 .when_some(
8311 accept_keystroke.as_ref(),
8312 |el, accept_keystroke| {
8313 el.child(h_flex().children(ui::render_modifiers(
8314 &accept_keystroke.modifiers,
8315 PlatformStyle::platform(),
8316 Some(Color::Default),
8317 Some(IconSize::XSmall.rems().into()),
8318 false,
8319 )))
8320 },
8321 ),
8322 )
8323 .into_any(),
8324 );
8325 }
8326
8327 self.render_edit_prediction_cursor_popover_preview(
8328 prediction,
8329 cursor_point,
8330 style,
8331 cx,
8332 )?
8333 }
8334
8335 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8336 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8337 stale_completion,
8338 cursor_point,
8339 style,
8340 cx,
8341 )?,
8342
8343 None => {
8344 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8345 }
8346 },
8347
8348 None => pending_completion_container().child(Label::new("No Prediction")),
8349 };
8350
8351 let completion = if is_refreshing {
8352 completion
8353 .with_animation(
8354 "loading-completion",
8355 Animation::new(Duration::from_secs(2))
8356 .repeat()
8357 .with_easing(pulsating_between(0.4, 0.8)),
8358 |label, delta| label.opacity(delta),
8359 )
8360 .into_any_element()
8361 } else {
8362 completion.into_any_element()
8363 };
8364
8365 let has_completion = self.active_inline_completion.is_some();
8366
8367 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8368 Some(
8369 h_flex()
8370 .min_w(min_width)
8371 .max_w(max_width)
8372 .flex_1()
8373 .elevation_2(cx)
8374 .border_color(cx.theme().colors().border)
8375 .child(
8376 div()
8377 .flex_1()
8378 .py_1()
8379 .px_2()
8380 .overflow_hidden()
8381 .child(completion),
8382 )
8383 .when_some(accept_keystroke, |el, accept_keystroke| {
8384 if !accept_keystroke.modifiers.modified() {
8385 return el;
8386 }
8387
8388 el.child(
8389 h_flex()
8390 .h_full()
8391 .border_l_1()
8392 .rounded_r_lg()
8393 .border_color(cx.theme().colors().border)
8394 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8395 .gap_1()
8396 .py_1()
8397 .px_2()
8398 .child(
8399 h_flex()
8400 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8401 .when(is_platform_style_mac, |parent| parent.gap_1())
8402 .child(h_flex().children(ui::render_modifiers(
8403 &accept_keystroke.modifiers,
8404 PlatformStyle::platform(),
8405 Some(if !has_completion {
8406 Color::Muted
8407 } else {
8408 Color::Default
8409 }),
8410 None,
8411 false,
8412 ))),
8413 )
8414 .child(Label::new("Preview").into_any_element())
8415 .opacity(if has_completion { 1.0 } else { 0.4 }),
8416 )
8417 })
8418 .into_any(),
8419 )
8420 }
8421
8422 fn render_edit_prediction_cursor_popover_preview(
8423 &self,
8424 completion: &InlineCompletionState,
8425 cursor_point: Point,
8426 style: &EditorStyle,
8427 cx: &mut Context<Editor>,
8428 ) -> Option<Div> {
8429 use text::ToPoint as _;
8430
8431 fn render_relative_row_jump(
8432 prefix: impl Into<String>,
8433 current_row: u32,
8434 target_row: u32,
8435 ) -> Div {
8436 let (row_diff, arrow) = if target_row < current_row {
8437 (current_row - target_row, IconName::ArrowUp)
8438 } else {
8439 (target_row - current_row, IconName::ArrowDown)
8440 };
8441
8442 h_flex()
8443 .child(
8444 Label::new(format!("{}{}", prefix.into(), row_diff))
8445 .color(Color::Muted)
8446 .size(LabelSize::Small),
8447 )
8448 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8449 }
8450
8451 match &completion.completion {
8452 InlineCompletion::Move {
8453 target, snapshot, ..
8454 } => Some(
8455 h_flex()
8456 .px_2()
8457 .gap_2()
8458 .flex_1()
8459 .child(
8460 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8461 Icon::new(IconName::ZedPredictDown)
8462 } else {
8463 Icon::new(IconName::ZedPredictUp)
8464 },
8465 )
8466 .child(Label::new("Jump to Edit")),
8467 ),
8468
8469 InlineCompletion::Edit {
8470 edits,
8471 edit_preview,
8472 snapshot,
8473 display_mode: _,
8474 } => {
8475 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8476
8477 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8478 &snapshot,
8479 &edits,
8480 edit_preview.as_ref()?,
8481 true,
8482 cx,
8483 )
8484 .first_line_preview();
8485
8486 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8487 .with_default_highlights(&style.text, highlighted_edits.highlights);
8488
8489 let preview = h_flex()
8490 .gap_1()
8491 .min_w_16()
8492 .child(styled_text)
8493 .when(has_more_lines, |parent| parent.child("…"));
8494
8495 let left = if first_edit_row != cursor_point.row {
8496 render_relative_row_jump("", cursor_point.row, first_edit_row)
8497 .into_any_element()
8498 } else {
8499 Icon::new(IconName::ZedPredict).into_any_element()
8500 };
8501
8502 Some(
8503 h_flex()
8504 .h_full()
8505 .flex_1()
8506 .gap_2()
8507 .pr_1()
8508 .overflow_x_hidden()
8509 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8510 .child(left)
8511 .child(preview),
8512 )
8513 }
8514 }
8515 }
8516
8517 fn render_context_menu(
8518 &self,
8519 style: &EditorStyle,
8520 max_height_in_lines: u32,
8521 window: &mut Window,
8522 cx: &mut Context<Editor>,
8523 ) -> Option<AnyElement> {
8524 let menu = self.context_menu.borrow();
8525 let menu = menu.as_ref()?;
8526 if !menu.visible() {
8527 return None;
8528 };
8529 Some(menu.render(style, max_height_in_lines, window, cx))
8530 }
8531
8532 fn render_context_menu_aside(
8533 &mut self,
8534 max_size: Size<Pixels>,
8535 window: &mut Window,
8536 cx: &mut Context<Editor>,
8537 ) -> Option<AnyElement> {
8538 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8539 if menu.visible() {
8540 menu.render_aside(self, max_size, window, cx)
8541 } else {
8542 None
8543 }
8544 })
8545 }
8546
8547 fn hide_context_menu(
8548 &mut self,
8549 window: &mut Window,
8550 cx: &mut Context<Self>,
8551 ) -> Option<CodeContextMenu> {
8552 cx.notify();
8553 self.completion_tasks.clear();
8554 let context_menu = self.context_menu.borrow_mut().take();
8555 self.stale_inline_completion_in_menu.take();
8556 self.update_visible_inline_completion(window, cx);
8557 context_menu
8558 }
8559
8560 fn show_snippet_choices(
8561 &mut self,
8562 choices: &Vec<String>,
8563 selection: Range<Anchor>,
8564 cx: &mut Context<Self>,
8565 ) {
8566 if selection.start.buffer_id.is_none() {
8567 return;
8568 }
8569 let buffer_id = selection.start.buffer_id.unwrap();
8570 let buffer = self.buffer().read(cx).buffer(buffer_id);
8571 let id = post_inc(&mut self.next_completion_id);
8572 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8573
8574 if let Some(buffer) = buffer {
8575 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8576 CompletionsMenu::new_snippet_choices(
8577 id,
8578 true,
8579 choices,
8580 selection,
8581 buffer,
8582 snippet_sort_order,
8583 ),
8584 ));
8585 }
8586 }
8587
8588 pub fn insert_snippet(
8589 &mut self,
8590 insertion_ranges: &[Range<usize>],
8591 snippet: Snippet,
8592 window: &mut Window,
8593 cx: &mut Context<Self>,
8594 ) -> Result<()> {
8595 struct Tabstop<T> {
8596 is_end_tabstop: bool,
8597 ranges: Vec<Range<T>>,
8598 choices: Option<Vec<String>>,
8599 }
8600
8601 let tabstops = self.buffer.update(cx, |buffer, cx| {
8602 let snippet_text: Arc<str> = snippet.text.clone().into();
8603 let edits = insertion_ranges
8604 .iter()
8605 .cloned()
8606 .map(|range| (range, snippet_text.clone()));
8607 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8608
8609 let snapshot = &*buffer.read(cx);
8610 let snippet = &snippet;
8611 snippet
8612 .tabstops
8613 .iter()
8614 .map(|tabstop| {
8615 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8616 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8617 });
8618 let mut tabstop_ranges = tabstop
8619 .ranges
8620 .iter()
8621 .flat_map(|tabstop_range| {
8622 let mut delta = 0_isize;
8623 insertion_ranges.iter().map(move |insertion_range| {
8624 let insertion_start = insertion_range.start as isize + delta;
8625 delta +=
8626 snippet.text.len() as isize - insertion_range.len() as isize;
8627
8628 let start = ((insertion_start + tabstop_range.start) as usize)
8629 .min(snapshot.len());
8630 let end = ((insertion_start + tabstop_range.end) as usize)
8631 .min(snapshot.len());
8632 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8633 })
8634 })
8635 .collect::<Vec<_>>();
8636 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8637
8638 Tabstop {
8639 is_end_tabstop,
8640 ranges: tabstop_ranges,
8641 choices: tabstop.choices.clone(),
8642 }
8643 })
8644 .collect::<Vec<_>>()
8645 });
8646 if let Some(tabstop) = tabstops.first() {
8647 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8648 s.select_ranges(tabstop.ranges.iter().cloned());
8649 });
8650
8651 if let Some(choices) = &tabstop.choices {
8652 if let Some(selection) = tabstop.ranges.first() {
8653 self.show_snippet_choices(choices, selection.clone(), cx)
8654 }
8655 }
8656
8657 // If we're already at the last tabstop and it's at the end of the snippet,
8658 // we're done, we don't need to keep the state around.
8659 if !tabstop.is_end_tabstop {
8660 let choices = tabstops
8661 .iter()
8662 .map(|tabstop| tabstop.choices.clone())
8663 .collect();
8664
8665 let ranges = tabstops
8666 .into_iter()
8667 .map(|tabstop| tabstop.ranges)
8668 .collect::<Vec<_>>();
8669
8670 self.snippet_stack.push(SnippetState {
8671 active_index: 0,
8672 ranges,
8673 choices,
8674 });
8675 }
8676
8677 // Check whether the just-entered snippet ends with an auto-closable bracket.
8678 if self.autoclose_regions.is_empty() {
8679 let snapshot = self.buffer.read(cx).snapshot(cx);
8680 for selection in &mut self.selections.all::<Point>(cx) {
8681 let selection_head = selection.head();
8682 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8683 continue;
8684 };
8685
8686 let mut bracket_pair = None;
8687 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8688 let prev_chars = snapshot
8689 .reversed_chars_at(selection_head)
8690 .collect::<String>();
8691 for (pair, enabled) in scope.brackets() {
8692 if enabled
8693 && pair.close
8694 && prev_chars.starts_with(pair.start.as_str())
8695 && next_chars.starts_with(pair.end.as_str())
8696 {
8697 bracket_pair = Some(pair.clone());
8698 break;
8699 }
8700 }
8701 if let Some(pair) = bracket_pair {
8702 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8703 let autoclose_enabled =
8704 self.use_autoclose && snapshot_settings.use_autoclose;
8705 if autoclose_enabled {
8706 let start = snapshot.anchor_after(selection_head);
8707 let end = snapshot.anchor_after(selection_head);
8708 self.autoclose_regions.push(AutocloseRegion {
8709 selection_id: selection.id,
8710 range: start..end,
8711 pair,
8712 });
8713 }
8714 }
8715 }
8716 }
8717 }
8718 Ok(())
8719 }
8720
8721 pub fn move_to_next_snippet_tabstop(
8722 &mut self,
8723 window: &mut Window,
8724 cx: &mut Context<Self>,
8725 ) -> bool {
8726 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8727 }
8728
8729 pub fn move_to_prev_snippet_tabstop(
8730 &mut self,
8731 window: &mut Window,
8732 cx: &mut Context<Self>,
8733 ) -> bool {
8734 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8735 }
8736
8737 pub fn move_to_snippet_tabstop(
8738 &mut self,
8739 bias: Bias,
8740 window: &mut Window,
8741 cx: &mut Context<Self>,
8742 ) -> bool {
8743 if let Some(mut snippet) = self.snippet_stack.pop() {
8744 match bias {
8745 Bias::Left => {
8746 if snippet.active_index > 0 {
8747 snippet.active_index -= 1;
8748 } else {
8749 self.snippet_stack.push(snippet);
8750 return false;
8751 }
8752 }
8753 Bias::Right => {
8754 if snippet.active_index + 1 < snippet.ranges.len() {
8755 snippet.active_index += 1;
8756 } else {
8757 self.snippet_stack.push(snippet);
8758 return false;
8759 }
8760 }
8761 }
8762 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8763 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8764 s.select_anchor_ranges(current_ranges.iter().cloned())
8765 });
8766
8767 if let Some(choices) = &snippet.choices[snippet.active_index] {
8768 if let Some(selection) = current_ranges.first() {
8769 self.show_snippet_choices(&choices, selection.clone(), cx);
8770 }
8771 }
8772
8773 // If snippet state is not at the last tabstop, push it back on the stack
8774 if snippet.active_index + 1 < snippet.ranges.len() {
8775 self.snippet_stack.push(snippet);
8776 }
8777 return true;
8778 }
8779 }
8780
8781 false
8782 }
8783
8784 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8785 self.transact(window, cx, |this, window, cx| {
8786 this.select_all(&SelectAll, window, cx);
8787 this.insert("", window, cx);
8788 });
8789 }
8790
8791 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8792 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8793 self.transact(window, cx, |this, window, cx| {
8794 this.select_autoclose_pair(window, cx);
8795 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8796 if !this.linked_edit_ranges.is_empty() {
8797 let selections = this.selections.all::<MultiBufferPoint>(cx);
8798 let snapshot = this.buffer.read(cx).snapshot(cx);
8799
8800 for selection in selections.iter() {
8801 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8802 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8803 if selection_start.buffer_id != selection_end.buffer_id {
8804 continue;
8805 }
8806 if let Some(ranges) =
8807 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8808 {
8809 for (buffer, entries) in ranges {
8810 linked_ranges.entry(buffer).or_default().extend(entries);
8811 }
8812 }
8813 }
8814 }
8815
8816 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8817 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8818 for selection in &mut selections {
8819 if selection.is_empty() {
8820 let old_head = selection.head();
8821 let mut new_head =
8822 movement::left(&display_map, old_head.to_display_point(&display_map))
8823 .to_point(&display_map);
8824 if let Some((buffer, line_buffer_range)) = display_map
8825 .buffer_snapshot
8826 .buffer_line_for_row(MultiBufferRow(old_head.row))
8827 {
8828 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8829 let indent_len = match indent_size.kind {
8830 IndentKind::Space => {
8831 buffer.settings_at(line_buffer_range.start, cx).tab_size
8832 }
8833 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8834 };
8835 if old_head.column <= indent_size.len && old_head.column > 0 {
8836 let indent_len = indent_len.get();
8837 new_head = cmp::min(
8838 new_head,
8839 MultiBufferPoint::new(
8840 old_head.row,
8841 ((old_head.column - 1) / indent_len) * indent_len,
8842 ),
8843 );
8844 }
8845 }
8846
8847 selection.set_head(new_head, SelectionGoal::None);
8848 }
8849 }
8850
8851 this.signature_help_state.set_backspace_pressed(true);
8852 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8853 s.select(selections)
8854 });
8855 this.insert("", window, cx);
8856 let empty_str: Arc<str> = Arc::from("");
8857 for (buffer, edits) in linked_ranges {
8858 let snapshot = buffer.read(cx).snapshot();
8859 use text::ToPoint as TP;
8860
8861 let edits = edits
8862 .into_iter()
8863 .map(|range| {
8864 let end_point = TP::to_point(&range.end, &snapshot);
8865 let mut start_point = TP::to_point(&range.start, &snapshot);
8866
8867 if end_point == start_point {
8868 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8869 .saturating_sub(1);
8870 start_point =
8871 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8872 };
8873
8874 (start_point..end_point, empty_str.clone())
8875 })
8876 .sorted_by_key(|(range, _)| range.start)
8877 .collect::<Vec<_>>();
8878 buffer.update(cx, |this, cx| {
8879 this.edit(edits, None, cx);
8880 })
8881 }
8882 this.refresh_inline_completion(true, false, window, cx);
8883 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8884 });
8885 }
8886
8887 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8888 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8889 self.transact(window, cx, |this, window, cx| {
8890 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8891 s.move_with(|map, selection| {
8892 if selection.is_empty() {
8893 let cursor = movement::right(map, selection.head());
8894 selection.end = cursor;
8895 selection.reversed = true;
8896 selection.goal = SelectionGoal::None;
8897 }
8898 })
8899 });
8900 this.insert("", window, cx);
8901 this.refresh_inline_completion(true, false, window, cx);
8902 });
8903 }
8904
8905 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8906 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8907 if self.move_to_prev_snippet_tabstop(window, cx) {
8908 return;
8909 }
8910 self.outdent(&Outdent, window, cx);
8911 }
8912
8913 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8914 if self.move_to_next_snippet_tabstop(window, cx) {
8915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8916 return;
8917 }
8918 if self.read_only(cx) {
8919 return;
8920 }
8921 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8922 let mut selections = self.selections.all_adjusted(cx);
8923 let buffer = self.buffer.read(cx);
8924 let snapshot = buffer.snapshot(cx);
8925 let rows_iter = selections.iter().map(|s| s.head().row);
8926 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8927
8928 let has_some_cursor_in_whitespace = selections
8929 .iter()
8930 .filter(|selection| selection.is_empty())
8931 .any(|selection| {
8932 let cursor = selection.head();
8933 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8934 cursor.column < current_indent.len
8935 });
8936
8937 let mut edits = Vec::new();
8938 let mut prev_edited_row = 0;
8939 let mut row_delta = 0;
8940 for selection in &mut selections {
8941 if selection.start.row != prev_edited_row {
8942 row_delta = 0;
8943 }
8944 prev_edited_row = selection.end.row;
8945
8946 // If the selection is non-empty, then increase the indentation of the selected lines.
8947 if !selection.is_empty() {
8948 row_delta =
8949 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8950 continue;
8951 }
8952
8953 let cursor = selection.head();
8954 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8955 if let Some(suggested_indent) =
8956 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8957 {
8958 // Don't do anything if already at suggested indent
8959 // and there is any other cursor which is not
8960 if has_some_cursor_in_whitespace
8961 && cursor.column == current_indent.len
8962 && current_indent.len == suggested_indent.len
8963 {
8964 continue;
8965 }
8966
8967 // Adjust line and move cursor to suggested indent
8968 // if cursor is not at suggested indent
8969 if cursor.column < suggested_indent.len
8970 && cursor.column <= current_indent.len
8971 && current_indent.len <= suggested_indent.len
8972 {
8973 selection.start = Point::new(cursor.row, suggested_indent.len);
8974 selection.end = selection.start;
8975 if row_delta == 0 {
8976 edits.extend(Buffer::edit_for_indent_size_adjustment(
8977 cursor.row,
8978 current_indent,
8979 suggested_indent,
8980 ));
8981 row_delta = suggested_indent.len - current_indent.len;
8982 }
8983 continue;
8984 }
8985
8986 // If current indent is more than suggested indent
8987 // only move cursor to current indent and skip indent
8988 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8989 selection.start = Point::new(cursor.row, current_indent.len);
8990 selection.end = selection.start;
8991 continue;
8992 }
8993 }
8994
8995 // Otherwise, insert a hard or soft tab.
8996 let settings = buffer.language_settings_at(cursor, cx);
8997 let tab_size = if settings.hard_tabs {
8998 IndentSize::tab()
8999 } else {
9000 let tab_size = settings.tab_size.get();
9001 let indent_remainder = snapshot
9002 .text_for_range(Point::new(cursor.row, 0)..cursor)
9003 .flat_map(str::chars)
9004 .fold(row_delta % tab_size, |counter: u32, c| {
9005 if c == '\t' {
9006 0
9007 } else {
9008 (counter + 1) % tab_size
9009 }
9010 });
9011
9012 let chars_to_next_tab_stop = tab_size - indent_remainder;
9013 IndentSize::spaces(chars_to_next_tab_stop)
9014 };
9015 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9016 selection.end = selection.start;
9017 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9018 row_delta += tab_size.len;
9019 }
9020
9021 self.transact(window, cx, |this, window, cx| {
9022 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9023 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9024 s.select(selections)
9025 });
9026 this.refresh_inline_completion(true, false, window, cx);
9027 });
9028 }
9029
9030 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9031 if self.read_only(cx) {
9032 return;
9033 }
9034 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9035 let mut selections = self.selections.all::<Point>(cx);
9036 let mut prev_edited_row = 0;
9037 let mut row_delta = 0;
9038 let mut edits = Vec::new();
9039 let buffer = self.buffer.read(cx);
9040 let snapshot = buffer.snapshot(cx);
9041 for selection in &mut selections {
9042 if selection.start.row != prev_edited_row {
9043 row_delta = 0;
9044 }
9045 prev_edited_row = selection.end.row;
9046
9047 row_delta =
9048 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9049 }
9050
9051 self.transact(window, cx, |this, window, cx| {
9052 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9053 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9054 s.select(selections)
9055 });
9056 });
9057 }
9058
9059 fn indent_selection(
9060 buffer: &MultiBuffer,
9061 snapshot: &MultiBufferSnapshot,
9062 selection: &mut Selection<Point>,
9063 edits: &mut Vec<(Range<Point>, String)>,
9064 delta_for_start_row: u32,
9065 cx: &App,
9066 ) -> u32 {
9067 let settings = buffer.language_settings_at(selection.start, cx);
9068 let tab_size = settings.tab_size.get();
9069 let indent_kind = if settings.hard_tabs {
9070 IndentKind::Tab
9071 } else {
9072 IndentKind::Space
9073 };
9074 let mut start_row = selection.start.row;
9075 let mut end_row = selection.end.row + 1;
9076
9077 // If a selection ends at the beginning of a line, don't indent
9078 // that last line.
9079 if selection.end.column == 0 && selection.end.row > selection.start.row {
9080 end_row -= 1;
9081 }
9082
9083 // Avoid re-indenting a row that has already been indented by a
9084 // previous selection, but still update this selection's column
9085 // to reflect that indentation.
9086 if delta_for_start_row > 0 {
9087 start_row += 1;
9088 selection.start.column += delta_for_start_row;
9089 if selection.end.row == selection.start.row {
9090 selection.end.column += delta_for_start_row;
9091 }
9092 }
9093
9094 let mut delta_for_end_row = 0;
9095 let has_multiple_rows = start_row + 1 != end_row;
9096 for row in start_row..end_row {
9097 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9098 let indent_delta = match (current_indent.kind, indent_kind) {
9099 (IndentKind::Space, IndentKind::Space) => {
9100 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9101 IndentSize::spaces(columns_to_next_tab_stop)
9102 }
9103 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9104 (_, IndentKind::Tab) => IndentSize::tab(),
9105 };
9106
9107 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9108 0
9109 } else {
9110 selection.start.column
9111 };
9112 let row_start = Point::new(row, start);
9113 edits.push((
9114 row_start..row_start,
9115 indent_delta.chars().collect::<String>(),
9116 ));
9117
9118 // Update this selection's endpoints to reflect the indentation.
9119 if row == selection.start.row {
9120 selection.start.column += indent_delta.len;
9121 }
9122 if row == selection.end.row {
9123 selection.end.column += indent_delta.len;
9124 delta_for_end_row = indent_delta.len;
9125 }
9126 }
9127
9128 if selection.start.row == selection.end.row {
9129 delta_for_start_row + delta_for_end_row
9130 } else {
9131 delta_for_end_row
9132 }
9133 }
9134
9135 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9136 if self.read_only(cx) {
9137 return;
9138 }
9139 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9140 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9141 let selections = self.selections.all::<Point>(cx);
9142 let mut deletion_ranges = Vec::new();
9143 let mut last_outdent = None;
9144 {
9145 let buffer = self.buffer.read(cx);
9146 let snapshot = buffer.snapshot(cx);
9147 for selection in &selections {
9148 let settings = buffer.language_settings_at(selection.start, cx);
9149 let tab_size = settings.tab_size.get();
9150 let mut rows = selection.spanned_rows(false, &display_map);
9151
9152 // Avoid re-outdenting a row that has already been outdented by a
9153 // previous selection.
9154 if let Some(last_row) = last_outdent {
9155 if last_row == rows.start {
9156 rows.start = rows.start.next_row();
9157 }
9158 }
9159 let has_multiple_rows = rows.len() > 1;
9160 for row in rows.iter_rows() {
9161 let indent_size = snapshot.indent_size_for_line(row);
9162 if indent_size.len > 0 {
9163 let deletion_len = match indent_size.kind {
9164 IndentKind::Space => {
9165 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9166 if columns_to_prev_tab_stop == 0 {
9167 tab_size
9168 } else {
9169 columns_to_prev_tab_stop
9170 }
9171 }
9172 IndentKind::Tab => 1,
9173 };
9174 let start = if has_multiple_rows
9175 || deletion_len > selection.start.column
9176 || indent_size.len < selection.start.column
9177 {
9178 0
9179 } else {
9180 selection.start.column - deletion_len
9181 };
9182 deletion_ranges.push(
9183 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9184 );
9185 last_outdent = Some(row);
9186 }
9187 }
9188 }
9189 }
9190
9191 self.transact(window, cx, |this, window, cx| {
9192 this.buffer.update(cx, |buffer, cx| {
9193 let empty_str: Arc<str> = Arc::default();
9194 buffer.edit(
9195 deletion_ranges
9196 .into_iter()
9197 .map(|range| (range, empty_str.clone())),
9198 None,
9199 cx,
9200 );
9201 });
9202 let selections = this.selections.all::<usize>(cx);
9203 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9204 s.select(selections)
9205 });
9206 });
9207 }
9208
9209 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9210 if self.read_only(cx) {
9211 return;
9212 }
9213 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9214 let selections = self
9215 .selections
9216 .all::<usize>(cx)
9217 .into_iter()
9218 .map(|s| s.range());
9219
9220 self.transact(window, cx, |this, window, cx| {
9221 this.buffer.update(cx, |buffer, cx| {
9222 buffer.autoindent_ranges(selections, cx);
9223 });
9224 let selections = this.selections.all::<usize>(cx);
9225 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9226 s.select(selections)
9227 });
9228 });
9229 }
9230
9231 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9232 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9234 let selections = self.selections.all::<Point>(cx);
9235
9236 let mut new_cursors = Vec::new();
9237 let mut edit_ranges = Vec::new();
9238 let mut selections = selections.iter().peekable();
9239 while let Some(selection) = selections.next() {
9240 let mut rows = selection.spanned_rows(false, &display_map);
9241 let goal_display_column = selection.head().to_display_point(&display_map).column();
9242
9243 // Accumulate contiguous regions of rows that we want to delete.
9244 while let Some(next_selection) = selections.peek() {
9245 let next_rows = next_selection.spanned_rows(false, &display_map);
9246 if next_rows.start <= rows.end {
9247 rows.end = next_rows.end;
9248 selections.next().unwrap();
9249 } else {
9250 break;
9251 }
9252 }
9253
9254 let buffer = &display_map.buffer_snapshot;
9255 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9256 let edit_end;
9257 let cursor_buffer_row;
9258 if buffer.max_point().row >= rows.end.0 {
9259 // If there's a line after the range, delete the \n from the end of the row range
9260 // and position the cursor on the next line.
9261 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9262 cursor_buffer_row = rows.end;
9263 } else {
9264 // If there isn't a line after the range, delete the \n from the line before the
9265 // start of the row range and position the cursor there.
9266 edit_start = edit_start.saturating_sub(1);
9267 edit_end = buffer.len();
9268 cursor_buffer_row = rows.start.previous_row();
9269 }
9270
9271 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9272 *cursor.column_mut() =
9273 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9274
9275 new_cursors.push((
9276 selection.id,
9277 buffer.anchor_after(cursor.to_point(&display_map)),
9278 ));
9279 edit_ranges.push(edit_start..edit_end);
9280 }
9281
9282 self.transact(window, cx, |this, window, cx| {
9283 let buffer = this.buffer.update(cx, |buffer, cx| {
9284 let empty_str: Arc<str> = Arc::default();
9285 buffer.edit(
9286 edit_ranges
9287 .into_iter()
9288 .map(|range| (range, empty_str.clone())),
9289 None,
9290 cx,
9291 );
9292 buffer.snapshot(cx)
9293 });
9294 let new_selections = new_cursors
9295 .into_iter()
9296 .map(|(id, cursor)| {
9297 let cursor = cursor.to_point(&buffer);
9298 Selection {
9299 id,
9300 start: cursor,
9301 end: cursor,
9302 reversed: false,
9303 goal: SelectionGoal::None,
9304 }
9305 })
9306 .collect();
9307
9308 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9309 s.select(new_selections);
9310 });
9311 });
9312 }
9313
9314 pub fn join_lines_impl(
9315 &mut self,
9316 insert_whitespace: bool,
9317 window: &mut Window,
9318 cx: &mut Context<Self>,
9319 ) {
9320 if self.read_only(cx) {
9321 return;
9322 }
9323 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9324 for selection in self.selections.all::<Point>(cx) {
9325 let start = MultiBufferRow(selection.start.row);
9326 // Treat single line selections as if they include the next line. Otherwise this action
9327 // would do nothing for single line selections individual cursors.
9328 let end = if selection.start.row == selection.end.row {
9329 MultiBufferRow(selection.start.row + 1)
9330 } else {
9331 MultiBufferRow(selection.end.row)
9332 };
9333
9334 if let Some(last_row_range) = row_ranges.last_mut() {
9335 if start <= last_row_range.end {
9336 last_row_range.end = end;
9337 continue;
9338 }
9339 }
9340 row_ranges.push(start..end);
9341 }
9342
9343 let snapshot = self.buffer.read(cx).snapshot(cx);
9344 let mut cursor_positions = Vec::new();
9345 for row_range in &row_ranges {
9346 let anchor = snapshot.anchor_before(Point::new(
9347 row_range.end.previous_row().0,
9348 snapshot.line_len(row_range.end.previous_row()),
9349 ));
9350 cursor_positions.push(anchor..anchor);
9351 }
9352
9353 self.transact(window, cx, |this, window, cx| {
9354 for row_range in row_ranges.into_iter().rev() {
9355 for row in row_range.iter_rows().rev() {
9356 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9357 let next_line_row = row.next_row();
9358 let indent = snapshot.indent_size_for_line(next_line_row);
9359 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9360
9361 let replace =
9362 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9363 " "
9364 } else {
9365 ""
9366 };
9367
9368 this.buffer.update(cx, |buffer, cx| {
9369 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9370 });
9371 }
9372 }
9373
9374 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9375 s.select_anchor_ranges(cursor_positions)
9376 });
9377 });
9378 }
9379
9380 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9381 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9382 self.join_lines_impl(true, window, cx);
9383 }
9384
9385 pub fn sort_lines_case_sensitive(
9386 &mut self,
9387 _: &SortLinesCaseSensitive,
9388 window: &mut Window,
9389 cx: &mut Context<Self>,
9390 ) {
9391 self.manipulate_lines(window, cx, |lines| lines.sort())
9392 }
9393
9394 pub fn sort_lines_case_insensitive(
9395 &mut self,
9396 _: &SortLinesCaseInsensitive,
9397 window: &mut Window,
9398 cx: &mut Context<Self>,
9399 ) {
9400 self.manipulate_lines(window, cx, |lines| {
9401 lines.sort_by_key(|line| line.to_lowercase())
9402 })
9403 }
9404
9405 pub fn unique_lines_case_insensitive(
9406 &mut self,
9407 _: &UniqueLinesCaseInsensitive,
9408 window: &mut Window,
9409 cx: &mut Context<Self>,
9410 ) {
9411 self.manipulate_lines(window, cx, |lines| {
9412 let mut seen = HashSet::default();
9413 lines.retain(|line| seen.insert(line.to_lowercase()));
9414 })
9415 }
9416
9417 pub fn unique_lines_case_sensitive(
9418 &mut self,
9419 _: &UniqueLinesCaseSensitive,
9420 window: &mut Window,
9421 cx: &mut Context<Self>,
9422 ) {
9423 self.manipulate_lines(window, cx, |lines| {
9424 let mut seen = HashSet::default();
9425 lines.retain(|line| seen.insert(*line));
9426 })
9427 }
9428
9429 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9430 let Some(project) = self.project.clone() else {
9431 return;
9432 };
9433 self.reload(project, window, cx)
9434 .detach_and_notify_err(window, cx);
9435 }
9436
9437 pub fn restore_file(
9438 &mut self,
9439 _: &::git::RestoreFile,
9440 window: &mut Window,
9441 cx: &mut Context<Self>,
9442 ) {
9443 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9444 let mut buffer_ids = HashSet::default();
9445 let snapshot = self.buffer().read(cx).snapshot(cx);
9446 for selection in self.selections.all::<usize>(cx) {
9447 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9448 }
9449
9450 let buffer = self.buffer().read(cx);
9451 let ranges = buffer_ids
9452 .into_iter()
9453 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9454 .collect::<Vec<_>>();
9455
9456 self.restore_hunks_in_ranges(ranges, window, cx);
9457 }
9458
9459 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9460 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9461 let selections = self
9462 .selections
9463 .all(cx)
9464 .into_iter()
9465 .map(|s| s.range())
9466 .collect();
9467 self.restore_hunks_in_ranges(selections, window, cx);
9468 }
9469
9470 pub fn restore_hunks_in_ranges(
9471 &mut self,
9472 ranges: Vec<Range<Point>>,
9473 window: &mut Window,
9474 cx: &mut Context<Editor>,
9475 ) {
9476 let mut revert_changes = HashMap::default();
9477 let chunk_by = self
9478 .snapshot(window, cx)
9479 .hunks_for_ranges(ranges)
9480 .into_iter()
9481 .chunk_by(|hunk| hunk.buffer_id);
9482 for (buffer_id, hunks) in &chunk_by {
9483 let hunks = hunks.collect::<Vec<_>>();
9484 for hunk in &hunks {
9485 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9486 }
9487 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9488 }
9489 drop(chunk_by);
9490 if !revert_changes.is_empty() {
9491 self.transact(window, cx, |editor, window, cx| {
9492 editor.restore(revert_changes, window, cx);
9493 });
9494 }
9495 }
9496
9497 pub fn open_active_item_in_terminal(
9498 &mut self,
9499 _: &OpenInTerminal,
9500 window: &mut Window,
9501 cx: &mut Context<Self>,
9502 ) {
9503 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9504 let project_path = buffer.read(cx).project_path(cx)?;
9505 let project = self.project.as_ref()?.read(cx);
9506 let entry = project.entry_for_path(&project_path, cx)?;
9507 let parent = match &entry.canonical_path {
9508 Some(canonical_path) => canonical_path.to_path_buf(),
9509 None => project.absolute_path(&project_path, cx)?,
9510 }
9511 .parent()?
9512 .to_path_buf();
9513 Some(parent)
9514 }) {
9515 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9516 }
9517 }
9518
9519 fn set_breakpoint_context_menu(
9520 &mut self,
9521 display_row: DisplayRow,
9522 position: Option<Anchor>,
9523 clicked_point: gpui::Point<Pixels>,
9524 window: &mut Window,
9525 cx: &mut Context<Self>,
9526 ) {
9527 if !cx.has_flag::<DebuggerFeatureFlag>() {
9528 return;
9529 }
9530 let source = self
9531 .buffer
9532 .read(cx)
9533 .snapshot(cx)
9534 .anchor_before(Point::new(display_row.0, 0u32));
9535
9536 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9537
9538 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9539 self,
9540 source,
9541 clicked_point,
9542 context_menu,
9543 window,
9544 cx,
9545 );
9546 }
9547
9548 fn add_edit_breakpoint_block(
9549 &mut self,
9550 anchor: Anchor,
9551 breakpoint: &Breakpoint,
9552 edit_action: BreakpointPromptEditAction,
9553 window: &mut Window,
9554 cx: &mut Context<Self>,
9555 ) {
9556 let weak_editor = cx.weak_entity();
9557 let bp_prompt = cx.new(|cx| {
9558 BreakpointPromptEditor::new(
9559 weak_editor,
9560 anchor,
9561 breakpoint.clone(),
9562 edit_action,
9563 window,
9564 cx,
9565 )
9566 });
9567
9568 let height = bp_prompt.update(cx, |this, cx| {
9569 this.prompt
9570 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9571 });
9572 let cloned_prompt = bp_prompt.clone();
9573 let blocks = vec![BlockProperties {
9574 style: BlockStyle::Sticky,
9575 placement: BlockPlacement::Above(anchor),
9576 height: Some(height),
9577 render: Arc::new(move |cx| {
9578 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9579 cloned_prompt.clone().into_any_element()
9580 }),
9581 priority: 0,
9582 render_in_minimap: true,
9583 }];
9584
9585 let focus_handle = bp_prompt.focus_handle(cx);
9586 window.focus(&focus_handle);
9587
9588 let block_ids = self.insert_blocks(blocks, None, cx);
9589 bp_prompt.update(cx, |prompt, _| {
9590 prompt.add_block_ids(block_ids);
9591 });
9592 }
9593
9594 pub(crate) fn breakpoint_at_row(
9595 &self,
9596 row: u32,
9597 window: &mut Window,
9598 cx: &mut Context<Self>,
9599 ) -> Option<(Anchor, Breakpoint)> {
9600 let snapshot = self.snapshot(window, cx);
9601 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9602
9603 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9604 }
9605
9606 pub(crate) fn breakpoint_at_anchor(
9607 &self,
9608 breakpoint_position: Anchor,
9609 snapshot: &EditorSnapshot,
9610 cx: &mut Context<Self>,
9611 ) -> Option<(Anchor, Breakpoint)> {
9612 let project = self.project.clone()?;
9613
9614 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9615 snapshot
9616 .buffer_snapshot
9617 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9618 })?;
9619
9620 let enclosing_excerpt = breakpoint_position.excerpt_id;
9621 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9622 let buffer_snapshot = buffer.read(cx).snapshot();
9623
9624 let row = buffer_snapshot
9625 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9626 .row;
9627
9628 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9629 let anchor_end = snapshot
9630 .buffer_snapshot
9631 .anchor_after(Point::new(row, line_len));
9632
9633 let bp = self
9634 .breakpoint_store
9635 .as_ref()?
9636 .read_with(cx, |breakpoint_store, cx| {
9637 breakpoint_store
9638 .breakpoints(
9639 &buffer,
9640 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9641 &buffer_snapshot,
9642 cx,
9643 )
9644 .next()
9645 .and_then(|(bp, _)| {
9646 let breakpoint_row = buffer_snapshot
9647 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9648 .row;
9649
9650 if breakpoint_row == row {
9651 snapshot
9652 .buffer_snapshot
9653 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9654 .map(|position| (position, bp.bp.clone()))
9655 } else {
9656 None
9657 }
9658 })
9659 });
9660 bp
9661 }
9662
9663 pub fn edit_log_breakpoint(
9664 &mut self,
9665 _: &EditLogBreakpoint,
9666 window: &mut Window,
9667 cx: &mut Context<Self>,
9668 ) {
9669 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9670 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9671 message: None,
9672 state: BreakpointState::Enabled,
9673 condition: None,
9674 hit_condition: None,
9675 });
9676
9677 self.add_edit_breakpoint_block(
9678 anchor,
9679 &breakpoint,
9680 BreakpointPromptEditAction::Log,
9681 window,
9682 cx,
9683 );
9684 }
9685 }
9686
9687 fn breakpoints_at_cursors(
9688 &self,
9689 window: &mut Window,
9690 cx: &mut Context<Self>,
9691 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9692 let snapshot = self.snapshot(window, cx);
9693 let cursors = self
9694 .selections
9695 .disjoint_anchors()
9696 .into_iter()
9697 .map(|selection| {
9698 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9699
9700 let breakpoint_position = self
9701 .breakpoint_at_row(cursor_position.row, window, cx)
9702 .map(|bp| bp.0)
9703 .unwrap_or_else(|| {
9704 snapshot
9705 .display_snapshot
9706 .buffer_snapshot
9707 .anchor_after(Point::new(cursor_position.row, 0))
9708 });
9709
9710 let breakpoint = self
9711 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9712 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9713
9714 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9715 })
9716 // 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.
9717 .collect::<HashMap<Anchor, _>>();
9718
9719 cursors.into_iter().collect()
9720 }
9721
9722 pub fn enable_breakpoint(
9723 &mut self,
9724 _: &crate::actions::EnableBreakpoint,
9725 window: &mut Window,
9726 cx: &mut Context<Self>,
9727 ) {
9728 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9729 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9730 continue;
9731 };
9732 self.edit_breakpoint_at_anchor(
9733 anchor,
9734 breakpoint,
9735 BreakpointEditAction::InvertState,
9736 cx,
9737 );
9738 }
9739 }
9740
9741 pub fn disable_breakpoint(
9742 &mut self,
9743 _: &crate::actions::DisableBreakpoint,
9744 window: &mut Window,
9745 cx: &mut Context<Self>,
9746 ) {
9747 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9748 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9749 continue;
9750 };
9751 self.edit_breakpoint_at_anchor(
9752 anchor,
9753 breakpoint,
9754 BreakpointEditAction::InvertState,
9755 cx,
9756 );
9757 }
9758 }
9759
9760 pub fn toggle_breakpoint(
9761 &mut self,
9762 _: &crate::actions::ToggleBreakpoint,
9763 window: &mut Window,
9764 cx: &mut Context<Self>,
9765 ) {
9766 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9767 if let Some(breakpoint) = breakpoint {
9768 self.edit_breakpoint_at_anchor(
9769 anchor,
9770 breakpoint,
9771 BreakpointEditAction::Toggle,
9772 cx,
9773 );
9774 } else {
9775 self.edit_breakpoint_at_anchor(
9776 anchor,
9777 Breakpoint::new_standard(),
9778 BreakpointEditAction::Toggle,
9779 cx,
9780 );
9781 }
9782 }
9783 }
9784
9785 pub fn edit_breakpoint_at_anchor(
9786 &mut self,
9787 breakpoint_position: Anchor,
9788 breakpoint: Breakpoint,
9789 edit_action: BreakpointEditAction,
9790 cx: &mut Context<Self>,
9791 ) {
9792 let Some(breakpoint_store) = &self.breakpoint_store else {
9793 return;
9794 };
9795
9796 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9797 if breakpoint_position == Anchor::min() {
9798 self.buffer()
9799 .read(cx)
9800 .excerpt_buffer_ids()
9801 .into_iter()
9802 .next()
9803 } else {
9804 None
9805 }
9806 }) else {
9807 return;
9808 };
9809
9810 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9811 return;
9812 };
9813
9814 breakpoint_store.update(cx, |breakpoint_store, cx| {
9815 breakpoint_store.toggle_breakpoint(
9816 buffer,
9817 BreakpointWithPosition {
9818 position: breakpoint_position.text_anchor,
9819 bp: breakpoint,
9820 },
9821 edit_action,
9822 cx,
9823 );
9824 });
9825
9826 cx.notify();
9827 }
9828
9829 #[cfg(any(test, feature = "test-support"))]
9830 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9831 self.breakpoint_store.clone()
9832 }
9833
9834 pub fn prepare_restore_change(
9835 &self,
9836 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9837 hunk: &MultiBufferDiffHunk,
9838 cx: &mut App,
9839 ) -> Option<()> {
9840 if hunk.is_created_file() {
9841 return None;
9842 }
9843 let buffer = self.buffer.read(cx);
9844 let diff = buffer.diff_for(hunk.buffer_id)?;
9845 let buffer = buffer.buffer(hunk.buffer_id)?;
9846 let buffer = buffer.read(cx);
9847 let original_text = diff
9848 .read(cx)
9849 .base_text()
9850 .as_rope()
9851 .slice(hunk.diff_base_byte_range.clone());
9852 let buffer_snapshot = buffer.snapshot();
9853 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9854 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9855 probe
9856 .0
9857 .start
9858 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9859 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9860 }) {
9861 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9862 Some(())
9863 } else {
9864 None
9865 }
9866 }
9867
9868 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9869 self.manipulate_lines(window, cx, |lines| lines.reverse())
9870 }
9871
9872 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9873 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9874 }
9875
9876 fn manipulate_lines<Fn>(
9877 &mut self,
9878 window: &mut Window,
9879 cx: &mut Context<Self>,
9880 mut callback: Fn,
9881 ) where
9882 Fn: FnMut(&mut Vec<&str>),
9883 {
9884 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9885
9886 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9887 let buffer = self.buffer.read(cx).snapshot(cx);
9888
9889 let mut edits = Vec::new();
9890
9891 let selections = self.selections.all::<Point>(cx);
9892 let mut selections = selections.iter().peekable();
9893 let mut contiguous_row_selections = Vec::new();
9894 let mut new_selections = Vec::new();
9895 let mut added_lines = 0;
9896 let mut removed_lines = 0;
9897
9898 while let Some(selection) = selections.next() {
9899 let (start_row, end_row) = consume_contiguous_rows(
9900 &mut contiguous_row_selections,
9901 selection,
9902 &display_map,
9903 &mut selections,
9904 );
9905
9906 let start_point = Point::new(start_row.0, 0);
9907 let end_point = Point::new(
9908 end_row.previous_row().0,
9909 buffer.line_len(end_row.previous_row()),
9910 );
9911 let text = buffer
9912 .text_for_range(start_point..end_point)
9913 .collect::<String>();
9914
9915 let mut lines = text.split('\n').collect_vec();
9916
9917 let lines_before = lines.len();
9918 callback(&mut lines);
9919 let lines_after = lines.len();
9920
9921 edits.push((start_point..end_point, lines.join("\n")));
9922
9923 // Selections must change based on added and removed line count
9924 let start_row =
9925 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9926 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9927 new_selections.push(Selection {
9928 id: selection.id,
9929 start: start_row,
9930 end: end_row,
9931 goal: SelectionGoal::None,
9932 reversed: selection.reversed,
9933 });
9934
9935 if lines_after > lines_before {
9936 added_lines += lines_after - lines_before;
9937 } else if lines_before > lines_after {
9938 removed_lines += lines_before - lines_after;
9939 }
9940 }
9941
9942 self.transact(window, cx, |this, window, cx| {
9943 let buffer = this.buffer.update(cx, |buffer, cx| {
9944 buffer.edit(edits, None, cx);
9945 buffer.snapshot(cx)
9946 });
9947
9948 // Recalculate offsets on newly edited buffer
9949 let new_selections = new_selections
9950 .iter()
9951 .map(|s| {
9952 let start_point = Point::new(s.start.0, 0);
9953 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9954 Selection {
9955 id: s.id,
9956 start: buffer.point_to_offset(start_point),
9957 end: buffer.point_to_offset(end_point),
9958 goal: s.goal,
9959 reversed: s.reversed,
9960 }
9961 })
9962 .collect();
9963
9964 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9965 s.select(new_selections);
9966 });
9967
9968 this.request_autoscroll(Autoscroll::fit(), cx);
9969 });
9970 }
9971
9972 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9973 self.manipulate_text(window, cx, |text| {
9974 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9975 if has_upper_case_characters {
9976 text.to_lowercase()
9977 } else {
9978 text.to_uppercase()
9979 }
9980 })
9981 }
9982
9983 pub fn convert_to_upper_case(
9984 &mut self,
9985 _: &ConvertToUpperCase,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) {
9989 self.manipulate_text(window, cx, |text| text.to_uppercase())
9990 }
9991
9992 pub fn convert_to_lower_case(
9993 &mut self,
9994 _: &ConvertToLowerCase,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 self.manipulate_text(window, cx, |text| text.to_lowercase())
9999 }
10000
10001 pub fn convert_to_title_case(
10002 &mut self,
10003 _: &ConvertToTitleCase,
10004 window: &mut Window,
10005 cx: &mut Context<Self>,
10006 ) {
10007 self.manipulate_text(window, cx, |text| {
10008 text.split('\n')
10009 .map(|line| line.to_case(Case::Title))
10010 .join("\n")
10011 })
10012 }
10013
10014 pub fn convert_to_snake_case(
10015 &mut self,
10016 _: &ConvertToSnakeCase,
10017 window: &mut Window,
10018 cx: &mut Context<Self>,
10019 ) {
10020 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10021 }
10022
10023 pub fn convert_to_kebab_case(
10024 &mut self,
10025 _: &ConvertToKebabCase,
10026 window: &mut Window,
10027 cx: &mut Context<Self>,
10028 ) {
10029 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10030 }
10031
10032 pub fn convert_to_upper_camel_case(
10033 &mut self,
10034 _: &ConvertToUpperCamelCase,
10035 window: &mut Window,
10036 cx: &mut Context<Self>,
10037 ) {
10038 self.manipulate_text(window, cx, |text| {
10039 text.split('\n')
10040 .map(|line| line.to_case(Case::UpperCamel))
10041 .join("\n")
10042 })
10043 }
10044
10045 pub fn convert_to_lower_camel_case(
10046 &mut self,
10047 _: &ConvertToLowerCamelCase,
10048 window: &mut Window,
10049 cx: &mut Context<Self>,
10050 ) {
10051 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10052 }
10053
10054 pub fn convert_to_opposite_case(
10055 &mut self,
10056 _: &ConvertToOppositeCase,
10057 window: &mut Window,
10058 cx: &mut Context<Self>,
10059 ) {
10060 self.manipulate_text(window, cx, |text| {
10061 text.chars()
10062 .fold(String::with_capacity(text.len()), |mut t, c| {
10063 if c.is_uppercase() {
10064 t.extend(c.to_lowercase());
10065 } else {
10066 t.extend(c.to_uppercase());
10067 }
10068 t
10069 })
10070 })
10071 }
10072
10073 pub fn convert_to_rot13(
10074 &mut self,
10075 _: &ConvertToRot13,
10076 window: &mut Window,
10077 cx: &mut Context<Self>,
10078 ) {
10079 self.manipulate_text(window, cx, |text| {
10080 text.chars()
10081 .map(|c| match c {
10082 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10083 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10084 _ => c,
10085 })
10086 .collect()
10087 })
10088 }
10089
10090 pub fn convert_to_rot47(
10091 &mut self,
10092 _: &ConvertToRot47,
10093 window: &mut Window,
10094 cx: &mut Context<Self>,
10095 ) {
10096 self.manipulate_text(window, cx, |text| {
10097 text.chars()
10098 .map(|c| {
10099 let code_point = c as u32;
10100 if code_point >= 33 && code_point <= 126 {
10101 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10102 }
10103 c
10104 })
10105 .collect()
10106 })
10107 }
10108
10109 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10110 where
10111 Fn: FnMut(&str) -> String,
10112 {
10113 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10114 let buffer = self.buffer.read(cx).snapshot(cx);
10115
10116 let mut new_selections = Vec::new();
10117 let mut edits = Vec::new();
10118 let mut selection_adjustment = 0i32;
10119
10120 for selection in self.selections.all::<usize>(cx) {
10121 let selection_is_empty = selection.is_empty();
10122
10123 let (start, end) = if selection_is_empty {
10124 let word_range = movement::surrounding_word(
10125 &display_map,
10126 selection.start.to_display_point(&display_map),
10127 );
10128 let start = word_range.start.to_offset(&display_map, Bias::Left);
10129 let end = word_range.end.to_offset(&display_map, Bias::Left);
10130 (start, end)
10131 } else {
10132 (selection.start, selection.end)
10133 };
10134
10135 let text = buffer.text_for_range(start..end).collect::<String>();
10136 let old_length = text.len() as i32;
10137 let text = callback(&text);
10138
10139 new_selections.push(Selection {
10140 start: (start as i32 - selection_adjustment) as usize,
10141 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10142 goal: SelectionGoal::None,
10143 ..selection
10144 });
10145
10146 selection_adjustment += old_length - text.len() as i32;
10147
10148 edits.push((start..end, text));
10149 }
10150
10151 self.transact(window, cx, |this, window, cx| {
10152 this.buffer.update(cx, |buffer, cx| {
10153 buffer.edit(edits, None, cx);
10154 });
10155
10156 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10157 s.select(new_selections);
10158 });
10159
10160 this.request_autoscroll(Autoscroll::fit(), cx);
10161 });
10162 }
10163
10164 pub fn duplicate(
10165 &mut self,
10166 upwards: bool,
10167 whole_lines: bool,
10168 window: &mut Window,
10169 cx: &mut Context<Self>,
10170 ) {
10171 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10172
10173 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10174 let buffer = &display_map.buffer_snapshot;
10175 let selections = self.selections.all::<Point>(cx);
10176
10177 let mut edits = Vec::new();
10178 let mut selections_iter = selections.iter().peekable();
10179 while let Some(selection) = selections_iter.next() {
10180 let mut rows = selection.spanned_rows(false, &display_map);
10181 // duplicate line-wise
10182 if whole_lines || selection.start == selection.end {
10183 // Avoid duplicating the same lines twice.
10184 while let Some(next_selection) = selections_iter.peek() {
10185 let next_rows = next_selection.spanned_rows(false, &display_map);
10186 if next_rows.start < rows.end {
10187 rows.end = next_rows.end;
10188 selections_iter.next().unwrap();
10189 } else {
10190 break;
10191 }
10192 }
10193
10194 // Copy the text from the selected row region and splice it either at the start
10195 // or end of the region.
10196 let start = Point::new(rows.start.0, 0);
10197 let end = Point::new(
10198 rows.end.previous_row().0,
10199 buffer.line_len(rows.end.previous_row()),
10200 );
10201 let text = buffer
10202 .text_for_range(start..end)
10203 .chain(Some("\n"))
10204 .collect::<String>();
10205 let insert_location = if upwards {
10206 Point::new(rows.end.0, 0)
10207 } else {
10208 start
10209 };
10210 edits.push((insert_location..insert_location, text));
10211 } else {
10212 // duplicate character-wise
10213 let start = selection.start;
10214 let end = selection.end;
10215 let text = buffer.text_for_range(start..end).collect::<String>();
10216 edits.push((selection.end..selection.end, text));
10217 }
10218 }
10219
10220 self.transact(window, cx, |this, _, cx| {
10221 this.buffer.update(cx, |buffer, cx| {
10222 buffer.edit(edits, None, cx);
10223 });
10224
10225 this.request_autoscroll(Autoscroll::fit(), cx);
10226 });
10227 }
10228
10229 pub fn duplicate_line_up(
10230 &mut self,
10231 _: &DuplicateLineUp,
10232 window: &mut Window,
10233 cx: &mut Context<Self>,
10234 ) {
10235 self.duplicate(true, true, window, cx);
10236 }
10237
10238 pub fn duplicate_line_down(
10239 &mut self,
10240 _: &DuplicateLineDown,
10241 window: &mut Window,
10242 cx: &mut Context<Self>,
10243 ) {
10244 self.duplicate(false, true, window, cx);
10245 }
10246
10247 pub fn duplicate_selection(
10248 &mut self,
10249 _: &DuplicateSelection,
10250 window: &mut Window,
10251 cx: &mut Context<Self>,
10252 ) {
10253 self.duplicate(false, false, window, cx);
10254 }
10255
10256 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10257 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10258
10259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10260 let buffer = self.buffer.read(cx).snapshot(cx);
10261
10262 let mut edits = Vec::new();
10263 let mut unfold_ranges = Vec::new();
10264 let mut refold_creases = Vec::new();
10265
10266 let selections = self.selections.all::<Point>(cx);
10267 let mut selections = selections.iter().peekable();
10268 let mut contiguous_row_selections = Vec::new();
10269 let mut new_selections = Vec::new();
10270
10271 while let Some(selection) = selections.next() {
10272 // Find all the selections that span a contiguous row range
10273 let (start_row, end_row) = consume_contiguous_rows(
10274 &mut contiguous_row_selections,
10275 selection,
10276 &display_map,
10277 &mut selections,
10278 );
10279
10280 // Move the text spanned by the row range to be before the line preceding the row range
10281 if start_row.0 > 0 {
10282 let range_to_move = Point::new(
10283 start_row.previous_row().0,
10284 buffer.line_len(start_row.previous_row()),
10285 )
10286 ..Point::new(
10287 end_row.previous_row().0,
10288 buffer.line_len(end_row.previous_row()),
10289 );
10290 let insertion_point = display_map
10291 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10292 .0;
10293
10294 // Don't move lines across excerpts
10295 if buffer
10296 .excerpt_containing(insertion_point..range_to_move.end)
10297 .is_some()
10298 {
10299 let text = buffer
10300 .text_for_range(range_to_move.clone())
10301 .flat_map(|s| s.chars())
10302 .skip(1)
10303 .chain(['\n'])
10304 .collect::<String>();
10305
10306 edits.push((
10307 buffer.anchor_after(range_to_move.start)
10308 ..buffer.anchor_before(range_to_move.end),
10309 String::new(),
10310 ));
10311 let insertion_anchor = buffer.anchor_after(insertion_point);
10312 edits.push((insertion_anchor..insertion_anchor, text));
10313
10314 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10315
10316 // Move selections up
10317 new_selections.extend(contiguous_row_selections.drain(..).map(
10318 |mut selection| {
10319 selection.start.row -= row_delta;
10320 selection.end.row -= row_delta;
10321 selection
10322 },
10323 ));
10324
10325 // Move folds up
10326 unfold_ranges.push(range_to_move.clone());
10327 for fold in display_map.folds_in_range(
10328 buffer.anchor_before(range_to_move.start)
10329 ..buffer.anchor_after(range_to_move.end),
10330 ) {
10331 let mut start = fold.range.start.to_point(&buffer);
10332 let mut end = fold.range.end.to_point(&buffer);
10333 start.row -= row_delta;
10334 end.row -= row_delta;
10335 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10336 }
10337 }
10338 }
10339
10340 // If we didn't move line(s), preserve the existing selections
10341 new_selections.append(&mut contiguous_row_selections);
10342 }
10343
10344 self.transact(window, cx, |this, window, cx| {
10345 this.unfold_ranges(&unfold_ranges, true, true, cx);
10346 this.buffer.update(cx, |buffer, cx| {
10347 for (range, text) in edits {
10348 buffer.edit([(range, text)], None, cx);
10349 }
10350 });
10351 this.fold_creases(refold_creases, true, window, cx);
10352 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10353 s.select(new_selections);
10354 })
10355 });
10356 }
10357
10358 pub fn move_line_down(
10359 &mut self,
10360 _: &MoveLineDown,
10361 window: &mut Window,
10362 cx: &mut Context<Self>,
10363 ) {
10364 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10365
10366 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10367 let buffer = self.buffer.read(cx).snapshot(cx);
10368
10369 let mut edits = Vec::new();
10370 let mut unfold_ranges = Vec::new();
10371 let mut refold_creases = Vec::new();
10372
10373 let selections = self.selections.all::<Point>(cx);
10374 let mut selections = selections.iter().peekable();
10375 let mut contiguous_row_selections = Vec::new();
10376 let mut new_selections = Vec::new();
10377
10378 while let Some(selection) = selections.next() {
10379 // Find all the selections that span a contiguous row range
10380 let (start_row, end_row) = consume_contiguous_rows(
10381 &mut contiguous_row_selections,
10382 selection,
10383 &display_map,
10384 &mut selections,
10385 );
10386
10387 // Move the text spanned by the row range to be after the last line of the row range
10388 if end_row.0 <= buffer.max_point().row {
10389 let range_to_move =
10390 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10391 let insertion_point = display_map
10392 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10393 .0;
10394
10395 // Don't move lines across excerpt boundaries
10396 if buffer
10397 .excerpt_containing(range_to_move.start..insertion_point)
10398 .is_some()
10399 {
10400 let mut text = String::from("\n");
10401 text.extend(buffer.text_for_range(range_to_move.clone()));
10402 text.pop(); // Drop trailing newline
10403 edits.push((
10404 buffer.anchor_after(range_to_move.start)
10405 ..buffer.anchor_before(range_to_move.end),
10406 String::new(),
10407 ));
10408 let insertion_anchor = buffer.anchor_after(insertion_point);
10409 edits.push((insertion_anchor..insertion_anchor, text));
10410
10411 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10412
10413 // Move selections down
10414 new_selections.extend(contiguous_row_selections.drain(..).map(
10415 |mut selection| {
10416 selection.start.row += row_delta;
10417 selection.end.row += row_delta;
10418 selection
10419 },
10420 ));
10421
10422 // Move folds down
10423 unfold_ranges.push(range_to_move.clone());
10424 for fold in display_map.folds_in_range(
10425 buffer.anchor_before(range_to_move.start)
10426 ..buffer.anchor_after(range_to_move.end),
10427 ) {
10428 let mut start = fold.range.start.to_point(&buffer);
10429 let mut end = fold.range.end.to_point(&buffer);
10430 start.row += row_delta;
10431 end.row += row_delta;
10432 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10433 }
10434 }
10435 }
10436
10437 // If we didn't move line(s), preserve the existing selections
10438 new_selections.append(&mut contiguous_row_selections);
10439 }
10440
10441 self.transact(window, cx, |this, window, cx| {
10442 this.unfold_ranges(&unfold_ranges, true, true, cx);
10443 this.buffer.update(cx, |buffer, cx| {
10444 for (range, text) in edits {
10445 buffer.edit([(range, text)], None, cx);
10446 }
10447 });
10448 this.fold_creases(refold_creases, true, window, cx);
10449 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10450 s.select(new_selections)
10451 });
10452 });
10453 }
10454
10455 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10456 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10457 let text_layout_details = &self.text_layout_details(window);
10458 self.transact(window, cx, |this, window, cx| {
10459 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10460 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10461 s.move_with(|display_map, selection| {
10462 if !selection.is_empty() {
10463 return;
10464 }
10465
10466 let mut head = selection.head();
10467 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10468 if head.column() == display_map.line_len(head.row()) {
10469 transpose_offset = display_map
10470 .buffer_snapshot
10471 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10472 }
10473
10474 if transpose_offset == 0 {
10475 return;
10476 }
10477
10478 *head.column_mut() += 1;
10479 head = display_map.clip_point(head, Bias::Right);
10480 let goal = SelectionGoal::HorizontalPosition(
10481 display_map
10482 .x_for_display_point(head, text_layout_details)
10483 .into(),
10484 );
10485 selection.collapse_to(head, goal);
10486
10487 let transpose_start = display_map
10488 .buffer_snapshot
10489 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10490 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10491 let transpose_end = display_map
10492 .buffer_snapshot
10493 .clip_offset(transpose_offset + 1, Bias::Right);
10494 if let Some(ch) =
10495 display_map.buffer_snapshot.chars_at(transpose_start).next()
10496 {
10497 edits.push((transpose_start..transpose_offset, String::new()));
10498 edits.push((transpose_end..transpose_end, ch.to_string()));
10499 }
10500 }
10501 });
10502 edits
10503 });
10504 this.buffer
10505 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10506 let selections = this.selections.all::<usize>(cx);
10507 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10508 s.select(selections);
10509 });
10510 });
10511 }
10512
10513 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10514 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10515 self.rewrap_impl(RewrapOptions::default(), cx)
10516 }
10517
10518 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10519 let buffer = self.buffer.read(cx).snapshot(cx);
10520 let selections = self.selections.all::<Point>(cx);
10521 let mut selections = selections.iter().peekable();
10522
10523 let mut edits = Vec::new();
10524 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10525
10526 while let Some(selection) = selections.next() {
10527 let mut start_row = selection.start.row;
10528 let mut end_row = selection.end.row;
10529
10530 // Skip selections that overlap with a range that has already been rewrapped.
10531 let selection_range = start_row..end_row;
10532 if rewrapped_row_ranges
10533 .iter()
10534 .any(|range| range.overlaps(&selection_range))
10535 {
10536 continue;
10537 }
10538
10539 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10540
10541 // Since not all lines in the selection may be at the same indent
10542 // level, choose the indent size that is the most common between all
10543 // of the lines.
10544 //
10545 // If there is a tie, we use the deepest indent.
10546 let (indent_size, indent_end) = {
10547 let mut indent_size_occurrences = HashMap::default();
10548 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10549
10550 for row in start_row..=end_row {
10551 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10552 rows_by_indent_size.entry(indent).or_default().push(row);
10553 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10554 }
10555
10556 let indent_size = indent_size_occurrences
10557 .into_iter()
10558 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10559 .map(|(indent, _)| indent)
10560 .unwrap_or_default();
10561 let row = rows_by_indent_size[&indent_size][0];
10562 let indent_end = Point::new(row, indent_size.len);
10563
10564 (indent_size, indent_end)
10565 };
10566
10567 let mut line_prefix = indent_size.chars().collect::<String>();
10568
10569 let mut inside_comment = false;
10570 if let Some(comment_prefix) =
10571 buffer
10572 .language_scope_at(selection.head())
10573 .and_then(|language| {
10574 language
10575 .line_comment_prefixes()
10576 .iter()
10577 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10578 .cloned()
10579 })
10580 {
10581 line_prefix.push_str(&comment_prefix);
10582 inside_comment = true;
10583 }
10584
10585 let language_settings = buffer.language_settings_at(selection.head(), cx);
10586 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10587 RewrapBehavior::InComments => inside_comment,
10588 RewrapBehavior::InSelections => !selection.is_empty(),
10589 RewrapBehavior::Anywhere => true,
10590 };
10591
10592 let should_rewrap = options.override_language_settings
10593 || allow_rewrap_based_on_language
10594 || self.hard_wrap.is_some();
10595 if !should_rewrap {
10596 continue;
10597 }
10598
10599 if selection.is_empty() {
10600 'expand_upwards: while start_row > 0 {
10601 let prev_row = start_row - 1;
10602 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10603 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10604 {
10605 start_row = prev_row;
10606 } else {
10607 break 'expand_upwards;
10608 }
10609 }
10610
10611 'expand_downwards: while end_row < buffer.max_point().row {
10612 let next_row = end_row + 1;
10613 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10614 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10615 {
10616 end_row = next_row;
10617 } else {
10618 break 'expand_downwards;
10619 }
10620 }
10621 }
10622
10623 let start = Point::new(start_row, 0);
10624 let start_offset = start.to_offset(&buffer);
10625 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10626 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10627 let Some(lines_without_prefixes) = selection_text
10628 .lines()
10629 .map(|line| {
10630 line.strip_prefix(&line_prefix)
10631 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10632 .ok_or_else(|| {
10633 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10634 })
10635 })
10636 .collect::<Result<Vec<_>, _>>()
10637 .log_err()
10638 else {
10639 continue;
10640 };
10641
10642 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10643 buffer
10644 .language_settings_at(Point::new(start_row, 0), cx)
10645 .preferred_line_length as usize
10646 });
10647 let wrapped_text = wrap_with_prefix(
10648 line_prefix,
10649 lines_without_prefixes.join("\n"),
10650 wrap_column,
10651 tab_size,
10652 options.preserve_existing_whitespace,
10653 );
10654
10655 // TODO: should always use char-based diff while still supporting cursor behavior that
10656 // matches vim.
10657 let mut diff_options = DiffOptions::default();
10658 if options.override_language_settings {
10659 diff_options.max_word_diff_len = 0;
10660 diff_options.max_word_diff_line_count = 0;
10661 } else {
10662 diff_options.max_word_diff_len = usize::MAX;
10663 diff_options.max_word_diff_line_count = usize::MAX;
10664 }
10665
10666 for (old_range, new_text) in
10667 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10668 {
10669 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10670 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10671 edits.push((edit_start..edit_end, new_text));
10672 }
10673
10674 rewrapped_row_ranges.push(start_row..=end_row);
10675 }
10676
10677 self.buffer
10678 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10679 }
10680
10681 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10682 let mut text = String::new();
10683 let buffer = self.buffer.read(cx).snapshot(cx);
10684 let mut selections = self.selections.all::<Point>(cx);
10685 let mut clipboard_selections = Vec::with_capacity(selections.len());
10686 {
10687 let max_point = buffer.max_point();
10688 let mut is_first = true;
10689 for selection in &mut selections {
10690 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10691 if is_entire_line {
10692 selection.start = Point::new(selection.start.row, 0);
10693 if !selection.is_empty() && selection.end.column == 0 {
10694 selection.end = cmp::min(max_point, selection.end);
10695 } else {
10696 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10697 }
10698 selection.goal = SelectionGoal::None;
10699 }
10700 if is_first {
10701 is_first = false;
10702 } else {
10703 text += "\n";
10704 }
10705 let mut len = 0;
10706 for chunk in buffer.text_for_range(selection.start..selection.end) {
10707 text.push_str(chunk);
10708 len += chunk.len();
10709 }
10710 clipboard_selections.push(ClipboardSelection {
10711 len,
10712 is_entire_line,
10713 first_line_indent: buffer
10714 .indent_size_for_line(MultiBufferRow(selection.start.row))
10715 .len,
10716 });
10717 }
10718 }
10719
10720 self.transact(window, cx, |this, window, cx| {
10721 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10722 s.select(selections);
10723 });
10724 this.insert("", window, cx);
10725 });
10726 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10727 }
10728
10729 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10730 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10731 let item = self.cut_common(window, cx);
10732 cx.write_to_clipboard(item);
10733 }
10734
10735 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10736 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10737 self.change_selections(None, window, cx, |s| {
10738 s.move_with(|snapshot, sel| {
10739 if sel.is_empty() {
10740 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10741 }
10742 });
10743 });
10744 let item = self.cut_common(window, cx);
10745 cx.set_global(KillRing(item))
10746 }
10747
10748 pub fn kill_ring_yank(
10749 &mut self,
10750 _: &KillRingYank,
10751 window: &mut Window,
10752 cx: &mut Context<Self>,
10753 ) {
10754 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10755 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10756 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10757 (kill_ring.text().to_string(), kill_ring.metadata_json())
10758 } else {
10759 return;
10760 }
10761 } else {
10762 return;
10763 };
10764 self.do_paste(&text, metadata, false, window, cx);
10765 }
10766
10767 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10768 self.do_copy(true, cx);
10769 }
10770
10771 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10772 self.do_copy(false, cx);
10773 }
10774
10775 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10776 let selections = self.selections.all::<Point>(cx);
10777 let buffer = self.buffer.read(cx).read(cx);
10778 let mut text = String::new();
10779
10780 let mut clipboard_selections = Vec::with_capacity(selections.len());
10781 {
10782 let max_point = buffer.max_point();
10783 let mut is_first = true;
10784 for selection in &selections {
10785 let mut start = selection.start;
10786 let mut end = selection.end;
10787 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10788 if is_entire_line {
10789 start = Point::new(start.row, 0);
10790 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10791 }
10792
10793 let mut trimmed_selections = Vec::new();
10794 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10795 let row = MultiBufferRow(start.row);
10796 let first_indent = buffer.indent_size_for_line(row);
10797 if first_indent.len == 0 || start.column > first_indent.len {
10798 trimmed_selections.push(start..end);
10799 } else {
10800 trimmed_selections.push(
10801 Point::new(row.0, first_indent.len)
10802 ..Point::new(row.0, buffer.line_len(row)),
10803 );
10804 for row in start.row + 1..=end.row {
10805 let mut line_len = buffer.line_len(MultiBufferRow(row));
10806 if row == end.row {
10807 line_len = end.column;
10808 }
10809 if line_len == 0 {
10810 trimmed_selections
10811 .push(Point::new(row, 0)..Point::new(row, line_len));
10812 continue;
10813 }
10814 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10815 if row_indent_size.len >= first_indent.len {
10816 trimmed_selections.push(
10817 Point::new(row, first_indent.len)..Point::new(row, line_len),
10818 );
10819 } else {
10820 trimmed_selections.clear();
10821 trimmed_selections.push(start..end);
10822 break;
10823 }
10824 }
10825 }
10826 } else {
10827 trimmed_selections.push(start..end);
10828 }
10829
10830 for trimmed_range in trimmed_selections {
10831 if is_first {
10832 is_first = false;
10833 } else {
10834 text += "\n";
10835 }
10836 let mut len = 0;
10837 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10838 text.push_str(chunk);
10839 len += chunk.len();
10840 }
10841 clipboard_selections.push(ClipboardSelection {
10842 len,
10843 is_entire_line,
10844 first_line_indent: buffer
10845 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10846 .len,
10847 });
10848 }
10849 }
10850 }
10851
10852 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10853 text,
10854 clipboard_selections,
10855 ));
10856 }
10857
10858 pub fn do_paste(
10859 &mut self,
10860 text: &String,
10861 clipboard_selections: Option<Vec<ClipboardSelection>>,
10862 handle_entire_lines: bool,
10863 window: &mut Window,
10864 cx: &mut Context<Self>,
10865 ) {
10866 if self.read_only(cx) {
10867 return;
10868 }
10869
10870 let clipboard_text = Cow::Borrowed(text);
10871
10872 self.transact(window, cx, |this, window, cx| {
10873 if let Some(mut clipboard_selections) = clipboard_selections {
10874 let old_selections = this.selections.all::<usize>(cx);
10875 let all_selections_were_entire_line =
10876 clipboard_selections.iter().all(|s| s.is_entire_line);
10877 let first_selection_indent_column =
10878 clipboard_selections.first().map(|s| s.first_line_indent);
10879 if clipboard_selections.len() != old_selections.len() {
10880 clipboard_selections.drain(..);
10881 }
10882 let cursor_offset = this.selections.last::<usize>(cx).head();
10883 let mut auto_indent_on_paste = true;
10884
10885 this.buffer.update(cx, |buffer, cx| {
10886 let snapshot = buffer.read(cx);
10887 auto_indent_on_paste = snapshot
10888 .language_settings_at(cursor_offset, cx)
10889 .auto_indent_on_paste;
10890
10891 let mut start_offset = 0;
10892 let mut edits = Vec::new();
10893 let mut original_indent_columns = Vec::new();
10894 for (ix, selection) in old_selections.iter().enumerate() {
10895 let to_insert;
10896 let entire_line;
10897 let original_indent_column;
10898 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10899 let end_offset = start_offset + clipboard_selection.len;
10900 to_insert = &clipboard_text[start_offset..end_offset];
10901 entire_line = clipboard_selection.is_entire_line;
10902 start_offset = end_offset + 1;
10903 original_indent_column = Some(clipboard_selection.first_line_indent);
10904 } else {
10905 to_insert = clipboard_text.as_str();
10906 entire_line = all_selections_were_entire_line;
10907 original_indent_column = first_selection_indent_column
10908 }
10909
10910 // If the corresponding selection was empty when this slice of the
10911 // clipboard text was written, then the entire line containing the
10912 // selection was copied. If this selection is also currently empty,
10913 // then paste the line before the current line of the buffer.
10914 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10915 let column = selection.start.to_point(&snapshot).column as usize;
10916 let line_start = selection.start - column;
10917 line_start..line_start
10918 } else {
10919 selection.range()
10920 };
10921
10922 edits.push((range, to_insert));
10923 original_indent_columns.push(original_indent_column);
10924 }
10925 drop(snapshot);
10926
10927 buffer.edit(
10928 edits,
10929 if auto_indent_on_paste {
10930 Some(AutoindentMode::Block {
10931 original_indent_columns,
10932 })
10933 } else {
10934 None
10935 },
10936 cx,
10937 );
10938 });
10939
10940 let selections = this.selections.all::<usize>(cx);
10941 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10942 s.select(selections)
10943 });
10944 } else {
10945 this.insert(&clipboard_text, window, cx);
10946 }
10947 });
10948 }
10949
10950 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10951 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10952 if let Some(item) = cx.read_from_clipboard() {
10953 let entries = item.entries();
10954
10955 match entries.first() {
10956 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10957 // of all the pasted entries.
10958 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10959 .do_paste(
10960 clipboard_string.text(),
10961 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10962 true,
10963 window,
10964 cx,
10965 ),
10966 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10967 }
10968 }
10969 }
10970
10971 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10972 if self.read_only(cx) {
10973 return;
10974 }
10975
10976 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10977
10978 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10979 if let Some((selections, _)) =
10980 self.selection_history.transaction(transaction_id).cloned()
10981 {
10982 self.change_selections(None, window, cx, |s| {
10983 s.select_anchors(selections.to_vec());
10984 });
10985 } else {
10986 log::error!(
10987 "No entry in selection_history found for undo. \
10988 This may correspond to a bug where undo does not update the selection. \
10989 If this is occurring, please add details to \
10990 https://github.com/zed-industries/zed/issues/22692"
10991 );
10992 }
10993 self.request_autoscroll(Autoscroll::fit(), cx);
10994 self.unmark_text(window, cx);
10995 self.refresh_inline_completion(true, false, window, cx);
10996 cx.emit(EditorEvent::Edited { transaction_id });
10997 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10998 }
10999 }
11000
11001 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11002 if self.read_only(cx) {
11003 return;
11004 }
11005
11006 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11007
11008 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11009 if let Some((_, Some(selections))) =
11010 self.selection_history.transaction(transaction_id).cloned()
11011 {
11012 self.change_selections(None, window, cx, |s| {
11013 s.select_anchors(selections.to_vec());
11014 });
11015 } else {
11016 log::error!(
11017 "No entry in selection_history found for redo. \
11018 This may correspond to a bug where undo does not update the selection. \
11019 If this is occurring, please add details to \
11020 https://github.com/zed-industries/zed/issues/22692"
11021 );
11022 }
11023 self.request_autoscroll(Autoscroll::fit(), cx);
11024 self.unmark_text(window, cx);
11025 self.refresh_inline_completion(true, false, window, cx);
11026 cx.emit(EditorEvent::Edited { transaction_id });
11027 }
11028 }
11029
11030 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11031 self.buffer
11032 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11033 }
11034
11035 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11036 self.buffer
11037 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11038 }
11039
11040 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11041 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11043 s.move_with(|map, selection| {
11044 let cursor = if selection.is_empty() {
11045 movement::left(map, selection.start)
11046 } else {
11047 selection.start
11048 };
11049 selection.collapse_to(cursor, SelectionGoal::None);
11050 });
11051 })
11052 }
11053
11054 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11055 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11056 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11057 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11058 })
11059 }
11060
11061 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11062 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11063 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11064 s.move_with(|map, selection| {
11065 let cursor = if selection.is_empty() {
11066 movement::right(map, selection.end)
11067 } else {
11068 selection.end
11069 };
11070 selection.collapse_to(cursor, SelectionGoal::None)
11071 });
11072 })
11073 }
11074
11075 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11076 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11077 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11078 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11079 })
11080 }
11081
11082 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11083 if self.take_rename(true, window, cx).is_some() {
11084 return;
11085 }
11086
11087 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11088 cx.propagate();
11089 return;
11090 }
11091
11092 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11093
11094 let text_layout_details = &self.text_layout_details(window);
11095 let selection_count = self.selections.count();
11096 let first_selection = self.selections.first_anchor();
11097
11098 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11099 s.move_with(|map, selection| {
11100 if !selection.is_empty() {
11101 selection.goal = SelectionGoal::None;
11102 }
11103 let (cursor, goal) = movement::up(
11104 map,
11105 selection.start,
11106 selection.goal,
11107 false,
11108 text_layout_details,
11109 );
11110 selection.collapse_to(cursor, goal);
11111 });
11112 });
11113
11114 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11115 {
11116 cx.propagate();
11117 }
11118 }
11119
11120 pub fn move_up_by_lines(
11121 &mut self,
11122 action: &MoveUpByLines,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 ) {
11126 if self.take_rename(true, window, cx).is_some() {
11127 return;
11128 }
11129
11130 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11131 cx.propagate();
11132 return;
11133 }
11134
11135 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11136
11137 let text_layout_details = &self.text_layout_details(window);
11138
11139 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11140 s.move_with(|map, selection| {
11141 if !selection.is_empty() {
11142 selection.goal = SelectionGoal::None;
11143 }
11144 let (cursor, goal) = movement::up_by_rows(
11145 map,
11146 selection.start,
11147 action.lines,
11148 selection.goal,
11149 false,
11150 text_layout_details,
11151 );
11152 selection.collapse_to(cursor, goal);
11153 });
11154 })
11155 }
11156
11157 pub fn move_down_by_lines(
11158 &mut self,
11159 action: &MoveDownByLines,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 ) {
11163 if self.take_rename(true, window, cx).is_some() {
11164 return;
11165 }
11166
11167 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11168 cx.propagate();
11169 return;
11170 }
11171
11172 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11173
11174 let text_layout_details = &self.text_layout_details(window);
11175
11176 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11177 s.move_with(|map, selection| {
11178 if !selection.is_empty() {
11179 selection.goal = SelectionGoal::None;
11180 }
11181 let (cursor, goal) = movement::down_by_rows(
11182 map,
11183 selection.start,
11184 action.lines,
11185 selection.goal,
11186 false,
11187 text_layout_details,
11188 );
11189 selection.collapse_to(cursor, goal);
11190 });
11191 })
11192 }
11193
11194 pub fn select_down_by_lines(
11195 &mut self,
11196 action: &SelectDownByLines,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11201 let text_layout_details = &self.text_layout_details(window);
11202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11203 s.move_heads_with(|map, head, goal| {
11204 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11205 })
11206 })
11207 }
11208
11209 pub fn select_up_by_lines(
11210 &mut self,
11211 action: &SelectUpByLines,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11216 let text_layout_details = &self.text_layout_details(window);
11217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11218 s.move_heads_with(|map, head, goal| {
11219 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11220 })
11221 })
11222 }
11223
11224 pub fn select_page_up(
11225 &mut self,
11226 _: &SelectPageUp,
11227 window: &mut Window,
11228 cx: &mut Context<Self>,
11229 ) {
11230 let Some(row_count) = self.visible_row_count() else {
11231 return;
11232 };
11233
11234 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11235
11236 let text_layout_details = &self.text_layout_details(window);
11237
11238 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11239 s.move_heads_with(|map, head, goal| {
11240 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11241 })
11242 })
11243 }
11244
11245 pub fn move_page_up(
11246 &mut self,
11247 action: &MovePageUp,
11248 window: &mut Window,
11249 cx: &mut Context<Self>,
11250 ) {
11251 if self.take_rename(true, window, cx).is_some() {
11252 return;
11253 }
11254
11255 if self
11256 .context_menu
11257 .borrow_mut()
11258 .as_mut()
11259 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11260 .unwrap_or(false)
11261 {
11262 return;
11263 }
11264
11265 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11266 cx.propagate();
11267 return;
11268 }
11269
11270 let Some(row_count) = self.visible_row_count() else {
11271 return;
11272 };
11273
11274 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11275
11276 let autoscroll = if action.center_cursor {
11277 Autoscroll::center()
11278 } else {
11279 Autoscroll::fit()
11280 };
11281
11282 let text_layout_details = &self.text_layout_details(window);
11283
11284 self.change_selections(Some(autoscroll), window, cx, |s| {
11285 s.move_with(|map, selection| {
11286 if !selection.is_empty() {
11287 selection.goal = SelectionGoal::None;
11288 }
11289 let (cursor, goal) = movement::up_by_rows(
11290 map,
11291 selection.end,
11292 row_count,
11293 selection.goal,
11294 false,
11295 text_layout_details,
11296 );
11297 selection.collapse_to(cursor, goal);
11298 });
11299 });
11300 }
11301
11302 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11303 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11304 let text_layout_details = &self.text_layout_details(window);
11305 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11306 s.move_heads_with(|map, head, goal| {
11307 movement::up(map, head, goal, false, text_layout_details)
11308 })
11309 })
11310 }
11311
11312 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11313 self.take_rename(true, window, cx);
11314
11315 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11316 cx.propagate();
11317 return;
11318 }
11319
11320 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11321
11322 let text_layout_details = &self.text_layout_details(window);
11323 let selection_count = self.selections.count();
11324 let first_selection = self.selections.first_anchor();
11325
11326 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11327 s.move_with(|map, selection| {
11328 if !selection.is_empty() {
11329 selection.goal = SelectionGoal::None;
11330 }
11331 let (cursor, goal) = movement::down(
11332 map,
11333 selection.end,
11334 selection.goal,
11335 false,
11336 text_layout_details,
11337 );
11338 selection.collapse_to(cursor, goal);
11339 });
11340 });
11341
11342 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11343 {
11344 cx.propagate();
11345 }
11346 }
11347
11348 pub fn select_page_down(
11349 &mut self,
11350 _: &SelectPageDown,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 let Some(row_count) = self.visible_row_count() else {
11355 return;
11356 };
11357
11358 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11359
11360 let text_layout_details = &self.text_layout_details(window);
11361
11362 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11363 s.move_heads_with(|map, head, goal| {
11364 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11365 })
11366 })
11367 }
11368
11369 pub fn move_page_down(
11370 &mut self,
11371 action: &MovePageDown,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 if self.take_rename(true, window, cx).is_some() {
11376 return;
11377 }
11378
11379 if self
11380 .context_menu
11381 .borrow_mut()
11382 .as_mut()
11383 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11384 .unwrap_or(false)
11385 {
11386 return;
11387 }
11388
11389 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11390 cx.propagate();
11391 return;
11392 }
11393
11394 let Some(row_count) = self.visible_row_count() else {
11395 return;
11396 };
11397
11398 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11399
11400 let autoscroll = if action.center_cursor {
11401 Autoscroll::center()
11402 } else {
11403 Autoscroll::fit()
11404 };
11405
11406 let text_layout_details = &self.text_layout_details(window);
11407 self.change_selections(Some(autoscroll), window, cx, |s| {
11408 s.move_with(|map, selection| {
11409 if !selection.is_empty() {
11410 selection.goal = SelectionGoal::None;
11411 }
11412 let (cursor, goal) = movement::down_by_rows(
11413 map,
11414 selection.end,
11415 row_count,
11416 selection.goal,
11417 false,
11418 text_layout_details,
11419 );
11420 selection.collapse_to(cursor, goal);
11421 });
11422 });
11423 }
11424
11425 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11426 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11427 let text_layout_details = &self.text_layout_details(window);
11428 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11429 s.move_heads_with(|map, head, goal| {
11430 movement::down(map, head, goal, false, text_layout_details)
11431 })
11432 });
11433 }
11434
11435 pub fn context_menu_first(
11436 &mut self,
11437 _: &ContextMenuFirst,
11438 _window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11442 context_menu.select_first(self.completion_provider.as_deref(), cx);
11443 }
11444 }
11445
11446 pub fn context_menu_prev(
11447 &mut self,
11448 _: &ContextMenuPrevious,
11449 _window: &mut Window,
11450 cx: &mut Context<Self>,
11451 ) {
11452 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11453 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11454 }
11455 }
11456
11457 pub fn context_menu_next(
11458 &mut self,
11459 _: &ContextMenuNext,
11460 _window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11464 context_menu.select_next(self.completion_provider.as_deref(), cx);
11465 }
11466 }
11467
11468 pub fn context_menu_last(
11469 &mut self,
11470 _: &ContextMenuLast,
11471 _window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11475 context_menu.select_last(self.completion_provider.as_deref(), cx);
11476 }
11477 }
11478
11479 pub fn move_to_previous_word_start(
11480 &mut self,
11481 _: &MoveToPreviousWordStart,
11482 window: &mut Window,
11483 cx: &mut Context<Self>,
11484 ) {
11485 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11486 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11487 s.move_cursors_with(|map, head, _| {
11488 (
11489 movement::previous_word_start(map, head),
11490 SelectionGoal::None,
11491 )
11492 });
11493 })
11494 }
11495
11496 pub fn move_to_previous_subword_start(
11497 &mut self,
11498 _: &MoveToPreviousSubwordStart,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) {
11502 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11503 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11504 s.move_cursors_with(|map, head, _| {
11505 (
11506 movement::previous_subword_start(map, head),
11507 SelectionGoal::None,
11508 )
11509 });
11510 })
11511 }
11512
11513 pub fn select_to_previous_word_start(
11514 &mut self,
11515 _: &SelectToPreviousWordStart,
11516 window: &mut Window,
11517 cx: &mut Context<Self>,
11518 ) {
11519 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11520 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11521 s.move_heads_with(|map, head, _| {
11522 (
11523 movement::previous_word_start(map, head),
11524 SelectionGoal::None,
11525 )
11526 });
11527 })
11528 }
11529
11530 pub fn select_to_previous_subword_start(
11531 &mut self,
11532 _: &SelectToPreviousSubwordStart,
11533 window: &mut Window,
11534 cx: &mut Context<Self>,
11535 ) {
11536 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11537 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11538 s.move_heads_with(|map, head, _| {
11539 (
11540 movement::previous_subword_start(map, head),
11541 SelectionGoal::None,
11542 )
11543 });
11544 })
11545 }
11546
11547 pub fn delete_to_previous_word_start(
11548 &mut self,
11549 action: &DeleteToPreviousWordStart,
11550 window: &mut Window,
11551 cx: &mut Context<Self>,
11552 ) {
11553 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11554 self.transact(window, cx, |this, window, cx| {
11555 this.select_autoclose_pair(window, cx);
11556 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11557 s.move_with(|map, selection| {
11558 if selection.is_empty() {
11559 let cursor = if action.ignore_newlines {
11560 movement::previous_word_start(map, selection.head())
11561 } else {
11562 movement::previous_word_start_or_newline(map, selection.head())
11563 };
11564 selection.set_head(cursor, SelectionGoal::None);
11565 }
11566 });
11567 });
11568 this.insert("", window, cx);
11569 });
11570 }
11571
11572 pub fn delete_to_previous_subword_start(
11573 &mut self,
11574 _: &DeleteToPreviousSubwordStart,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11579 self.transact(window, cx, |this, window, cx| {
11580 this.select_autoclose_pair(window, cx);
11581 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11582 s.move_with(|map, selection| {
11583 if selection.is_empty() {
11584 let cursor = movement::previous_subword_start(map, selection.head());
11585 selection.set_head(cursor, SelectionGoal::None);
11586 }
11587 });
11588 });
11589 this.insert("", window, cx);
11590 });
11591 }
11592
11593 pub fn move_to_next_word_end(
11594 &mut self,
11595 _: &MoveToNextWordEnd,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11601 s.move_cursors_with(|map, head, _| {
11602 (movement::next_word_end(map, head), SelectionGoal::None)
11603 });
11604 })
11605 }
11606
11607 pub fn move_to_next_subword_end(
11608 &mut self,
11609 _: &MoveToNextSubwordEnd,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11615 s.move_cursors_with(|map, head, _| {
11616 (movement::next_subword_end(map, head), SelectionGoal::None)
11617 });
11618 })
11619 }
11620
11621 pub fn select_to_next_word_end(
11622 &mut self,
11623 _: &SelectToNextWordEnd,
11624 window: &mut Window,
11625 cx: &mut Context<Self>,
11626 ) {
11627 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11628 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11629 s.move_heads_with(|map, head, _| {
11630 (movement::next_word_end(map, head), SelectionGoal::None)
11631 });
11632 })
11633 }
11634
11635 pub fn select_to_next_subword_end(
11636 &mut self,
11637 _: &SelectToNextSubwordEnd,
11638 window: &mut Window,
11639 cx: &mut Context<Self>,
11640 ) {
11641 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11643 s.move_heads_with(|map, head, _| {
11644 (movement::next_subword_end(map, head), SelectionGoal::None)
11645 });
11646 })
11647 }
11648
11649 pub fn delete_to_next_word_end(
11650 &mut self,
11651 action: &DeleteToNextWordEnd,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11656 self.transact(window, cx, |this, window, cx| {
11657 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.move_with(|map, selection| {
11659 if selection.is_empty() {
11660 let cursor = if action.ignore_newlines {
11661 movement::next_word_end(map, selection.head())
11662 } else {
11663 movement::next_word_end_or_newline(map, selection.head())
11664 };
11665 selection.set_head(cursor, SelectionGoal::None);
11666 }
11667 });
11668 });
11669 this.insert("", window, cx);
11670 });
11671 }
11672
11673 pub fn delete_to_next_subword_end(
11674 &mut self,
11675 _: &DeleteToNextSubwordEnd,
11676 window: &mut Window,
11677 cx: &mut Context<Self>,
11678 ) {
11679 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11680 self.transact(window, cx, |this, window, cx| {
11681 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11682 s.move_with(|map, selection| {
11683 if selection.is_empty() {
11684 let cursor = movement::next_subword_end(map, selection.head());
11685 selection.set_head(cursor, SelectionGoal::None);
11686 }
11687 });
11688 });
11689 this.insert("", window, cx);
11690 });
11691 }
11692
11693 pub fn move_to_beginning_of_line(
11694 &mut self,
11695 action: &MoveToBeginningOfLine,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) {
11699 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11700 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11701 s.move_cursors_with(|map, head, _| {
11702 (
11703 movement::indented_line_beginning(
11704 map,
11705 head,
11706 action.stop_at_soft_wraps,
11707 action.stop_at_indent,
11708 ),
11709 SelectionGoal::None,
11710 )
11711 });
11712 })
11713 }
11714
11715 pub fn select_to_beginning_of_line(
11716 &mut self,
11717 action: &SelectToBeginningOfLine,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11722 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11723 s.move_heads_with(|map, head, _| {
11724 (
11725 movement::indented_line_beginning(
11726 map,
11727 head,
11728 action.stop_at_soft_wraps,
11729 action.stop_at_indent,
11730 ),
11731 SelectionGoal::None,
11732 )
11733 });
11734 });
11735 }
11736
11737 pub fn delete_to_beginning_of_line(
11738 &mut self,
11739 action: &DeleteToBeginningOfLine,
11740 window: &mut Window,
11741 cx: &mut Context<Self>,
11742 ) {
11743 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11744 self.transact(window, cx, |this, window, cx| {
11745 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11746 s.move_with(|_, selection| {
11747 selection.reversed = true;
11748 });
11749 });
11750
11751 this.select_to_beginning_of_line(
11752 &SelectToBeginningOfLine {
11753 stop_at_soft_wraps: false,
11754 stop_at_indent: action.stop_at_indent,
11755 },
11756 window,
11757 cx,
11758 );
11759 this.backspace(&Backspace, window, cx);
11760 });
11761 }
11762
11763 pub fn move_to_end_of_line(
11764 &mut self,
11765 action: &MoveToEndOfLine,
11766 window: &mut Window,
11767 cx: &mut Context<Self>,
11768 ) {
11769 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11770 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11771 s.move_cursors_with(|map, head, _| {
11772 (
11773 movement::line_end(map, head, action.stop_at_soft_wraps),
11774 SelectionGoal::None,
11775 )
11776 });
11777 })
11778 }
11779
11780 pub fn select_to_end_of_line(
11781 &mut self,
11782 action: &SelectToEndOfLine,
11783 window: &mut Window,
11784 cx: &mut Context<Self>,
11785 ) {
11786 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11788 s.move_heads_with(|map, head, _| {
11789 (
11790 movement::line_end(map, head, action.stop_at_soft_wraps),
11791 SelectionGoal::None,
11792 )
11793 });
11794 })
11795 }
11796
11797 pub fn delete_to_end_of_line(
11798 &mut self,
11799 _: &DeleteToEndOfLine,
11800 window: &mut Window,
11801 cx: &mut Context<Self>,
11802 ) {
11803 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11804 self.transact(window, cx, |this, window, cx| {
11805 this.select_to_end_of_line(
11806 &SelectToEndOfLine {
11807 stop_at_soft_wraps: false,
11808 },
11809 window,
11810 cx,
11811 );
11812 this.delete(&Delete, window, cx);
11813 });
11814 }
11815
11816 pub fn cut_to_end_of_line(
11817 &mut self,
11818 _: &CutToEndOfLine,
11819 window: &mut Window,
11820 cx: &mut Context<Self>,
11821 ) {
11822 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11823 self.transact(window, cx, |this, window, cx| {
11824 this.select_to_end_of_line(
11825 &SelectToEndOfLine {
11826 stop_at_soft_wraps: false,
11827 },
11828 window,
11829 cx,
11830 );
11831 this.cut(&Cut, window, cx);
11832 });
11833 }
11834
11835 pub fn move_to_start_of_paragraph(
11836 &mut self,
11837 _: &MoveToStartOfParagraph,
11838 window: &mut Window,
11839 cx: &mut Context<Self>,
11840 ) {
11841 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11842 cx.propagate();
11843 return;
11844 }
11845 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11846 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11847 s.move_with(|map, selection| {
11848 selection.collapse_to(
11849 movement::start_of_paragraph(map, selection.head(), 1),
11850 SelectionGoal::None,
11851 )
11852 });
11853 })
11854 }
11855
11856 pub fn move_to_end_of_paragraph(
11857 &mut self,
11858 _: &MoveToEndOfParagraph,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11863 cx.propagate();
11864 return;
11865 }
11866 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11867 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11868 s.move_with(|map, selection| {
11869 selection.collapse_to(
11870 movement::end_of_paragraph(map, selection.head(), 1),
11871 SelectionGoal::None,
11872 )
11873 });
11874 })
11875 }
11876
11877 pub fn select_to_start_of_paragraph(
11878 &mut self,
11879 _: &SelectToStartOfParagraph,
11880 window: &mut Window,
11881 cx: &mut Context<Self>,
11882 ) {
11883 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11884 cx.propagate();
11885 return;
11886 }
11887 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11888 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11889 s.move_heads_with(|map, head, _| {
11890 (
11891 movement::start_of_paragraph(map, head, 1),
11892 SelectionGoal::None,
11893 )
11894 });
11895 })
11896 }
11897
11898 pub fn select_to_end_of_paragraph(
11899 &mut self,
11900 _: &SelectToEndOfParagraph,
11901 window: &mut Window,
11902 cx: &mut Context<Self>,
11903 ) {
11904 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11905 cx.propagate();
11906 return;
11907 }
11908 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11909 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11910 s.move_heads_with(|map, head, _| {
11911 (
11912 movement::end_of_paragraph(map, head, 1),
11913 SelectionGoal::None,
11914 )
11915 });
11916 })
11917 }
11918
11919 pub fn move_to_start_of_excerpt(
11920 &mut self,
11921 _: &MoveToStartOfExcerpt,
11922 window: &mut Window,
11923 cx: &mut Context<Self>,
11924 ) {
11925 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11926 cx.propagate();
11927 return;
11928 }
11929 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11930 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11931 s.move_with(|map, selection| {
11932 selection.collapse_to(
11933 movement::start_of_excerpt(
11934 map,
11935 selection.head(),
11936 workspace::searchable::Direction::Prev,
11937 ),
11938 SelectionGoal::None,
11939 )
11940 });
11941 })
11942 }
11943
11944 pub fn move_to_start_of_next_excerpt(
11945 &mut self,
11946 _: &MoveToStartOfNextExcerpt,
11947 window: &mut Window,
11948 cx: &mut Context<Self>,
11949 ) {
11950 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11951 cx.propagate();
11952 return;
11953 }
11954
11955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11956 s.move_with(|map, selection| {
11957 selection.collapse_to(
11958 movement::start_of_excerpt(
11959 map,
11960 selection.head(),
11961 workspace::searchable::Direction::Next,
11962 ),
11963 SelectionGoal::None,
11964 )
11965 });
11966 })
11967 }
11968
11969 pub fn move_to_end_of_excerpt(
11970 &mut self,
11971 _: &MoveToEndOfExcerpt,
11972 window: &mut Window,
11973 cx: &mut Context<Self>,
11974 ) {
11975 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11976 cx.propagate();
11977 return;
11978 }
11979 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11980 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11981 s.move_with(|map, selection| {
11982 selection.collapse_to(
11983 movement::end_of_excerpt(
11984 map,
11985 selection.head(),
11986 workspace::searchable::Direction::Next,
11987 ),
11988 SelectionGoal::None,
11989 )
11990 });
11991 })
11992 }
11993
11994 pub fn move_to_end_of_previous_excerpt(
11995 &mut self,
11996 _: &MoveToEndOfPreviousExcerpt,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12001 cx.propagate();
12002 return;
12003 }
12004 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12005 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12006 s.move_with(|map, selection| {
12007 selection.collapse_to(
12008 movement::end_of_excerpt(
12009 map,
12010 selection.head(),
12011 workspace::searchable::Direction::Prev,
12012 ),
12013 SelectionGoal::None,
12014 )
12015 });
12016 })
12017 }
12018
12019 pub fn select_to_start_of_excerpt(
12020 &mut self,
12021 _: &SelectToStartOfExcerpt,
12022 window: &mut Window,
12023 cx: &mut Context<Self>,
12024 ) {
12025 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12026 cx.propagate();
12027 return;
12028 }
12029 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12030 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12031 s.move_heads_with(|map, head, _| {
12032 (
12033 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12034 SelectionGoal::None,
12035 )
12036 });
12037 })
12038 }
12039
12040 pub fn select_to_start_of_next_excerpt(
12041 &mut self,
12042 _: &SelectToStartOfNextExcerpt,
12043 window: &mut Window,
12044 cx: &mut Context<Self>,
12045 ) {
12046 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12047 cx.propagate();
12048 return;
12049 }
12050 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12052 s.move_heads_with(|map, head, _| {
12053 (
12054 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12055 SelectionGoal::None,
12056 )
12057 });
12058 })
12059 }
12060
12061 pub fn select_to_end_of_excerpt(
12062 &mut self,
12063 _: &SelectToEndOfExcerpt,
12064 window: &mut Window,
12065 cx: &mut Context<Self>,
12066 ) {
12067 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12068 cx.propagate();
12069 return;
12070 }
12071 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12072 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12073 s.move_heads_with(|map, head, _| {
12074 (
12075 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12076 SelectionGoal::None,
12077 )
12078 });
12079 })
12080 }
12081
12082 pub fn select_to_end_of_previous_excerpt(
12083 &mut self,
12084 _: &SelectToEndOfPreviousExcerpt,
12085 window: &mut Window,
12086 cx: &mut Context<Self>,
12087 ) {
12088 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12089 cx.propagate();
12090 return;
12091 }
12092 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12093 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12094 s.move_heads_with(|map, head, _| {
12095 (
12096 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12097 SelectionGoal::None,
12098 )
12099 });
12100 })
12101 }
12102
12103 pub fn move_to_beginning(
12104 &mut self,
12105 _: &MoveToBeginning,
12106 window: &mut Window,
12107 cx: &mut Context<Self>,
12108 ) {
12109 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12110 cx.propagate();
12111 return;
12112 }
12113 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12114 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12115 s.select_ranges(vec![0..0]);
12116 });
12117 }
12118
12119 pub fn select_to_beginning(
12120 &mut self,
12121 _: &SelectToBeginning,
12122 window: &mut Window,
12123 cx: &mut Context<Self>,
12124 ) {
12125 let mut selection = self.selections.last::<Point>(cx);
12126 selection.set_head(Point::zero(), SelectionGoal::None);
12127 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12128 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12129 s.select(vec![selection]);
12130 });
12131 }
12132
12133 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12134 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12135 cx.propagate();
12136 return;
12137 }
12138 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12139 let cursor = self.buffer.read(cx).read(cx).len();
12140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.select_ranges(vec![cursor..cursor])
12142 });
12143 }
12144
12145 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12146 self.nav_history = nav_history;
12147 }
12148
12149 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12150 self.nav_history.as_ref()
12151 }
12152
12153 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12154 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12155 }
12156
12157 fn push_to_nav_history(
12158 &mut self,
12159 cursor_anchor: Anchor,
12160 new_position: Option<Point>,
12161 is_deactivate: bool,
12162 cx: &mut Context<Self>,
12163 ) {
12164 if let Some(nav_history) = self.nav_history.as_mut() {
12165 let buffer = self.buffer.read(cx).read(cx);
12166 let cursor_position = cursor_anchor.to_point(&buffer);
12167 let scroll_state = self.scroll_manager.anchor();
12168 let scroll_top_row = scroll_state.top_row(&buffer);
12169 drop(buffer);
12170
12171 if let Some(new_position) = new_position {
12172 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12173 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12174 return;
12175 }
12176 }
12177
12178 nav_history.push(
12179 Some(NavigationData {
12180 cursor_anchor,
12181 cursor_position,
12182 scroll_anchor: scroll_state,
12183 scroll_top_row,
12184 }),
12185 cx,
12186 );
12187 cx.emit(EditorEvent::PushedToNavHistory {
12188 anchor: cursor_anchor,
12189 is_deactivate,
12190 })
12191 }
12192 }
12193
12194 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12195 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12196 let buffer = self.buffer.read(cx).snapshot(cx);
12197 let mut selection = self.selections.first::<usize>(cx);
12198 selection.set_head(buffer.len(), SelectionGoal::None);
12199 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12200 s.select(vec![selection]);
12201 });
12202 }
12203
12204 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12205 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12206 let end = self.buffer.read(cx).read(cx).len();
12207 self.change_selections(None, window, cx, |s| {
12208 s.select_ranges(vec![0..end]);
12209 });
12210 }
12211
12212 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12213 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12214 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12215 let mut selections = self.selections.all::<Point>(cx);
12216 let max_point = display_map.buffer_snapshot.max_point();
12217 for selection in &mut selections {
12218 let rows = selection.spanned_rows(true, &display_map);
12219 selection.start = Point::new(rows.start.0, 0);
12220 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12221 selection.reversed = false;
12222 }
12223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12224 s.select(selections);
12225 });
12226 }
12227
12228 pub fn split_selection_into_lines(
12229 &mut self,
12230 _: &SplitSelectionIntoLines,
12231 window: &mut Window,
12232 cx: &mut Context<Self>,
12233 ) {
12234 let selections = self
12235 .selections
12236 .all::<Point>(cx)
12237 .into_iter()
12238 .map(|selection| selection.start..selection.end)
12239 .collect::<Vec<_>>();
12240 self.unfold_ranges(&selections, true, true, cx);
12241
12242 let mut new_selection_ranges = Vec::new();
12243 {
12244 let buffer = self.buffer.read(cx).read(cx);
12245 for selection in selections {
12246 for row in selection.start.row..selection.end.row {
12247 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12248 new_selection_ranges.push(cursor..cursor);
12249 }
12250
12251 let is_multiline_selection = selection.start.row != selection.end.row;
12252 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12253 // so this action feels more ergonomic when paired with other selection operations
12254 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12255 if !should_skip_last {
12256 new_selection_ranges.push(selection.end..selection.end);
12257 }
12258 }
12259 }
12260 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12261 s.select_ranges(new_selection_ranges);
12262 });
12263 }
12264
12265 pub fn add_selection_above(
12266 &mut self,
12267 _: &AddSelectionAbove,
12268 window: &mut Window,
12269 cx: &mut Context<Self>,
12270 ) {
12271 self.add_selection(true, window, cx);
12272 }
12273
12274 pub fn add_selection_below(
12275 &mut self,
12276 _: &AddSelectionBelow,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 self.add_selection(false, window, cx);
12281 }
12282
12283 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12284 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12285
12286 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12287 let mut selections = self.selections.all::<Point>(cx);
12288 let text_layout_details = self.text_layout_details(window);
12289 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12290 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12291 let range = oldest_selection.display_range(&display_map).sorted();
12292
12293 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12294 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12295 let positions = start_x.min(end_x)..start_x.max(end_x);
12296
12297 selections.clear();
12298 let mut stack = Vec::new();
12299 for row in range.start.row().0..=range.end.row().0 {
12300 if let Some(selection) = self.selections.build_columnar_selection(
12301 &display_map,
12302 DisplayRow(row),
12303 &positions,
12304 oldest_selection.reversed,
12305 &text_layout_details,
12306 ) {
12307 stack.push(selection.id);
12308 selections.push(selection);
12309 }
12310 }
12311
12312 if above {
12313 stack.reverse();
12314 }
12315
12316 AddSelectionsState { above, stack }
12317 });
12318
12319 let last_added_selection = *state.stack.last().unwrap();
12320 let mut new_selections = Vec::new();
12321 if above == state.above {
12322 let end_row = if above {
12323 DisplayRow(0)
12324 } else {
12325 display_map.max_point().row()
12326 };
12327
12328 'outer: for selection in selections {
12329 if selection.id == last_added_selection {
12330 let range = selection.display_range(&display_map).sorted();
12331 debug_assert_eq!(range.start.row(), range.end.row());
12332 let mut row = range.start.row();
12333 let positions =
12334 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12335 px(start)..px(end)
12336 } else {
12337 let start_x =
12338 display_map.x_for_display_point(range.start, &text_layout_details);
12339 let end_x =
12340 display_map.x_for_display_point(range.end, &text_layout_details);
12341 start_x.min(end_x)..start_x.max(end_x)
12342 };
12343
12344 while row != end_row {
12345 if above {
12346 row.0 -= 1;
12347 } else {
12348 row.0 += 1;
12349 }
12350
12351 if let Some(new_selection) = self.selections.build_columnar_selection(
12352 &display_map,
12353 row,
12354 &positions,
12355 selection.reversed,
12356 &text_layout_details,
12357 ) {
12358 state.stack.push(new_selection.id);
12359 if above {
12360 new_selections.push(new_selection);
12361 new_selections.push(selection);
12362 } else {
12363 new_selections.push(selection);
12364 new_selections.push(new_selection);
12365 }
12366
12367 continue 'outer;
12368 }
12369 }
12370 }
12371
12372 new_selections.push(selection);
12373 }
12374 } else {
12375 new_selections = selections;
12376 new_selections.retain(|s| s.id != last_added_selection);
12377 state.stack.pop();
12378 }
12379
12380 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12381 s.select(new_selections);
12382 });
12383 if state.stack.len() > 1 {
12384 self.add_selections_state = Some(state);
12385 }
12386 }
12387
12388 fn select_match_ranges(
12389 &mut self,
12390 range: Range<usize>,
12391 reversed: bool,
12392 replace_newest: bool,
12393 auto_scroll: Option<Autoscroll>,
12394 window: &mut Window,
12395 cx: &mut Context<Editor>,
12396 ) {
12397 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12398 self.change_selections(auto_scroll, window, cx, |s| {
12399 if replace_newest {
12400 s.delete(s.newest_anchor().id);
12401 }
12402 if reversed {
12403 s.insert_range(range.end..range.start);
12404 } else {
12405 s.insert_range(range);
12406 }
12407 });
12408 }
12409
12410 pub fn select_next_match_internal(
12411 &mut self,
12412 display_map: &DisplaySnapshot,
12413 replace_newest: bool,
12414 autoscroll: Option<Autoscroll>,
12415 window: &mut Window,
12416 cx: &mut Context<Self>,
12417 ) -> Result<()> {
12418 let buffer = &display_map.buffer_snapshot;
12419 let mut selections = self.selections.all::<usize>(cx);
12420 if let Some(mut select_next_state) = self.select_next_state.take() {
12421 let query = &select_next_state.query;
12422 if !select_next_state.done {
12423 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12424 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12425 let mut next_selected_range = None;
12426
12427 let bytes_after_last_selection =
12428 buffer.bytes_in_range(last_selection.end..buffer.len());
12429 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12430 let query_matches = query
12431 .stream_find_iter(bytes_after_last_selection)
12432 .map(|result| (last_selection.end, result))
12433 .chain(
12434 query
12435 .stream_find_iter(bytes_before_first_selection)
12436 .map(|result| (0, result)),
12437 );
12438
12439 for (start_offset, query_match) in query_matches {
12440 let query_match = query_match.unwrap(); // can only fail due to I/O
12441 let offset_range =
12442 start_offset + query_match.start()..start_offset + query_match.end();
12443 let display_range = offset_range.start.to_display_point(display_map)
12444 ..offset_range.end.to_display_point(display_map);
12445
12446 if !select_next_state.wordwise
12447 || (!movement::is_inside_word(display_map, display_range.start)
12448 && !movement::is_inside_word(display_map, display_range.end))
12449 {
12450 // TODO: This is n^2, because we might check all the selections
12451 if !selections
12452 .iter()
12453 .any(|selection| selection.range().overlaps(&offset_range))
12454 {
12455 next_selected_range = Some(offset_range);
12456 break;
12457 }
12458 }
12459 }
12460
12461 if let Some(next_selected_range) = next_selected_range {
12462 self.select_match_ranges(
12463 next_selected_range,
12464 last_selection.reversed,
12465 replace_newest,
12466 autoscroll,
12467 window,
12468 cx,
12469 );
12470 } else {
12471 select_next_state.done = true;
12472 }
12473 }
12474
12475 self.select_next_state = Some(select_next_state);
12476 } else {
12477 let mut only_carets = true;
12478 let mut same_text_selected = true;
12479 let mut selected_text = None;
12480
12481 let mut selections_iter = selections.iter().peekable();
12482 while let Some(selection) = selections_iter.next() {
12483 if selection.start != selection.end {
12484 only_carets = false;
12485 }
12486
12487 if same_text_selected {
12488 if selected_text.is_none() {
12489 selected_text =
12490 Some(buffer.text_for_range(selection.range()).collect::<String>());
12491 }
12492
12493 if let Some(next_selection) = selections_iter.peek() {
12494 if next_selection.range().len() == selection.range().len() {
12495 let next_selected_text = buffer
12496 .text_for_range(next_selection.range())
12497 .collect::<String>();
12498 if Some(next_selected_text) != selected_text {
12499 same_text_selected = false;
12500 selected_text = None;
12501 }
12502 } else {
12503 same_text_selected = false;
12504 selected_text = None;
12505 }
12506 }
12507 }
12508 }
12509
12510 if only_carets {
12511 for selection in &mut selections {
12512 let word_range = movement::surrounding_word(
12513 display_map,
12514 selection.start.to_display_point(display_map),
12515 );
12516 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12517 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12518 selection.goal = SelectionGoal::None;
12519 selection.reversed = false;
12520 self.select_match_ranges(
12521 selection.start..selection.end,
12522 selection.reversed,
12523 replace_newest,
12524 autoscroll,
12525 window,
12526 cx,
12527 );
12528 }
12529
12530 if selections.len() == 1 {
12531 let selection = selections
12532 .last()
12533 .expect("ensured that there's only one selection");
12534 let query = buffer
12535 .text_for_range(selection.start..selection.end)
12536 .collect::<String>();
12537 let is_empty = query.is_empty();
12538 let select_state = SelectNextState {
12539 query: AhoCorasick::new(&[query])?,
12540 wordwise: true,
12541 done: is_empty,
12542 };
12543 self.select_next_state = Some(select_state);
12544 } else {
12545 self.select_next_state = None;
12546 }
12547 } else if let Some(selected_text) = selected_text {
12548 self.select_next_state = Some(SelectNextState {
12549 query: AhoCorasick::new(&[selected_text])?,
12550 wordwise: false,
12551 done: false,
12552 });
12553 self.select_next_match_internal(
12554 display_map,
12555 replace_newest,
12556 autoscroll,
12557 window,
12558 cx,
12559 )?;
12560 }
12561 }
12562 Ok(())
12563 }
12564
12565 pub fn select_all_matches(
12566 &mut self,
12567 _action: &SelectAllMatches,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 ) -> Result<()> {
12571 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12572
12573 self.push_to_selection_history();
12574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12575
12576 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12577 let Some(select_next_state) = self.select_next_state.as_mut() else {
12578 return Ok(());
12579 };
12580 if select_next_state.done {
12581 return Ok(());
12582 }
12583
12584 let mut new_selections = Vec::new();
12585
12586 let reversed = self.selections.oldest::<usize>(cx).reversed;
12587 let buffer = &display_map.buffer_snapshot;
12588 let query_matches = select_next_state
12589 .query
12590 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12591
12592 for query_match in query_matches.into_iter() {
12593 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12594 let offset_range = if reversed {
12595 query_match.end()..query_match.start()
12596 } else {
12597 query_match.start()..query_match.end()
12598 };
12599 let display_range = offset_range.start.to_display_point(&display_map)
12600 ..offset_range.end.to_display_point(&display_map);
12601
12602 if !select_next_state.wordwise
12603 || (!movement::is_inside_word(&display_map, display_range.start)
12604 && !movement::is_inside_word(&display_map, display_range.end))
12605 {
12606 new_selections.push(offset_range.start..offset_range.end);
12607 }
12608 }
12609
12610 select_next_state.done = true;
12611 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12612 self.change_selections(None, window, cx, |selections| {
12613 selections.select_ranges(new_selections)
12614 });
12615
12616 Ok(())
12617 }
12618
12619 pub fn select_next(
12620 &mut self,
12621 action: &SelectNext,
12622 window: &mut Window,
12623 cx: &mut Context<Self>,
12624 ) -> Result<()> {
12625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12626 self.push_to_selection_history();
12627 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12628 self.select_next_match_internal(
12629 &display_map,
12630 action.replace_newest,
12631 Some(Autoscroll::newest()),
12632 window,
12633 cx,
12634 )?;
12635 Ok(())
12636 }
12637
12638 pub fn select_previous(
12639 &mut self,
12640 action: &SelectPrevious,
12641 window: &mut Window,
12642 cx: &mut Context<Self>,
12643 ) -> Result<()> {
12644 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12645 self.push_to_selection_history();
12646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12647 let buffer = &display_map.buffer_snapshot;
12648 let mut selections = self.selections.all::<usize>(cx);
12649 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12650 let query = &select_prev_state.query;
12651 if !select_prev_state.done {
12652 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12653 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12654 let mut next_selected_range = None;
12655 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12656 let bytes_before_last_selection =
12657 buffer.reversed_bytes_in_range(0..last_selection.start);
12658 let bytes_after_first_selection =
12659 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12660 let query_matches = query
12661 .stream_find_iter(bytes_before_last_selection)
12662 .map(|result| (last_selection.start, result))
12663 .chain(
12664 query
12665 .stream_find_iter(bytes_after_first_selection)
12666 .map(|result| (buffer.len(), result)),
12667 );
12668 for (end_offset, query_match) in query_matches {
12669 let query_match = query_match.unwrap(); // can only fail due to I/O
12670 let offset_range =
12671 end_offset - query_match.end()..end_offset - query_match.start();
12672 let display_range = offset_range.start.to_display_point(&display_map)
12673 ..offset_range.end.to_display_point(&display_map);
12674
12675 if !select_prev_state.wordwise
12676 || (!movement::is_inside_word(&display_map, display_range.start)
12677 && !movement::is_inside_word(&display_map, display_range.end))
12678 {
12679 next_selected_range = Some(offset_range);
12680 break;
12681 }
12682 }
12683
12684 if let Some(next_selected_range) = next_selected_range {
12685 self.select_match_ranges(
12686 next_selected_range,
12687 last_selection.reversed,
12688 action.replace_newest,
12689 Some(Autoscroll::newest()),
12690 window,
12691 cx,
12692 );
12693 } else {
12694 select_prev_state.done = true;
12695 }
12696 }
12697
12698 self.select_prev_state = Some(select_prev_state);
12699 } else {
12700 let mut only_carets = true;
12701 let mut same_text_selected = true;
12702 let mut selected_text = None;
12703
12704 let mut selections_iter = selections.iter().peekable();
12705 while let Some(selection) = selections_iter.next() {
12706 if selection.start != selection.end {
12707 only_carets = false;
12708 }
12709
12710 if same_text_selected {
12711 if selected_text.is_none() {
12712 selected_text =
12713 Some(buffer.text_for_range(selection.range()).collect::<String>());
12714 }
12715
12716 if let Some(next_selection) = selections_iter.peek() {
12717 if next_selection.range().len() == selection.range().len() {
12718 let next_selected_text = buffer
12719 .text_for_range(next_selection.range())
12720 .collect::<String>();
12721 if Some(next_selected_text) != selected_text {
12722 same_text_selected = false;
12723 selected_text = None;
12724 }
12725 } else {
12726 same_text_selected = false;
12727 selected_text = None;
12728 }
12729 }
12730 }
12731 }
12732
12733 if only_carets {
12734 for selection in &mut selections {
12735 let word_range = movement::surrounding_word(
12736 &display_map,
12737 selection.start.to_display_point(&display_map),
12738 );
12739 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12740 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12741 selection.goal = SelectionGoal::None;
12742 selection.reversed = false;
12743 self.select_match_ranges(
12744 selection.start..selection.end,
12745 selection.reversed,
12746 action.replace_newest,
12747 Some(Autoscroll::newest()),
12748 window,
12749 cx,
12750 );
12751 }
12752 if selections.len() == 1 {
12753 let selection = selections
12754 .last()
12755 .expect("ensured that there's only one selection");
12756 let query = buffer
12757 .text_for_range(selection.start..selection.end)
12758 .collect::<String>();
12759 let is_empty = query.is_empty();
12760 let select_state = SelectNextState {
12761 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12762 wordwise: true,
12763 done: is_empty,
12764 };
12765 self.select_prev_state = Some(select_state);
12766 } else {
12767 self.select_prev_state = None;
12768 }
12769 } else if let Some(selected_text) = selected_text {
12770 self.select_prev_state = Some(SelectNextState {
12771 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12772 wordwise: false,
12773 done: false,
12774 });
12775 self.select_previous(action, window, cx)?;
12776 }
12777 }
12778 Ok(())
12779 }
12780
12781 pub fn find_next_match(
12782 &mut self,
12783 _: &FindNextMatch,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) -> Result<()> {
12787 let selections = self.selections.disjoint_anchors();
12788 match selections.first() {
12789 Some(first) if selections.len() >= 2 => {
12790 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12791 s.select_ranges([first.range()]);
12792 });
12793 }
12794 _ => self.select_next(
12795 &SelectNext {
12796 replace_newest: true,
12797 },
12798 window,
12799 cx,
12800 )?,
12801 }
12802 Ok(())
12803 }
12804
12805 pub fn find_previous_match(
12806 &mut self,
12807 _: &FindPreviousMatch,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) -> Result<()> {
12811 let selections = self.selections.disjoint_anchors();
12812 match selections.last() {
12813 Some(last) if selections.len() >= 2 => {
12814 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12815 s.select_ranges([last.range()]);
12816 });
12817 }
12818 _ => self.select_previous(
12819 &SelectPrevious {
12820 replace_newest: true,
12821 },
12822 window,
12823 cx,
12824 )?,
12825 }
12826 Ok(())
12827 }
12828
12829 pub fn toggle_comments(
12830 &mut self,
12831 action: &ToggleComments,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) {
12835 if self.read_only(cx) {
12836 return;
12837 }
12838 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12839 let text_layout_details = &self.text_layout_details(window);
12840 self.transact(window, cx, |this, window, cx| {
12841 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12842 let mut edits = Vec::new();
12843 let mut selection_edit_ranges = Vec::new();
12844 let mut last_toggled_row = None;
12845 let snapshot = this.buffer.read(cx).read(cx);
12846 let empty_str: Arc<str> = Arc::default();
12847 let mut suffixes_inserted = Vec::new();
12848 let ignore_indent = action.ignore_indent;
12849
12850 fn comment_prefix_range(
12851 snapshot: &MultiBufferSnapshot,
12852 row: MultiBufferRow,
12853 comment_prefix: &str,
12854 comment_prefix_whitespace: &str,
12855 ignore_indent: bool,
12856 ) -> Range<Point> {
12857 let indent_size = if ignore_indent {
12858 0
12859 } else {
12860 snapshot.indent_size_for_line(row).len
12861 };
12862
12863 let start = Point::new(row.0, indent_size);
12864
12865 let mut line_bytes = snapshot
12866 .bytes_in_range(start..snapshot.max_point())
12867 .flatten()
12868 .copied();
12869
12870 // If this line currently begins with the line comment prefix, then record
12871 // the range containing the prefix.
12872 if line_bytes
12873 .by_ref()
12874 .take(comment_prefix.len())
12875 .eq(comment_prefix.bytes())
12876 {
12877 // Include any whitespace that matches the comment prefix.
12878 let matching_whitespace_len = line_bytes
12879 .zip(comment_prefix_whitespace.bytes())
12880 .take_while(|(a, b)| a == b)
12881 .count() as u32;
12882 let end = Point::new(
12883 start.row,
12884 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12885 );
12886 start..end
12887 } else {
12888 start..start
12889 }
12890 }
12891
12892 fn comment_suffix_range(
12893 snapshot: &MultiBufferSnapshot,
12894 row: MultiBufferRow,
12895 comment_suffix: &str,
12896 comment_suffix_has_leading_space: bool,
12897 ) -> Range<Point> {
12898 let end = Point::new(row.0, snapshot.line_len(row));
12899 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12900
12901 let mut line_end_bytes = snapshot
12902 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12903 .flatten()
12904 .copied();
12905
12906 let leading_space_len = if suffix_start_column > 0
12907 && line_end_bytes.next() == Some(b' ')
12908 && comment_suffix_has_leading_space
12909 {
12910 1
12911 } else {
12912 0
12913 };
12914
12915 // If this line currently begins with the line comment prefix, then record
12916 // the range containing the prefix.
12917 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12918 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12919 start..end
12920 } else {
12921 end..end
12922 }
12923 }
12924
12925 // TODO: Handle selections that cross excerpts
12926 for selection in &mut selections {
12927 let start_column = snapshot
12928 .indent_size_for_line(MultiBufferRow(selection.start.row))
12929 .len;
12930 let language = if let Some(language) =
12931 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12932 {
12933 language
12934 } else {
12935 continue;
12936 };
12937
12938 selection_edit_ranges.clear();
12939
12940 // If multiple selections contain a given row, avoid processing that
12941 // row more than once.
12942 let mut start_row = MultiBufferRow(selection.start.row);
12943 if last_toggled_row == Some(start_row) {
12944 start_row = start_row.next_row();
12945 }
12946 let end_row =
12947 if selection.end.row > selection.start.row && selection.end.column == 0 {
12948 MultiBufferRow(selection.end.row - 1)
12949 } else {
12950 MultiBufferRow(selection.end.row)
12951 };
12952 last_toggled_row = Some(end_row);
12953
12954 if start_row > end_row {
12955 continue;
12956 }
12957
12958 // If the language has line comments, toggle those.
12959 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12960
12961 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12962 if ignore_indent {
12963 full_comment_prefixes = full_comment_prefixes
12964 .into_iter()
12965 .map(|s| Arc::from(s.trim_end()))
12966 .collect();
12967 }
12968
12969 if !full_comment_prefixes.is_empty() {
12970 let first_prefix = full_comment_prefixes
12971 .first()
12972 .expect("prefixes is non-empty");
12973 let prefix_trimmed_lengths = full_comment_prefixes
12974 .iter()
12975 .map(|p| p.trim_end_matches(' ').len())
12976 .collect::<SmallVec<[usize; 4]>>();
12977
12978 let mut all_selection_lines_are_comments = true;
12979
12980 for row in start_row.0..=end_row.0 {
12981 let row = MultiBufferRow(row);
12982 if start_row < end_row && snapshot.is_line_blank(row) {
12983 continue;
12984 }
12985
12986 let prefix_range = full_comment_prefixes
12987 .iter()
12988 .zip(prefix_trimmed_lengths.iter().copied())
12989 .map(|(prefix, trimmed_prefix_len)| {
12990 comment_prefix_range(
12991 snapshot.deref(),
12992 row,
12993 &prefix[..trimmed_prefix_len],
12994 &prefix[trimmed_prefix_len..],
12995 ignore_indent,
12996 )
12997 })
12998 .max_by_key(|range| range.end.column - range.start.column)
12999 .expect("prefixes is non-empty");
13000
13001 if prefix_range.is_empty() {
13002 all_selection_lines_are_comments = false;
13003 }
13004
13005 selection_edit_ranges.push(prefix_range);
13006 }
13007
13008 if all_selection_lines_are_comments {
13009 edits.extend(
13010 selection_edit_ranges
13011 .iter()
13012 .cloned()
13013 .map(|range| (range, empty_str.clone())),
13014 );
13015 } else {
13016 let min_column = selection_edit_ranges
13017 .iter()
13018 .map(|range| range.start.column)
13019 .min()
13020 .unwrap_or(0);
13021 edits.extend(selection_edit_ranges.iter().map(|range| {
13022 let position = Point::new(range.start.row, min_column);
13023 (position..position, first_prefix.clone())
13024 }));
13025 }
13026 } else if let Some((full_comment_prefix, comment_suffix)) =
13027 language.block_comment_delimiters()
13028 {
13029 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13030 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13031 let prefix_range = comment_prefix_range(
13032 snapshot.deref(),
13033 start_row,
13034 comment_prefix,
13035 comment_prefix_whitespace,
13036 ignore_indent,
13037 );
13038 let suffix_range = comment_suffix_range(
13039 snapshot.deref(),
13040 end_row,
13041 comment_suffix.trim_start_matches(' '),
13042 comment_suffix.starts_with(' '),
13043 );
13044
13045 if prefix_range.is_empty() || suffix_range.is_empty() {
13046 edits.push((
13047 prefix_range.start..prefix_range.start,
13048 full_comment_prefix.clone(),
13049 ));
13050 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13051 suffixes_inserted.push((end_row, comment_suffix.len()));
13052 } else {
13053 edits.push((prefix_range, empty_str.clone()));
13054 edits.push((suffix_range, empty_str.clone()));
13055 }
13056 } else {
13057 continue;
13058 }
13059 }
13060
13061 drop(snapshot);
13062 this.buffer.update(cx, |buffer, cx| {
13063 buffer.edit(edits, None, cx);
13064 });
13065
13066 // Adjust selections so that they end before any comment suffixes that
13067 // were inserted.
13068 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13069 let mut selections = this.selections.all::<Point>(cx);
13070 let snapshot = this.buffer.read(cx).read(cx);
13071 for selection in &mut selections {
13072 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13073 match row.cmp(&MultiBufferRow(selection.end.row)) {
13074 Ordering::Less => {
13075 suffixes_inserted.next();
13076 continue;
13077 }
13078 Ordering::Greater => break,
13079 Ordering::Equal => {
13080 if selection.end.column == snapshot.line_len(row) {
13081 if selection.is_empty() {
13082 selection.start.column -= suffix_len as u32;
13083 }
13084 selection.end.column -= suffix_len as u32;
13085 }
13086 break;
13087 }
13088 }
13089 }
13090 }
13091
13092 drop(snapshot);
13093 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13094 s.select(selections)
13095 });
13096
13097 let selections = this.selections.all::<Point>(cx);
13098 let selections_on_single_row = selections.windows(2).all(|selections| {
13099 selections[0].start.row == selections[1].start.row
13100 && selections[0].end.row == selections[1].end.row
13101 && selections[0].start.row == selections[0].end.row
13102 });
13103 let selections_selecting = selections
13104 .iter()
13105 .any(|selection| selection.start != selection.end);
13106 let advance_downwards = action.advance_downwards
13107 && selections_on_single_row
13108 && !selections_selecting
13109 && !matches!(this.mode, EditorMode::SingleLine { .. });
13110
13111 if advance_downwards {
13112 let snapshot = this.buffer.read(cx).snapshot(cx);
13113
13114 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13115 s.move_cursors_with(|display_snapshot, display_point, _| {
13116 let mut point = display_point.to_point(display_snapshot);
13117 point.row += 1;
13118 point = snapshot.clip_point(point, Bias::Left);
13119 let display_point = point.to_display_point(display_snapshot);
13120 let goal = SelectionGoal::HorizontalPosition(
13121 display_snapshot
13122 .x_for_display_point(display_point, text_layout_details)
13123 .into(),
13124 );
13125 (display_point, goal)
13126 })
13127 });
13128 }
13129 });
13130 }
13131
13132 pub fn select_enclosing_symbol(
13133 &mut self,
13134 _: &SelectEnclosingSymbol,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13139
13140 let buffer = self.buffer.read(cx).snapshot(cx);
13141 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13142
13143 fn update_selection(
13144 selection: &Selection<usize>,
13145 buffer_snap: &MultiBufferSnapshot,
13146 ) -> Option<Selection<usize>> {
13147 let cursor = selection.head();
13148 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13149 for symbol in symbols.iter().rev() {
13150 let start = symbol.range.start.to_offset(buffer_snap);
13151 let end = symbol.range.end.to_offset(buffer_snap);
13152 let new_range = start..end;
13153 if start < selection.start || end > selection.end {
13154 return Some(Selection {
13155 id: selection.id,
13156 start: new_range.start,
13157 end: new_range.end,
13158 goal: SelectionGoal::None,
13159 reversed: selection.reversed,
13160 });
13161 }
13162 }
13163 None
13164 }
13165
13166 let mut selected_larger_symbol = false;
13167 let new_selections = old_selections
13168 .iter()
13169 .map(|selection| match update_selection(selection, &buffer) {
13170 Some(new_selection) => {
13171 if new_selection.range() != selection.range() {
13172 selected_larger_symbol = true;
13173 }
13174 new_selection
13175 }
13176 None => selection.clone(),
13177 })
13178 .collect::<Vec<_>>();
13179
13180 if selected_larger_symbol {
13181 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13182 s.select(new_selections);
13183 });
13184 }
13185 }
13186
13187 pub fn select_larger_syntax_node(
13188 &mut self,
13189 _: &SelectLargerSyntaxNode,
13190 window: &mut Window,
13191 cx: &mut Context<Self>,
13192 ) {
13193 let Some(visible_row_count) = self.visible_row_count() else {
13194 return;
13195 };
13196 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13197 if old_selections.is_empty() {
13198 return;
13199 }
13200
13201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13202
13203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13204 let buffer = self.buffer.read(cx).snapshot(cx);
13205
13206 let mut selected_larger_node = false;
13207 let mut new_selections = old_selections
13208 .iter()
13209 .map(|selection| {
13210 let old_range = selection.start..selection.end;
13211
13212 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13213 // manually select word at selection
13214 if ["string_content", "inline"].contains(&node.kind()) {
13215 let word_range = {
13216 let display_point = buffer
13217 .offset_to_point(old_range.start)
13218 .to_display_point(&display_map);
13219 let Range { start, end } =
13220 movement::surrounding_word(&display_map, display_point);
13221 start.to_point(&display_map).to_offset(&buffer)
13222 ..end.to_point(&display_map).to_offset(&buffer)
13223 };
13224 // ignore if word is already selected
13225 if !word_range.is_empty() && old_range != word_range {
13226 let last_word_range = {
13227 let display_point = buffer
13228 .offset_to_point(old_range.end)
13229 .to_display_point(&display_map);
13230 let Range { start, end } =
13231 movement::surrounding_word(&display_map, display_point);
13232 start.to_point(&display_map).to_offset(&buffer)
13233 ..end.to_point(&display_map).to_offset(&buffer)
13234 };
13235 // only select word if start and end point belongs to same word
13236 if word_range == last_word_range {
13237 selected_larger_node = true;
13238 return Selection {
13239 id: selection.id,
13240 start: word_range.start,
13241 end: word_range.end,
13242 goal: SelectionGoal::None,
13243 reversed: selection.reversed,
13244 };
13245 }
13246 }
13247 }
13248 }
13249
13250 let mut new_range = old_range.clone();
13251 while let Some((_node, containing_range)) =
13252 buffer.syntax_ancestor(new_range.clone())
13253 {
13254 new_range = match containing_range {
13255 MultiOrSingleBufferOffsetRange::Single(_) => break,
13256 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13257 };
13258 if !display_map.intersects_fold(new_range.start)
13259 && !display_map.intersects_fold(new_range.end)
13260 {
13261 break;
13262 }
13263 }
13264
13265 selected_larger_node |= new_range != old_range;
13266 Selection {
13267 id: selection.id,
13268 start: new_range.start,
13269 end: new_range.end,
13270 goal: SelectionGoal::None,
13271 reversed: selection.reversed,
13272 }
13273 })
13274 .collect::<Vec<_>>();
13275
13276 if !selected_larger_node {
13277 return; // don't put this call in the history
13278 }
13279
13280 // scroll based on transformation done to the last selection created by the user
13281 let (last_old, last_new) = old_selections
13282 .last()
13283 .zip(new_selections.last().cloned())
13284 .expect("old_selections isn't empty");
13285
13286 // revert selection
13287 let is_selection_reversed = {
13288 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13289 new_selections.last_mut().expect("checked above").reversed =
13290 should_newest_selection_be_reversed;
13291 should_newest_selection_be_reversed
13292 };
13293
13294 if selected_larger_node {
13295 self.select_syntax_node_history.disable_clearing = true;
13296 self.change_selections(None, window, cx, |s| {
13297 s.select(new_selections.clone());
13298 });
13299 self.select_syntax_node_history.disable_clearing = false;
13300 }
13301
13302 let start_row = last_new.start.to_display_point(&display_map).row().0;
13303 let end_row = last_new.end.to_display_point(&display_map).row().0;
13304 let selection_height = end_row - start_row + 1;
13305 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13306
13307 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13308 let scroll_behavior = if fits_on_the_screen {
13309 self.request_autoscroll(Autoscroll::fit(), cx);
13310 SelectSyntaxNodeScrollBehavior::FitSelection
13311 } else if is_selection_reversed {
13312 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13313 SelectSyntaxNodeScrollBehavior::CursorTop
13314 } else {
13315 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13316 SelectSyntaxNodeScrollBehavior::CursorBottom
13317 };
13318
13319 self.select_syntax_node_history.push((
13320 old_selections,
13321 scroll_behavior,
13322 is_selection_reversed,
13323 ));
13324 }
13325
13326 pub fn select_smaller_syntax_node(
13327 &mut self,
13328 _: &SelectSmallerSyntaxNode,
13329 window: &mut Window,
13330 cx: &mut Context<Self>,
13331 ) {
13332 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13333
13334 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13335 self.select_syntax_node_history.pop()
13336 {
13337 if let Some(selection) = selections.last_mut() {
13338 selection.reversed = is_selection_reversed;
13339 }
13340
13341 self.select_syntax_node_history.disable_clearing = true;
13342 self.change_selections(None, window, cx, |s| {
13343 s.select(selections.to_vec());
13344 });
13345 self.select_syntax_node_history.disable_clearing = false;
13346
13347 match scroll_behavior {
13348 SelectSyntaxNodeScrollBehavior::CursorTop => {
13349 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13350 }
13351 SelectSyntaxNodeScrollBehavior::FitSelection => {
13352 self.request_autoscroll(Autoscroll::fit(), cx);
13353 }
13354 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13355 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13356 }
13357 }
13358 }
13359 }
13360
13361 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13362 if !EditorSettings::get_global(cx).gutter.runnables {
13363 self.clear_tasks();
13364 return Task::ready(());
13365 }
13366 let project = self.project.as_ref().map(Entity::downgrade);
13367 let task_sources = self.lsp_task_sources(cx);
13368 cx.spawn_in(window, async move |editor, cx| {
13369 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13370 let Some(project) = project.and_then(|p| p.upgrade()) else {
13371 return;
13372 };
13373 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13374 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13375 }) else {
13376 return;
13377 };
13378
13379 let hide_runnables = project
13380 .update(cx, |project, cx| {
13381 // Do not display any test indicators in non-dev server remote projects.
13382 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13383 })
13384 .unwrap_or(true);
13385 if hide_runnables {
13386 return;
13387 }
13388 let new_rows =
13389 cx.background_spawn({
13390 let snapshot = display_snapshot.clone();
13391 async move {
13392 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13393 }
13394 })
13395 .await;
13396 let Ok(lsp_tasks) =
13397 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13398 else {
13399 return;
13400 };
13401 let lsp_tasks = lsp_tasks.await;
13402
13403 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13404 lsp_tasks
13405 .into_iter()
13406 .flat_map(|(kind, tasks)| {
13407 tasks.into_iter().filter_map(move |(location, task)| {
13408 Some((kind.clone(), location?, task))
13409 })
13410 })
13411 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13412 let buffer = location.target.buffer;
13413 let buffer_snapshot = buffer.read(cx).snapshot();
13414 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13415 |(excerpt_id, snapshot, _)| {
13416 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13417 display_snapshot
13418 .buffer_snapshot
13419 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13420 } else {
13421 None
13422 }
13423 },
13424 );
13425 if let Some(offset) = offset {
13426 let task_buffer_range =
13427 location.target.range.to_point(&buffer_snapshot);
13428 let context_buffer_range =
13429 task_buffer_range.to_offset(&buffer_snapshot);
13430 let context_range = BufferOffset(context_buffer_range.start)
13431 ..BufferOffset(context_buffer_range.end);
13432
13433 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13434 .or_insert_with(|| RunnableTasks {
13435 templates: Vec::new(),
13436 offset,
13437 column: task_buffer_range.start.column,
13438 extra_variables: HashMap::default(),
13439 context_range,
13440 })
13441 .templates
13442 .push((kind, task.original_task().clone()));
13443 }
13444
13445 acc
13446 })
13447 }) else {
13448 return;
13449 };
13450
13451 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13452 editor
13453 .update(cx, |editor, _| {
13454 editor.clear_tasks();
13455 for (key, mut value) in rows {
13456 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13457 value.templates.extend(lsp_tasks.templates);
13458 }
13459
13460 editor.insert_tasks(key, value);
13461 }
13462 for (key, value) in lsp_tasks_by_rows {
13463 editor.insert_tasks(key, value);
13464 }
13465 })
13466 .ok();
13467 })
13468 }
13469 fn fetch_runnable_ranges(
13470 snapshot: &DisplaySnapshot,
13471 range: Range<Anchor>,
13472 ) -> Vec<language::RunnableRange> {
13473 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13474 }
13475
13476 fn runnable_rows(
13477 project: Entity<Project>,
13478 snapshot: DisplaySnapshot,
13479 runnable_ranges: Vec<RunnableRange>,
13480 mut cx: AsyncWindowContext,
13481 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13482 runnable_ranges
13483 .into_iter()
13484 .filter_map(|mut runnable| {
13485 let tasks = cx
13486 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13487 .ok()?;
13488 if tasks.is_empty() {
13489 return None;
13490 }
13491
13492 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13493
13494 let row = snapshot
13495 .buffer_snapshot
13496 .buffer_line_for_row(MultiBufferRow(point.row))?
13497 .1
13498 .start
13499 .row;
13500
13501 let context_range =
13502 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13503 Some((
13504 (runnable.buffer_id, row),
13505 RunnableTasks {
13506 templates: tasks,
13507 offset: snapshot
13508 .buffer_snapshot
13509 .anchor_before(runnable.run_range.start),
13510 context_range,
13511 column: point.column,
13512 extra_variables: runnable.extra_captures,
13513 },
13514 ))
13515 })
13516 .collect()
13517 }
13518
13519 fn templates_with_tags(
13520 project: &Entity<Project>,
13521 runnable: &mut Runnable,
13522 cx: &mut App,
13523 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13524 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13525 let (worktree_id, file) = project
13526 .buffer_for_id(runnable.buffer, cx)
13527 .and_then(|buffer| buffer.read(cx).file())
13528 .map(|file| (file.worktree_id(cx), file.clone()))
13529 .unzip();
13530
13531 (
13532 project.task_store().read(cx).task_inventory().cloned(),
13533 worktree_id,
13534 file,
13535 )
13536 });
13537
13538 let mut templates_with_tags = mem::take(&mut runnable.tags)
13539 .into_iter()
13540 .flat_map(|RunnableTag(tag)| {
13541 inventory
13542 .as_ref()
13543 .into_iter()
13544 .flat_map(|inventory| {
13545 inventory.read(cx).list_tasks(
13546 file.clone(),
13547 Some(runnable.language.clone()),
13548 worktree_id,
13549 cx,
13550 )
13551 })
13552 .filter(move |(_, template)| {
13553 template.tags.iter().any(|source_tag| source_tag == &tag)
13554 })
13555 })
13556 .sorted_by_key(|(kind, _)| kind.to_owned())
13557 .collect::<Vec<_>>();
13558 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13559 // Strongest source wins; if we have worktree tag binding, prefer that to
13560 // global and language bindings;
13561 // if we have a global binding, prefer that to language binding.
13562 let first_mismatch = templates_with_tags
13563 .iter()
13564 .position(|(tag_source, _)| tag_source != leading_tag_source);
13565 if let Some(index) = first_mismatch {
13566 templates_with_tags.truncate(index);
13567 }
13568 }
13569
13570 templates_with_tags
13571 }
13572
13573 pub fn move_to_enclosing_bracket(
13574 &mut self,
13575 _: &MoveToEnclosingBracket,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13580 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13581 s.move_offsets_with(|snapshot, selection| {
13582 let Some(enclosing_bracket_ranges) =
13583 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13584 else {
13585 return;
13586 };
13587
13588 let mut best_length = usize::MAX;
13589 let mut best_inside = false;
13590 let mut best_in_bracket_range = false;
13591 let mut best_destination = None;
13592 for (open, close) in enclosing_bracket_ranges {
13593 let close = close.to_inclusive();
13594 let length = close.end() - open.start;
13595 let inside = selection.start >= open.end && selection.end <= *close.start();
13596 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13597 || close.contains(&selection.head());
13598
13599 // If best is next to a bracket and current isn't, skip
13600 if !in_bracket_range && best_in_bracket_range {
13601 continue;
13602 }
13603
13604 // Prefer smaller lengths unless best is inside and current isn't
13605 if length > best_length && (best_inside || !inside) {
13606 continue;
13607 }
13608
13609 best_length = length;
13610 best_inside = inside;
13611 best_in_bracket_range = in_bracket_range;
13612 best_destination = Some(
13613 if close.contains(&selection.start) && close.contains(&selection.end) {
13614 if inside { open.end } else { open.start }
13615 } else if inside {
13616 *close.start()
13617 } else {
13618 *close.end()
13619 },
13620 );
13621 }
13622
13623 if let Some(destination) = best_destination {
13624 selection.collapse_to(destination, SelectionGoal::None);
13625 }
13626 })
13627 });
13628 }
13629
13630 pub fn undo_selection(
13631 &mut self,
13632 _: &UndoSelection,
13633 window: &mut Window,
13634 cx: &mut Context<Self>,
13635 ) {
13636 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13637 self.end_selection(window, cx);
13638 self.selection_history.mode = SelectionHistoryMode::Undoing;
13639 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13640 self.change_selections(None, window, cx, |s| {
13641 s.select_anchors(entry.selections.to_vec())
13642 });
13643 self.select_next_state = entry.select_next_state;
13644 self.select_prev_state = entry.select_prev_state;
13645 self.add_selections_state = entry.add_selections_state;
13646 self.request_autoscroll(Autoscroll::newest(), cx);
13647 }
13648 self.selection_history.mode = SelectionHistoryMode::Normal;
13649 }
13650
13651 pub fn redo_selection(
13652 &mut self,
13653 _: &RedoSelection,
13654 window: &mut Window,
13655 cx: &mut Context<Self>,
13656 ) {
13657 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13658 self.end_selection(window, cx);
13659 self.selection_history.mode = SelectionHistoryMode::Redoing;
13660 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13661 self.change_selections(None, window, cx, |s| {
13662 s.select_anchors(entry.selections.to_vec())
13663 });
13664 self.select_next_state = entry.select_next_state;
13665 self.select_prev_state = entry.select_prev_state;
13666 self.add_selections_state = entry.add_selections_state;
13667 self.request_autoscroll(Autoscroll::newest(), cx);
13668 }
13669 self.selection_history.mode = SelectionHistoryMode::Normal;
13670 }
13671
13672 pub fn expand_excerpts(
13673 &mut self,
13674 action: &ExpandExcerpts,
13675 _: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13679 }
13680
13681 pub fn expand_excerpts_down(
13682 &mut self,
13683 action: &ExpandExcerptsDown,
13684 _: &mut Window,
13685 cx: &mut Context<Self>,
13686 ) {
13687 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13688 }
13689
13690 pub fn expand_excerpts_up(
13691 &mut self,
13692 action: &ExpandExcerptsUp,
13693 _: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13697 }
13698
13699 pub fn expand_excerpts_for_direction(
13700 &mut self,
13701 lines: u32,
13702 direction: ExpandExcerptDirection,
13703
13704 cx: &mut Context<Self>,
13705 ) {
13706 let selections = self.selections.disjoint_anchors();
13707
13708 let lines = if lines == 0 {
13709 EditorSettings::get_global(cx).expand_excerpt_lines
13710 } else {
13711 lines
13712 };
13713
13714 self.buffer.update(cx, |buffer, cx| {
13715 let snapshot = buffer.snapshot(cx);
13716 let mut excerpt_ids = selections
13717 .iter()
13718 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13719 .collect::<Vec<_>>();
13720 excerpt_ids.sort();
13721 excerpt_ids.dedup();
13722 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13723 })
13724 }
13725
13726 pub fn expand_excerpt(
13727 &mut self,
13728 excerpt: ExcerptId,
13729 direction: ExpandExcerptDirection,
13730 window: &mut Window,
13731 cx: &mut Context<Self>,
13732 ) {
13733 let current_scroll_position = self.scroll_position(cx);
13734 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13735 let mut should_scroll_up = false;
13736
13737 if direction == ExpandExcerptDirection::Down {
13738 let multi_buffer = self.buffer.read(cx);
13739 let snapshot = multi_buffer.snapshot(cx);
13740 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13741 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13742 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13743 let buffer_snapshot = buffer.read(cx).snapshot();
13744 let excerpt_end_row =
13745 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13746 let last_row = buffer_snapshot.max_point().row;
13747 let lines_below = last_row.saturating_sub(excerpt_end_row);
13748 should_scroll_up = lines_below >= lines_to_expand;
13749 }
13750 }
13751 }
13752 }
13753
13754 self.buffer.update(cx, |buffer, cx| {
13755 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13756 });
13757
13758 if should_scroll_up {
13759 let new_scroll_position =
13760 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13761 self.set_scroll_position(new_scroll_position, window, cx);
13762 }
13763 }
13764
13765 pub fn go_to_singleton_buffer_point(
13766 &mut self,
13767 point: Point,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 self.go_to_singleton_buffer_range(point..point, window, cx);
13772 }
13773
13774 pub fn go_to_singleton_buffer_range(
13775 &mut self,
13776 range: Range<Point>,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 let multibuffer = self.buffer().read(cx);
13781 let Some(buffer) = multibuffer.as_singleton() else {
13782 return;
13783 };
13784 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13785 return;
13786 };
13787 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13788 return;
13789 };
13790 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13791 s.select_anchor_ranges([start..end])
13792 });
13793 }
13794
13795 pub fn go_to_diagnostic(
13796 &mut self,
13797 _: &GoToDiagnostic,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13802 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13803 }
13804
13805 pub fn go_to_prev_diagnostic(
13806 &mut self,
13807 _: &GoToPreviousDiagnostic,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13812 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13813 }
13814
13815 pub fn go_to_diagnostic_impl(
13816 &mut self,
13817 direction: Direction,
13818 window: &mut Window,
13819 cx: &mut Context<Self>,
13820 ) {
13821 let buffer = self.buffer.read(cx).snapshot(cx);
13822 let selection = self.selections.newest::<usize>(cx);
13823
13824 let mut active_group_id = None;
13825 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13826 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13827 active_group_id = Some(active_group.group_id);
13828 }
13829 }
13830
13831 fn filtered(
13832 snapshot: EditorSnapshot,
13833 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13834 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13835 diagnostics
13836 .filter(|entry| entry.range.start != entry.range.end)
13837 .filter(|entry| !entry.diagnostic.is_unnecessary)
13838 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13839 }
13840
13841 let snapshot = self.snapshot(window, cx);
13842 let before = filtered(
13843 snapshot.clone(),
13844 buffer
13845 .diagnostics_in_range(0..selection.start)
13846 .filter(|entry| entry.range.start <= selection.start),
13847 );
13848 let after = filtered(
13849 snapshot,
13850 buffer
13851 .diagnostics_in_range(selection.start..buffer.len())
13852 .filter(|entry| entry.range.start >= selection.start),
13853 );
13854
13855 let mut found: Option<DiagnosticEntry<usize>> = None;
13856 if direction == Direction::Prev {
13857 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13858 {
13859 for diagnostic in prev_diagnostics.into_iter().rev() {
13860 if diagnostic.range.start != selection.start
13861 || active_group_id
13862 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13863 {
13864 found = Some(diagnostic);
13865 break 'outer;
13866 }
13867 }
13868 }
13869 } else {
13870 for diagnostic in after.chain(before) {
13871 if diagnostic.range.start != selection.start
13872 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13873 {
13874 found = Some(diagnostic);
13875 break;
13876 }
13877 }
13878 }
13879 let Some(next_diagnostic) = found else {
13880 return;
13881 };
13882
13883 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13884 return;
13885 };
13886 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13887 s.select_ranges(vec![
13888 next_diagnostic.range.start..next_diagnostic.range.start,
13889 ])
13890 });
13891 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13892 self.refresh_inline_completion(false, true, window, cx);
13893 }
13894
13895 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13896 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13897 let snapshot = self.snapshot(window, cx);
13898 let selection = self.selections.newest::<Point>(cx);
13899 self.go_to_hunk_before_or_after_position(
13900 &snapshot,
13901 selection.head(),
13902 Direction::Next,
13903 window,
13904 cx,
13905 );
13906 }
13907
13908 pub fn go_to_hunk_before_or_after_position(
13909 &mut self,
13910 snapshot: &EditorSnapshot,
13911 position: Point,
13912 direction: Direction,
13913 window: &mut Window,
13914 cx: &mut Context<Editor>,
13915 ) {
13916 let row = if direction == Direction::Next {
13917 self.hunk_after_position(snapshot, position)
13918 .map(|hunk| hunk.row_range.start)
13919 } else {
13920 self.hunk_before_position(snapshot, position)
13921 };
13922
13923 if let Some(row) = row {
13924 let destination = Point::new(row.0, 0);
13925 let autoscroll = Autoscroll::center();
13926
13927 self.unfold_ranges(&[destination..destination], false, false, cx);
13928 self.change_selections(Some(autoscroll), window, cx, |s| {
13929 s.select_ranges([destination..destination]);
13930 });
13931 }
13932 }
13933
13934 fn hunk_after_position(
13935 &mut self,
13936 snapshot: &EditorSnapshot,
13937 position: Point,
13938 ) -> Option<MultiBufferDiffHunk> {
13939 snapshot
13940 .buffer_snapshot
13941 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13942 .find(|hunk| hunk.row_range.start.0 > position.row)
13943 .or_else(|| {
13944 snapshot
13945 .buffer_snapshot
13946 .diff_hunks_in_range(Point::zero()..position)
13947 .find(|hunk| hunk.row_range.end.0 < position.row)
13948 })
13949 }
13950
13951 fn go_to_prev_hunk(
13952 &mut self,
13953 _: &GoToPreviousHunk,
13954 window: &mut Window,
13955 cx: &mut Context<Self>,
13956 ) {
13957 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13958 let snapshot = self.snapshot(window, cx);
13959 let selection = self.selections.newest::<Point>(cx);
13960 self.go_to_hunk_before_or_after_position(
13961 &snapshot,
13962 selection.head(),
13963 Direction::Prev,
13964 window,
13965 cx,
13966 );
13967 }
13968
13969 fn hunk_before_position(
13970 &mut self,
13971 snapshot: &EditorSnapshot,
13972 position: Point,
13973 ) -> Option<MultiBufferRow> {
13974 snapshot
13975 .buffer_snapshot
13976 .diff_hunk_before(position)
13977 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13978 }
13979
13980 fn go_to_next_change(
13981 &mut self,
13982 _: &GoToNextChange,
13983 window: &mut Window,
13984 cx: &mut Context<Self>,
13985 ) {
13986 if let Some(selections) = self
13987 .change_list
13988 .next_change(1, Direction::Next)
13989 .map(|s| s.to_vec())
13990 {
13991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13992 let map = s.display_map();
13993 s.select_display_ranges(selections.iter().map(|a| {
13994 let point = a.to_display_point(&map);
13995 point..point
13996 }))
13997 })
13998 }
13999 }
14000
14001 fn go_to_previous_change(
14002 &mut self,
14003 _: &GoToPreviousChange,
14004 window: &mut Window,
14005 cx: &mut Context<Self>,
14006 ) {
14007 if let Some(selections) = self
14008 .change_list
14009 .next_change(1, Direction::Prev)
14010 .map(|s| s.to_vec())
14011 {
14012 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14013 let map = s.display_map();
14014 s.select_display_ranges(selections.iter().map(|a| {
14015 let point = a.to_display_point(&map);
14016 point..point
14017 }))
14018 })
14019 }
14020 }
14021
14022 fn go_to_line<T: 'static>(
14023 &mut self,
14024 position: Anchor,
14025 highlight_color: Option<Hsla>,
14026 window: &mut Window,
14027 cx: &mut Context<Self>,
14028 ) {
14029 let snapshot = self.snapshot(window, cx).display_snapshot;
14030 let position = position.to_point(&snapshot.buffer_snapshot);
14031 let start = snapshot
14032 .buffer_snapshot
14033 .clip_point(Point::new(position.row, 0), Bias::Left);
14034 let end = start + Point::new(1, 0);
14035 let start = snapshot.buffer_snapshot.anchor_before(start);
14036 let end = snapshot.buffer_snapshot.anchor_before(end);
14037
14038 self.highlight_rows::<T>(
14039 start..end,
14040 highlight_color
14041 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14042 Default::default(),
14043 cx,
14044 );
14045
14046 if self.buffer.read(cx).is_singleton() {
14047 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14048 }
14049 }
14050
14051 pub fn go_to_definition(
14052 &mut self,
14053 _: &GoToDefinition,
14054 window: &mut Window,
14055 cx: &mut Context<Self>,
14056 ) -> Task<Result<Navigated>> {
14057 let definition =
14058 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14059 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14060 cx.spawn_in(window, async move |editor, cx| {
14061 if definition.await? == Navigated::Yes {
14062 return Ok(Navigated::Yes);
14063 }
14064 match fallback_strategy {
14065 GoToDefinitionFallback::None => Ok(Navigated::No),
14066 GoToDefinitionFallback::FindAllReferences => {
14067 match editor.update_in(cx, |editor, window, cx| {
14068 editor.find_all_references(&FindAllReferences, window, cx)
14069 })? {
14070 Some(references) => references.await,
14071 None => Ok(Navigated::No),
14072 }
14073 }
14074 }
14075 })
14076 }
14077
14078 pub fn go_to_declaration(
14079 &mut self,
14080 _: &GoToDeclaration,
14081 window: &mut Window,
14082 cx: &mut Context<Self>,
14083 ) -> Task<Result<Navigated>> {
14084 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14085 }
14086
14087 pub fn go_to_declaration_split(
14088 &mut self,
14089 _: &GoToDeclaration,
14090 window: &mut Window,
14091 cx: &mut Context<Self>,
14092 ) -> Task<Result<Navigated>> {
14093 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14094 }
14095
14096 pub fn go_to_implementation(
14097 &mut self,
14098 _: &GoToImplementation,
14099 window: &mut Window,
14100 cx: &mut Context<Self>,
14101 ) -> Task<Result<Navigated>> {
14102 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14103 }
14104
14105 pub fn go_to_implementation_split(
14106 &mut self,
14107 _: &GoToImplementationSplit,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) -> Task<Result<Navigated>> {
14111 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14112 }
14113
14114 pub fn go_to_type_definition(
14115 &mut self,
14116 _: &GoToTypeDefinition,
14117 window: &mut Window,
14118 cx: &mut Context<Self>,
14119 ) -> Task<Result<Navigated>> {
14120 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14121 }
14122
14123 pub fn go_to_definition_split(
14124 &mut self,
14125 _: &GoToDefinitionSplit,
14126 window: &mut Window,
14127 cx: &mut Context<Self>,
14128 ) -> Task<Result<Navigated>> {
14129 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14130 }
14131
14132 pub fn go_to_type_definition_split(
14133 &mut self,
14134 _: &GoToTypeDefinitionSplit,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) -> Task<Result<Navigated>> {
14138 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14139 }
14140
14141 fn go_to_definition_of_kind(
14142 &mut self,
14143 kind: GotoDefinitionKind,
14144 split: bool,
14145 window: &mut Window,
14146 cx: &mut Context<Self>,
14147 ) -> Task<Result<Navigated>> {
14148 let Some(provider) = self.semantics_provider.clone() else {
14149 return Task::ready(Ok(Navigated::No));
14150 };
14151 let head = self.selections.newest::<usize>(cx).head();
14152 let buffer = self.buffer.read(cx);
14153 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14154 text_anchor
14155 } else {
14156 return Task::ready(Ok(Navigated::No));
14157 };
14158
14159 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14160 return Task::ready(Ok(Navigated::No));
14161 };
14162
14163 cx.spawn_in(window, async move |editor, cx| {
14164 let definitions = definitions.await?;
14165 let navigated = editor
14166 .update_in(cx, |editor, window, cx| {
14167 editor.navigate_to_hover_links(
14168 Some(kind),
14169 definitions
14170 .into_iter()
14171 .filter(|location| {
14172 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14173 })
14174 .map(HoverLink::Text)
14175 .collect::<Vec<_>>(),
14176 split,
14177 window,
14178 cx,
14179 )
14180 })?
14181 .await?;
14182 anyhow::Ok(navigated)
14183 })
14184 }
14185
14186 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14187 let selection = self.selections.newest_anchor();
14188 let head = selection.head();
14189 let tail = selection.tail();
14190
14191 let Some((buffer, start_position)) =
14192 self.buffer.read(cx).text_anchor_for_position(head, cx)
14193 else {
14194 return;
14195 };
14196
14197 let end_position = if head != tail {
14198 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14199 return;
14200 };
14201 Some(pos)
14202 } else {
14203 None
14204 };
14205
14206 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14207 let url = if let Some(end_pos) = end_position {
14208 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14209 } else {
14210 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14211 };
14212
14213 if let Some(url) = url {
14214 editor.update(cx, |_, cx| {
14215 cx.open_url(&url);
14216 })
14217 } else {
14218 Ok(())
14219 }
14220 });
14221
14222 url_finder.detach();
14223 }
14224
14225 pub fn open_selected_filename(
14226 &mut self,
14227 _: &OpenSelectedFilename,
14228 window: &mut Window,
14229 cx: &mut Context<Self>,
14230 ) {
14231 let Some(workspace) = self.workspace() else {
14232 return;
14233 };
14234
14235 let position = self.selections.newest_anchor().head();
14236
14237 let Some((buffer, buffer_position)) =
14238 self.buffer.read(cx).text_anchor_for_position(position, cx)
14239 else {
14240 return;
14241 };
14242
14243 let project = self.project.clone();
14244
14245 cx.spawn_in(window, async move |_, cx| {
14246 let result = find_file(&buffer, project, buffer_position, cx).await;
14247
14248 if let Some((_, path)) = result {
14249 workspace
14250 .update_in(cx, |workspace, window, cx| {
14251 workspace.open_resolved_path(path, window, cx)
14252 })?
14253 .await?;
14254 }
14255 anyhow::Ok(())
14256 })
14257 .detach();
14258 }
14259
14260 pub(crate) fn navigate_to_hover_links(
14261 &mut self,
14262 kind: Option<GotoDefinitionKind>,
14263 mut definitions: Vec<HoverLink>,
14264 split: bool,
14265 window: &mut Window,
14266 cx: &mut Context<Editor>,
14267 ) -> Task<Result<Navigated>> {
14268 // If there is one definition, just open it directly
14269 if definitions.len() == 1 {
14270 let definition = definitions.pop().unwrap();
14271
14272 enum TargetTaskResult {
14273 Location(Option<Location>),
14274 AlreadyNavigated,
14275 }
14276
14277 let target_task = match definition {
14278 HoverLink::Text(link) => {
14279 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14280 }
14281 HoverLink::InlayHint(lsp_location, server_id) => {
14282 let computation =
14283 self.compute_target_location(lsp_location, server_id, window, cx);
14284 cx.background_spawn(async move {
14285 let location = computation.await?;
14286 Ok(TargetTaskResult::Location(location))
14287 })
14288 }
14289 HoverLink::Url(url) => {
14290 cx.open_url(&url);
14291 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14292 }
14293 HoverLink::File(path) => {
14294 if let Some(workspace) = self.workspace() {
14295 cx.spawn_in(window, async move |_, cx| {
14296 workspace
14297 .update_in(cx, |workspace, window, cx| {
14298 workspace.open_resolved_path(path, window, cx)
14299 })?
14300 .await
14301 .map(|_| TargetTaskResult::AlreadyNavigated)
14302 })
14303 } else {
14304 Task::ready(Ok(TargetTaskResult::Location(None)))
14305 }
14306 }
14307 };
14308 cx.spawn_in(window, async move |editor, cx| {
14309 let target = match target_task.await.context("target resolution task")? {
14310 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14311 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14312 TargetTaskResult::Location(Some(target)) => target,
14313 };
14314
14315 editor.update_in(cx, |editor, window, cx| {
14316 let Some(workspace) = editor.workspace() else {
14317 return Navigated::No;
14318 };
14319 let pane = workspace.read(cx).active_pane().clone();
14320
14321 let range = target.range.to_point(target.buffer.read(cx));
14322 let range = editor.range_for_match(&range);
14323 let range = collapse_multiline_range(range);
14324
14325 if !split
14326 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14327 {
14328 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14329 } else {
14330 window.defer(cx, move |window, cx| {
14331 let target_editor: Entity<Self> =
14332 workspace.update(cx, |workspace, cx| {
14333 let pane = if split {
14334 workspace.adjacent_pane(window, cx)
14335 } else {
14336 workspace.active_pane().clone()
14337 };
14338
14339 workspace.open_project_item(
14340 pane,
14341 target.buffer.clone(),
14342 true,
14343 true,
14344 window,
14345 cx,
14346 )
14347 });
14348 target_editor.update(cx, |target_editor, cx| {
14349 // When selecting a definition in a different buffer, disable the nav history
14350 // to avoid creating a history entry at the previous cursor location.
14351 pane.update(cx, |pane, _| pane.disable_history());
14352 target_editor.go_to_singleton_buffer_range(range, window, cx);
14353 pane.update(cx, |pane, _| pane.enable_history());
14354 });
14355 });
14356 }
14357 Navigated::Yes
14358 })
14359 })
14360 } else if !definitions.is_empty() {
14361 cx.spawn_in(window, async move |editor, cx| {
14362 let (title, location_tasks, workspace) = editor
14363 .update_in(cx, |editor, window, cx| {
14364 let tab_kind = match kind {
14365 Some(GotoDefinitionKind::Implementation) => "Implementations",
14366 _ => "Definitions",
14367 };
14368 let title = definitions
14369 .iter()
14370 .find_map(|definition| match definition {
14371 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14372 let buffer = origin.buffer.read(cx);
14373 format!(
14374 "{} for {}",
14375 tab_kind,
14376 buffer
14377 .text_for_range(origin.range.clone())
14378 .collect::<String>()
14379 )
14380 }),
14381 HoverLink::InlayHint(_, _) => None,
14382 HoverLink::Url(_) => None,
14383 HoverLink::File(_) => None,
14384 })
14385 .unwrap_or(tab_kind.to_string());
14386 let location_tasks = definitions
14387 .into_iter()
14388 .map(|definition| match definition {
14389 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14390 HoverLink::InlayHint(lsp_location, server_id) => editor
14391 .compute_target_location(lsp_location, server_id, window, cx),
14392 HoverLink::Url(_) => Task::ready(Ok(None)),
14393 HoverLink::File(_) => Task::ready(Ok(None)),
14394 })
14395 .collect::<Vec<_>>();
14396 (title, location_tasks, editor.workspace().clone())
14397 })
14398 .context("location tasks preparation")?;
14399
14400 let locations = future::join_all(location_tasks)
14401 .await
14402 .into_iter()
14403 .filter_map(|location| location.transpose())
14404 .collect::<Result<_>>()
14405 .context("location tasks")?;
14406
14407 let Some(workspace) = workspace else {
14408 return Ok(Navigated::No);
14409 };
14410 let opened = workspace
14411 .update_in(cx, |workspace, window, cx| {
14412 Self::open_locations_in_multibuffer(
14413 workspace,
14414 locations,
14415 title,
14416 split,
14417 MultibufferSelectionMode::First,
14418 window,
14419 cx,
14420 )
14421 })
14422 .ok();
14423
14424 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14425 })
14426 } else {
14427 Task::ready(Ok(Navigated::No))
14428 }
14429 }
14430
14431 fn compute_target_location(
14432 &self,
14433 lsp_location: lsp::Location,
14434 server_id: LanguageServerId,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) -> Task<anyhow::Result<Option<Location>>> {
14438 let Some(project) = self.project.clone() else {
14439 return Task::ready(Ok(None));
14440 };
14441
14442 cx.spawn_in(window, async move |editor, cx| {
14443 let location_task = editor.update(cx, |_, cx| {
14444 project.update(cx, |project, cx| {
14445 let language_server_name = project
14446 .language_server_statuses(cx)
14447 .find(|(id, _)| server_id == *id)
14448 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14449 language_server_name.map(|language_server_name| {
14450 project.open_local_buffer_via_lsp(
14451 lsp_location.uri.clone(),
14452 server_id,
14453 language_server_name,
14454 cx,
14455 )
14456 })
14457 })
14458 })?;
14459 let location = match location_task {
14460 Some(task) => Some({
14461 let target_buffer_handle = task.await.context("open local buffer")?;
14462 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14463 let target_start = target_buffer
14464 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14465 let target_end = target_buffer
14466 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14467 target_buffer.anchor_after(target_start)
14468 ..target_buffer.anchor_before(target_end)
14469 })?;
14470 Location {
14471 buffer: target_buffer_handle,
14472 range,
14473 }
14474 }),
14475 None => None,
14476 };
14477 Ok(location)
14478 })
14479 }
14480
14481 pub fn find_all_references(
14482 &mut self,
14483 _: &FindAllReferences,
14484 window: &mut Window,
14485 cx: &mut Context<Self>,
14486 ) -> Option<Task<Result<Navigated>>> {
14487 let selection = self.selections.newest::<usize>(cx);
14488 let multi_buffer = self.buffer.read(cx);
14489 let head = selection.head();
14490
14491 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14492 let head_anchor = multi_buffer_snapshot.anchor_at(
14493 head,
14494 if head < selection.tail() {
14495 Bias::Right
14496 } else {
14497 Bias::Left
14498 },
14499 );
14500
14501 match self
14502 .find_all_references_task_sources
14503 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14504 {
14505 Ok(_) => {
14506 log::info!(
14507 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14508 );
14509 return None;
14510 }
14511 Err(i) => {
14512 self.find_all_references_task_sources.insert(i, head_anchor);
14513 }
14514 }
14515
14516 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14517 let workspace = self.workspace()?;
14518 let project = workspace.read(cx).project().clone();
14519 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14520 Some(cx.spawn_in(window, async move |editor, cx| {
14521 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14522 if let Ok(i) = editor
14523 .find_all_references_task_sources
14524 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14525 {
14526 editor.find_all_references_task_sources.remove(i);
14527 }
14528 });
14529
14530 let locations = references.await?;
14531 if locations.is_empty() {
14532 return anyhow::Ok(Navigated::No);
14533 }
14534
14535 workspace.update_in(cx, |workspace, window, cx| {
14536 let title = locations
14537 .first()
14538 .as_ref()
14539 .map(|location| {
14540 let buffer = location.buffer.read(cx);
14541 format!(
14542 "References to `{}`",
14543 buffer
14544 .text_for_range(location.range.clone())
14545 .collect::<String>()
14546 )
14547 })
14548 .unwrap();
14549 Self::open_locations_in_multibuffer(
14550 workspace,
14551 locations,
14552 title,
14553 false,
14554 MultibufferSelectionMode::First,
14555 window,
14556 cx,
14557 );
14558 Navigated::Yes
14559 })
14560 }))
14561 }
14562
14563 /// Opens a multibuffer with the given project locations in it
14564 pub fn open_locations_in_multibuffer(
14565 workspace: &mut Workspace,
14566 mut locations: Vec<Location>,
14567 title: String,
14568 split: bool,
14569 multibuffer_selection_mode: MultibufferSelectionMode,
14570 window: &mut Window,
14571 cx: &mut Context<Workspace>,
14572 ) {
14573 // If there are multiple definitions, open them in a multibuffer
14574 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14575 let mut locations = locations.into_iter().peekable();
14576 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14577 let capability = workspace.project().read(cx).capability();
14578
14579 let excerpt_buffer = cx.new(|cx| {
14580 let mut multibuffer = MultiBuffer::new(capability);
14581 while let Some(location) = locations.next() {
14582 let buffer = location.buffer.read(cx);
14583 let mut ranges_for_buffer = Vec::new();
14584 let range = location.range.to_point(buffer);
14585 ranges_for_buffer.push(range.clone());
14586
14587 while let Some(next_location) = locations.peek() {
14588 if next_location.buffer == location.buffer {
14589 ranges_for_buffer.push(next_location.range.to_point(buffer));
14590 locations.next();
14591 } else {
14592 break;
14593 }
14594 }
14595
14596 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14597 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14598 PathKey::for_buffer(&location.buffer, cx),
14599 location.buffer.clone(),
14600 ranges_for_buffer,
14601 DEFAULT_MULTIBUFFER_CONTEXT,
14602 cx,
14603 );
14604 ranges.extend(new_ranges)
14605 }
14606
14607 multibuffer.with_title(title)
14608 });
14609
14610 let editor = cx.new(|cx| {
14611 Editor::for_multibuffer(
14612 excerpt_buffer,
14613 Some(workspace.project().clone()),
14614 window,
14615 cx,
14616 )
14617 });
14618 editor.update(cx, |editor, cx| {
14619 match multibuffer_selection_mode {
14620 MultibufferSelectionMode::First => {
14621 if let Some(first_range) = ranges.first() {
14622 editor.change_selections(None, window, cx, |selections| {
14623 selections.clear_disjoint();
14624 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14625 });
14626 }
14627 editor.highlight_background::<Self>(
14628 &ranges,
14629 |theme| theme.editor_highlighted_line_background,
14630 cx,
14631 );
14632 }
14633 MultibufferSelectionMode::All => {
14634 editor.change_selections(None, window, cx, |selections| {
14635 selections.clear_disjoint();
14636 selections.select_anchor_ranges(ranges);
14637 });
14638 }
14639 }
14640 editor.register_buffers_with_language_servers(cx);
14641 });
14642
14643 let item = Box::new(editor);
14644 let item_id = item.item_id();
14645
14646 if split {
14647 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14648 } else {
14649 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14650 let (preview_item_id, preview_item_idx) =
14651 workspace.active_pane().update(cx, |pane, _| {
14652 (pane.preview_item_id(), pane.preview_item_idx())
14653 });
14654
14655 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14656
14657 if let Some(preview_item_id) = preview_item_id {
14658 workspace.active_pane().update(cx, |pane, cx| {
14659 pane.remove_item(preview_item_id, false, false, window, cx);
14660 });
14661 }
14662 } else {
14663 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14664 }
14665 }
14666 workspace.active_pane().update(cx, |pane, cx| {
14667 pane.set_preview_item_id(Some(item_id), cx);
14668 });
14669 }
14670
14671 pub fn rename(
14672 &mut self,
14673 _: &Rename,
14674 window: &mut Window,
14675 cx: &mut Context<Self>,
14676 ) -> Option<Task<Result<()>>> {
14677 use language::ToOffset as _;
14678
14679 let provider = self.semantics_provider.clone()?;
14680 let selection = self.selections.newest_anchor().clone();
14681 let (cursor_buffer, cursor_buffer_position) = self
14682 .buffer
14683 .read(cx)
14684 .text_anchor_for_position(selection.head(), cx)?;
14685 let (tail_buffer, cursor_buffer_position_end) = self
14686 .buffer
14687 .read(cx)
14688 .text_anchor_for_position(selection.tail(), cx)?;
14689 if tail_buffer != cursor_buffer {
14690 return None;
14691 }
14692
14693 let snapshot = cursor_buffer.read(cx).snapshot();
14694 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14695 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14696 let prepare_rename = provider
14697 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14698 .unwrap_or_else(|| Task::ready(Ok(None)));
14699 drop(snapshot);
14700
14701 Some(cx.spawn_in(window, async move |this, cx| {
14702 let rename_range = if let Some(range) = prepare_rename.await? {
14703 Some(range)
14704 } else {
14705 this.update(cx, |this, cx| {
14706 let buffer = this.buffer.read(cx).snapshot(cx);
14707 let mut buffer_highlights = this
14708 .document_highlights_for_position(selection.head(), &buffer)
14709 .filter(|highlight| {
14710 highlight.start.excerpt_id == selection.head().excerpt_id
14711 && highlight.end.excerpt_id == selection.head().excerpt_id
14712 });
14713 buffer_highlights
14714 .next()
14715 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14716 })?
14717 };
14718 if let Some(rename_range) = rename_range {
14719 this.update_in(cx, |this, window, cx| {
14720 let snapshot = cursor_buffer.read(cx).snapshot();
14721 let rename_buffer_range = rename_range.to_offset(&snapshot);
14722 let cursor_offset_in_rename_range =
14723 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14724 let cursor_offset_in_rename_range_end =
14725 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14726
14727 this.take_rename(false, window, cx);
14728 let buffer = this.buffer.read(cx).read(cx);
14729 let cursor_offset = selection.head().to_offset(&buffer);
14730 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14731 let rename_end = rename_start + rename_buffer_range.len();
14732 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14733 let mut old_highlight_id = None;
14734 let old_name: Arc<str> = buffer
14735 .chunks(rename_start..rename_end, true)
14736 .map(|chunk| {
14737 if old_highlight_id.is_none() {
14738 old_highlight_id = chunk.syntax_highlight_id;
14739 }
14740 chunk.text
14741 })
14742 .collect::<String>()
14743 .into();
14744
14745 drop(buffer);
14746
14747 // Position the selection in the rename editor so that it matches the current selection.
14748 this.show_local_selections = false;
14749 let rename_editor = cx.new(|cx| {
14750 let mut editor = Editor::single_line(window, cx);
14751 editor.buffer.update(cx, |buffer, cx| {
14752 buffer.edit([(0..0, old_name.clone())], None, cx)
14753 });
14754 let rename_selection_range = match cursor_offset_in_rename_range
14755 .cmp(&cursor_offset_in_rename_range_end)
14756 {
14757 Ordering::Equal => {
14758 editor.select_all(&SelectAll, window, cx);
14759 return editor;
14760 }
14761 Ordering::Less => {
14762 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14763 }
14764 Ordering::Greater => {
14765 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14766 }
14767 };
14768 if rename_selection_range.end > old_name.len() {
14769 editor.select_all(&SelectAll, window, cx);
14770 } else {
14771 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14772 s.select_ranges([rename_selection_range]);
14773 });
14774 }
14775 editor
14776 });
14777 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14778 if e == &EditorEvent::Focused {
14779 cx.emit(EditorEvent::FocusedIn)
14780 }
14781 })
14782 .detach();
14783
14784 let write_highlights =
14785 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14786 let read_highlights =
14787 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14788 let ranges = write_highlights
14789 .iter()
14790 .flat_map(|(_, ranges)| ranges.iter())
14791 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14792 .cloned()
14793 .collect();
14794
14795 this.highlight_text::<Rename>(
14796 ranges,
14797 HighlightStyle {
14798 fade_out: Some(0.6),
14799 ..Default::default()
14800 },
14801 cx,
14802 );
14803 let rename_focus_handle = rename_editor.focus_handle(cx);
14804 window.focus(&rename_focus_handle);
14805 let block_id = this.insert_blocks(
14806 [BlockProperties {
14807 style: BlockStyle::Flex,
14808 placement: BlockPlacement::Below(range.start),
14809 height: Some(1),
14810 render: Arc::new({
14811 let rename_editor = rename_editor.clone();
14812 move |cx: &mut BlockContext| {
14813 let mut text_style = cx.editor_style.text.clone();
14814 if let Some(highlight_style) = old_highlight_id
14815 .and_then(|h| h.style(&cx.editor_style.syntax))
14816 {
14817 text_style = text_style.highlight(highlight_style);
14818 }
14819 div()
14820 .block_mouse_down()
14821 .pl(cx.anchor_x)
14822 .child(EditorElement::new(
14823 &rename_editor,
14824 EditorStyle {
14825 background: cx.theme().system().transparent,
14826 local_player: cx.editor_style.local_player,
14827 text: text_style,
14828 scrollbar_width: cx.editor_style.scrollbar_width,
14829 syntax: cx.editor_style.syntax.clone(),
14830 status: cx.editor_style.status.clone(),
14831 inlay_hints_style: HighlightStyle {
14832 font_weight: Some(FontWeight::BOLD),
14833 ..make_inlay_hints_style(cx.app)
14834 },
14835 inline_completion_styles: make_suggestion_styles(
14836 cx.app,
14837 ),
14838 ..EditorStyle::default()
14839 },
14840 ))
14841 .into_any_element()
14842 }
14843 }),
14844 priority: 0,
14845 render_in_minimap: true,
14846 }],
14847 Some(Autoscroll::fit()),
14848 cx,
14849 )[0];
14850 this.pending_rename = Some(RenameState {
14851 range,
14852 old_name,
14853 editor: rename_editor,
14854 block_id,
14855 });
14856 })?;
14857 }
14858
14859 Ok(())
14860 }))
14861 }
14862
14863 pub fn confirm_rename(
14864 &mut self,
14865 _: &ConfirmRename,
14866 window: &mut Window,
14867 cx: &mut Context<Self>,
14868 ) -> Option<Task<Result<()>>> {
14869 let rename = self.take_rename(false, window, cx)?;
14870 let workspace = self.workspace()?.downgrade();
14871 let (buffer, start) = self
14872 .buffer
14873 .read(cx)
14874 .text_anchor_for_position(rename.range.start, cx)?;
14875 let (end_buffer, _) = self
14876 .buffer
14877 .read(cx)
14878 .text_anchor_for_position(rename.range.end, cx)?;
14879 if buffer != end_buffer {
14880 return None;
14881 }
14882
14883 let old_name = rename.old_name;
14884 let new_name = rename.editor.read(cx).text(cx);
14885
14886 let rename = self.semantics_provider.as_ref()?.perform_rename(
14887 &buffer,
14888 start,
14889 new_name.clone(),
14890 cx,
14891 )?;
14892
14893 Some(cx.spawn_in(window, async move |editor, cx| {
14894 let project_transaction = rename.await?;
14895 Self::open_project_transaction(
14896 &editor,
14897 workspace,
14898 project_transaction,
14899 format!("Rename: {} → {}", old_name, new_name),
14900 cx,
14901 )
14902 .await?;
14903
14904 editor.update(cx, |editor, cx| {
14905 editor.refresh_document_highlights(cx);
14906 })?;
14907 Ok(())
14908 }))
14909 }
14910
14911 fn take_rename(
14912 &mut self,
14913 moving_cursor: bool,
14914 window: &mut Window,
14915 cx: &mut Context<Self>,
14916 ) -> Option<RenameState> {
14917 let rename = self.pending_rename.take()?;
14918 if rename.editor.focus_handle(cx).is_focused(window) {
14919 window.focus(&self.focus_handle);
14920 }
14921
14922 self.remove_blocks(
14923 [rename.block_id].into_iter().collect(),
14924 Some(Autoscroll::fit()),
14925 cx,
14926 );
14927 self.clear_highlights::<Rename>(cx);
14928 self.show_local_selections = true;
14929
14930 if moving_cursor {
14931 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14932 editor.selections.newest::<usize>(cx).head()
14933 });
14934
14935 // Update the selection to match the position of the selection inside
14936 // the rename editor.
14937 let snapshot = self.buffer.read(cx).read(cx);
14938 let rename_range = rename.range.to_offset(&snapshot);
14939 let cursor_in_editor = snapshot
14940 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14941 .min(rename_range.end);
14942 drop(snapshot);
14943
14944 self.change_selections(None, window, cx, |s| {
14945 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14946 });
14947 } else {
14948 self.refresh_document_highlights(cx);
14949 }
14950
14951 Some(rename)
14952 }
14953
14954 pub fn pending_rename(&self) -> Option<&RenameState> {
14955 self.pending_rename.as_ref()
14956 }
14957
14958 fn format(
14959 &mut self,
14960 _: &Format,
14961 window: &mut Window,
14962 cx: &mut Context<Self>,
14963 ) -> Option<Task<Result<()>>> {
14964 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14965
14966 let project = match &self.project {
14967 Some(project) => project.clone(),
14968 None => return None,
14969 };
14970
14971 Some(self.perform_format(
14972 project,
14973 FormatTrigger::Manual,
14974 FormatTarget::Buffers,
14975 window,
14976 cx,
14977 ))
14978 }
14979
14980 fn format_selections(
14981 &mut self,
14982 _: &FormatSelections,
14983 window: &mut Window,
14984 cx: &mut Context<Self>,
14985 ) -> Option<Task<Result<()>>> {
14986 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14987
14988 let project = match &self.project {
14989 Some(project) => project.clone(),
14990 None => return None,
14991 };
14992
14993 let ranges = self
14994 .selections
14995 .all_adjusted(cx)
14996 .into_iter()
14997 .map(|selection| selection.range())
14998 .collect_vec();
14999
15000 Some(self.perform_format(
15001 project,
15002 FormatTrigger::Manual,
15003 FormatTarget::Ranges(ranges),
15004 window,
15005 cx,
15006 ))
15007 }
15008
15009 fn perform_format(
15010 &mut self,
15011 project: Entity<Project>,
15012 trigger: FormatTrigger,
15013 target: FormatTarget,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) -> Task<Result<()>> {
15017 let buffer = self.buffer.clone();
15018 let (buffers, target) = match target {
15019 FormatTarget::Buffers => {
15020 let mut buffers = buffer.read(cx).all_buffers();
15021 if trigger == FormatTrigger::Save {
15022 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15023 }
15024 (buffers, LspFormatTarget::Buffers)
15025 }
15026 FormatTarget::Ranges(selection_ranges) => {
15027 let multi_buffer = buffer.read(cx);
15028 let snapshot = multi_buffer.read(cx);
15029 let mut buffers = HashSet::default();
15030 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15031 BTreeMap::new();
15032 for selection_range in selection_ranges {
15033 for (buffer, buffer_range, _) in
15034 snapshot.range_to_buffer_ranges(selection_range)
15035 {
15036 let buffer_id = buffer.remote_id();
15037 let start = buffer.anchor_before(buffer_range.start);
15038 let end = buffer.anchor_after(buffer_range.end);
15039 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15040 buffer_id_to_ranges
15041 .entry(buffer_id)
15042 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15043 .or_insert_with(|| vec![start..end]);
15044 }
15045 }
15046 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15047 }
15048 };
15049
15050 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15051 let selections_prev = transaction_id_prev
15052 .and_then(|transaction_id_prev| {
15053 // default to selections as they were after the last edit, if we have them,
15054 // instead of how they are now.
15055 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15056 // will take you back to where you made the last edit, instead of staying where you scrolled
15057 self.selection_history
15058 .transaction(transaction_id_prev)
15059 .map(|t| t.0.clone())
15060 })
15061 .unwrap_or_else(|| {
15062 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15063 self.selections.disjoint_anchors()
15064 });
15065
15066 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15067 let format = project.update(cx, |project, cx| {
15068 project.format(buffers, target, true, trigger, cx)
15069 });
15070
15071 cx.spawn_in(window, async move |editor, cx| {
15072 let transaction = futures::select_biased! {
15073 transaction = format.log_err().fuse() => transaction,
15074 () = timeout => {
15075 log::warn!("timed out waiting for formatting");
15076 None
15077 }
15078 };
15079
15080 buffer
15081 .update(cx, |buffer, cx| {
15082 if let Some(transaction) = transaction {
15083 if !buffer.is_singleton() {
15084 buffer.push_transaction(&transaction.0, cx);
15085 }
15086 }
15087 cx.notify();
15088 })
15089 .ok();
15090
15091 if let Some(transaction_id_now) =
15092 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15093 {
15094 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15095 if has_new_transaction {
15096 _ = editor.update(cx, |editor, _| {
15097 editor
15098 .selection_history
15099 .insert_transaction(transaction_id_now, selections_prev);
15100 });
15101 }
15102 }
15103
15104 Ok(())
15105 })
15106 }
15107
15108 fn organize_imports(
15109 &mut self,
15110 _: &OrganizeImports,
15111 window: &mut Window,
15112 cx: &mut Context<Self>,
15113 ) -> Option<Task<Result<()>>> {
15114 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15115 let project = match &self.project {
15116 Some(project) => project.clone(),
15117 None => return None,
15118 };
15119 Some(self.perform_code_action_kind(
15120 project,
15121 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15122 window,
15123 cx,
15124 ))
15125 }
15126
15127 fn perform_code_action_kind(
15128 &mut self,
15129 project: Entity<Project>,
15130 kind: CodeActionKind,
15131 window: &mut Window,
15132 cx: &mut Context<Self>,
15133 ) -> Task<Result<()>> {
15134 let buffer = self.buffer.clone();
15135 let buffers = buffer.read(cx).all_buffers();
15136 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15137 let apply_action = project.update(cx, |project, cx| {
15138 project.apply_code_action_kind(buffers, kind, true, cx)
15139 });
15140 cx.spawn_in(window, async move |_, cx| {
15141 let transaction = futures::select_biased! {
15142 () = timeout => {
15143 log::warn!("timed out waiting for executing code action");
15144 None
15145 }
15146 transaction = apply_action.log_err().fuse() => transaction,
15147 };
15148 buffer
15149 .update(cx, |buffer, cx| {
15150 // check if we need this
15151 if let Some(transaction) = transaction {
15152 if !buffer.is_singleton() {
15153 buffer.push_transaction(&transaction.0, cx);
15154 }
15155 }
15156 cx.notify();
15157 })
15158 .ok();
15159 Ok(())
15160 })
15161 }
15162
15163 fn restart_language_server(
15164 &mut self,
15165 _: &RestartLanguageServer,
15166 _: &mut Window,
15167 cx: &mut Context<Self>,
15168 ) {
15169 if let Some(project) = self.project.clone() {
15170 self.buffer.update(cx, |multi_buffer, cx| {
15171 project.update(cx, |project, cx| {
15172 project.restart_language_servers_for_buffers(
15173 multi_buffer.all_buffers().into_iter().collect(),
15174 cx,
15175 );
15176 });
15177 })
15178 }
15179 }
15180
15181 fn stop_language_server(
15182 &mut self,
15183 _: &StopLanguageServer,
15184 _: &mut Window,
15185 cx: &mut Context<Self>,
15186 ) {
15187 if let Some(project) = self.project.clone() {
15188 self.buffer.update(cx, |multi_buffer, cx| {
15189 project.update(cx, |project, cx| {
15190 project.stop_language_servers_for_buffers(
15191 multi_buffer.all_buffers().into_iter().collect(),
15192 cx,
15193 );
15194 cx.emit(project::Event::RefreshInlayHints);
15195 });
15196 });
15197 }
15198 }
15199
15200 fn cancel_language_server_work(
15201 workspace: &mut Workspace,
15202 _: &actions::CancelLanguageServerWork,
15203 _: &mut Window,
15204 cx: &mut Context<Workspace>,
15205 ) {
15206 let project = workspace.project();
15207 let buffers = workspace
15208 .active_item(cx)
15209 .and_then(|item| item.act_as::<Editor>(cx))
15210 .map_or(HashSet::default(), |editor| {
15211 editor.read(cx).buffer.read(cx).all_buffers()
15212 });
15213 project.update(cx, |project, cx| {
15214 project.cancel_language_server_work_for_buffers(buffers, cx);
15215 });
15216 }
15217
15218 fn show_character_palette(
15219 &mut self,
15220 _: &ShowCharacterPalette,
15221 window: &mut Window,
15222 _: &mut Context<Self>,
15223 ) {
15224 window.show_character_palette();
15225 }
15226
15227 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15228 if self.mode.is_minimap() {
15229 return;
15230 }
15231
15232 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15233 let buffer = self.buffer.read(cx).snapshot(cx);
15234 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15235 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15236 let is_valid = buffer
15237 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15238 .any(|entry| {
15239 entry.diagnostic.is_primary
15240 && !entry.range.is_empty()
15241 && entry.range.start == primary_range_start
15242 && entry.diagnostic.message == active_diagnostics.active_message
15243 });
15244
15245 if !is_valid {
15246 self.dismiss_diagnostics(cx);
15247 }
15248 }
15249 }
15250
15251 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15252 match &self.active_diagnostics {
15253 ActiveDiagnostic::Group(group) => Some(group),
15254 _ => None,
15255 }
15256 }
15257
15258 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15259 self.dismiss_diagnostics(cx);
15260 self.active_diagnostics = ActiveDiagnostic::All;
15261 }
15262
15263 fn activate_diagnostics(
15264 &mut self,
15265 buffer_id: BufferId,
15266 diagnostic: DiagnosticEntry<usize>,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15271 return;
15272 }
15273 self.dismiss_diagnostics(cx);
15274 let snapshot = self.snapshot(window, cx);
15275 let buffer = self.buffer.read(cx).snapshot(cx);
15276 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15277 return;
15278 };
15279
15280 let diagnostic_group = buffer
15281 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15282 .collect::<Vec<_>>();
15283
15284 let blocks =
15285 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15286
15287 let blocks = self.display_map.update(cx, |display_map, cx| {
15288 display_map.insert_blocks(blocks, cx).into_iter().collect()
15289 });
15290 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15291 active_range: buffer.anchor_before(diagnostic.range.start)
15292 ..buffer.anchor_after(diagnostic.range.end),
15293 active_message: diagnostic.diagnostic.message.clone(),
15294 group_id: diagnostic.diagnostic.group_id,
15295 blocks,
15296 });
15297 cx.notify();
15298 }
15299
15300 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15301 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15302 return;
15303 };
15304
15305 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15306 if let ActiveDiagnostic::Group(group) = prev {
15307 self.display_map.update(cx, |display_map, cx| {
15308 display_map.remove_blocks(group.blocks, cx);
15309 });
15310 cx.notify();
15311 }
15312 }
15313
15314 /// Disable inline diagnostics rendering for this editor.
15315 pub fn disable_inline_diagnostics(&mut self) {
15316 self.inline_diagnostics_enabled = false;
15317 self.inline_diagnostics_update = Task::ready(());
15318 self.inline_diagnostics.clear();
15319 }
15320
15321 pub fn diagnostics_enabled(&self) -> bool {
15322 self.mode.is_full()
15323 }
15324
15325 pub fn inline_diagnostics_enabled(&self) -> bool {
15326 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15327 }
15328
15329 pub fn show_inline_diagnostics(&self) -> bool {
15330 self.show_inline_diagnostics
15331 }
15332
15333 pub fn toggle_inline_diagnostics(
15334 &mut self,
15335 _: &ToggleInlineDiagnostics,
15336 window: &mut Window,
15337 cx: &mut Context<Editor>,
15338 ) {
15339 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15340 self.refresh_inline_diagnostics(false, window, cx);
15341 }
15342
15343 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15344 self.diagnostics_max_severity = severity;
15345 self.display_map.update(cx, |display_map, _| {
15346 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15347 });
15348 }
15349
15350 pub fn toggle_diagnostics(
15351 &mut self,
15352 _: &ToggleDiagnostics,
15353 window: &mut Window,
15354 cx: &mut Context<Editor>,
15355 ) {
15356 if !self.diagnostics_enabled() {
15357 return;
15358 }
15359
15360 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15361 EditorSettings::get_global(cx)
15362 .diagnostics_max_severity
15363 .filter(|severity| severity != &DiagnosticSeverity::Off)
15364 .unwrap_or(DiagnosticSeverity::Hint)
15365 } else {
15366 DiagnosticSeverity::Off
15367 };
15368 self.set_max_diagnostics_severity(new_severity, cx);
15369 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15370 self.active_diagnostics = ActiveDiagnostic::None;
15371 self.inline_diagnostics_update = Task::ready(());
15372 self.inline_diagnostics.clear();
15373 } else {
15374 self.refresh_inline_diagnostics(false, window, cx);
15375 }
15376
15377 cx.notify();
15378 }
15379
15380 pub fn toggle_minimap(
15381 &mut self,
15382 _: &ToggleMinimap,
15383 window: &mut Window,
15384 cx: &mut Context<Editor>,
15385 ) {
15386 if self.supports_minimap(cx) {
15387 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15388 }
15389 }
15390
15391 fn refresh_inline_diagnostics(
15392 &mut self,
15393 debounce: bool,
15394 window: &mut Window,
15395 cx: &mut Context<Self>,
15396 ) {
15397 let max_severity = ProjectSettings::get_global(cx)
15398 .diagnostics
15399 .inline
15400 .max_severity
15401 .unwrap_or(self.diagnostics_max_severity);
15402
15403 if self.mode.is_minimap()
15404 || !self.inline_diagnostics_enabled()
15405 || !self.show_inline_diagnostics
15406 || max_severity == DiagnosticSeverity::Off
15407 {
15408 self.inline_diagnostics_update = Task::ready(());
15409 self.inline_diagnostics.clear();
15410 return;
15411 }
15412
15413 let debounce_ms = ProjectSettings::get_global(cx)
15414 .diagnostics
15415 .inline
15416 .update_debounce_ms;
15417 let debounce = if debounce && debounce_ms > 0 {
15418 Some(Duration::from_millis(debounce_ms))
15419 } else {
15420 None
15421 };
15422 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15423 let editor = editor.upgrade().unwrap();
15424
15425 if let Some(debounce) = debounce {
15426 cx.background_executor().timer(debounce).await;
15427 }
15428 let Some(snapshot) = editor
15429 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15430 .ok()
15431 else {
15432 return;
15433 };
15434
15435 let new_inline_diagnostics = cx
15436 .background_spawn(async move {
15437 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15438 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15439 let message = diagnostic_entry
15440 .diagnostic
15441 .message
15442 .split_once('\n')
15443 .map(|(line, _)| line)
15444 .map(SharedString::new)
15445 .unwrap_or_else(|| {
15446 SharedString::from(diagnostic_entry.diagnostic.message)
15447 });
15448 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15449 let (Ok(i) | Err(i)) = inline_diagnostics
15450 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15451 inline_diagnostics.insert(
15452 i,
15453 (
15454 start_anchor,
15455 InlineDiagnostic {
15456 message,
15457 group_id: diagnostic_entry.diagnostic.group_id,
15458 start: diagnostic_entry.range.start.to_point(&snapshot),
15459 is_primary: diagnostic_entry.diagnostic.is_primary,
15460 severity: diagnostic_entry.diagnostic.severity,
15461 },
15462 ),
15463 );
15464 }
15465 inline_diagnostics
15466 })
15467 .await;
15468
15469 editor
15470 .update(cx, |editor, cx| {
15471 editor.inline_diagnostics = new_inline_diagnostics;
15472 cx.notify();
15473 })
15474 .ok();
15475 });
15476 }
15477
15478 pub fn set_selections_from_remote(
15479 &mut self,
15480 selections: Vec<Selection<Anchor>>,
15481 pending_selection: Option<Selection<Anchor>>,
15482 window: &mut Window,
15483 cx: &mut Context<Self>,
15484 ) {
15485 let old_cursor_position = self.selections.newest_anchor().head();
15486 self.selections.change_with(cx, |s| {
15487 s.select_anchors(selections);
15488 if let Some(pending_selection) = pending_selection {
15489 s.set_pending(pending_selection, SelectMode::Character);
15490 } else {
15491 s.clear_pending();
15492 }
15493 });
15494 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15495 }
15496
15497 fn push_to_selection_history(&mut self) {
15498 self.selection_history.push(SelectionHistoryEntry {
15499 selections: self.selections.disjoint_anchors(),
15500 select_next_state: self.select_next_state.clone(),
15501 select_prev_state: self.select_prev_state.clone(),
15502 add_selections_state: self.add_selections_state.clone(),
15503 });
15504 }
15505
15506 pub fn transact(
15507 &mut self,
15508 window: &mut Window,
15509 cx: &mut Context<Self>,
15510 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15511 ) -> Option<TransactionId> {
15512 self.start_transaction_at(Instant::now(), window, cx);
15513 update(self, window, cx);
15514 self.end_transaction_at(Instant::now(), cx)
15515 }
15516
15517 pub fn start_transaction_at(
15518 &mut self,
15519 now: Instant,
15520 window: &mut Window,
15521 cx: &mut Context<Self>,
15522 ) {
15523 self.end_selection(window, cx);
15524 if let Some(tx_id) = self
15525 .buffer
15526 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15527 {
15528 self.selection_history
15529 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15530 cx.emit(EditorEvent::TransactionBegun {
15531 transaction_id: tx_id,
15532 })
15533 }
15534 }
15535
15536 pub fn end_transaction_at(
15537 &mut self,
15538 now: Instant,
15539 cx: &mut Context<Self>,
15540 ) -> Option<TransactionId> {
15541 if let Some(transaction_id) = self
15542 .buffer
15543 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15544 {
15545 if let Some((_, end_selections)) =
15546 self.selection_history.transaction_mut(transaction_id)
15547 {
15548 *end_selections = Some(self.selections.disjoint_anchors());
15549 } else {
15550 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15551 }
15552
15553 cx.emit(EditorEvent::Edited { transaction_id });
15554 Some(transaction_id)
15555 } else {
15556 None
15557 }
15558 }
15559
15560 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15561 if self.selection_mark_mode {
15562 self.change_selections(None, window, cx, |s| {
15563 s.move_with(|_, sel| {
15564 sel.collapse_to(sel.head(), SelectionGoal::None);
15565 });
15566 })
15567 }
15568 self.selection_mark_mode = true;
15569 cx.notify();
15570 }
15571
15572 pub fn swap_selection_ends(
15573 &mut self,
15574 _: &actions::SwapSelectionEnds,
15575 window: &mut Window,
15576 cx: &mut Context<Self>,
15577 ) {
15578 self.change_selections(None, window, cx, |s| {
15579 s.move_with(|_, sel| {
15580 if sel.start != sel.end {
15581 sel.reversed = !sel.reversed
15582 }
15583 });
15584 });
15585 self.request_autoscroll(Autoscroll::newest(), cx);
15586 cx.notify();
15587 }
15588
15589 pub fn toggle_fold(
15590 &mut self,
15591 _: &actions::ToggleFold,
15592 window: &mut Window,
15593 cx: &mut Context<Self>,
15594 ) {
15595 if self.is_singleton(cx) {
15596 let selection = self.selections.newest::<Point>(cx);
15597
15598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15599 let range = if selection.is_empty() {
15600 let point = selection.head().to_display_point(&display_map);
15601 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15602 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15603 .to_point(&display_map);
15604 start..end
15605 } else {
15606 selection.range()
15607 };
15608 if display_map.folds_in_range(range).next().is_some() {
15609 self.unfold_lines(&Default::default(), window, cx)
15610 } else {
15611 self.fold(&Default::default(), window, cx)
15612 }
15613 } else {
15614 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15615 let buffer_ids: HashSet<_> = self
15616 .selections
15617 .disjoint_anchor_ranges()
15618 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15619 .collect();
15620
15621 let should_unfold = buffer_ids
15622 .iter()
15623 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15624
15625 for buffer_id in buffer_ids {
15626 if should_unfold {
15627 self.unfold_buffer(buffer_id, cx);
15628 } else {
15629 self.fold_buffer(buffer_id, cx);
15630 }
15631 }
15632 }
15633 }
15634
15635 pub fn toggle_fold_recursive(
15636 &mut self,
15637 _: &actions::ToggleFoldRecursive,
15638 window: &mut Window,
15639 cx: &mut Context<Self>,
15640 ) {
15641 let selection = self.selections.newest::<Point>(cx);
15642
15643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15644 let range = if selection.is_empty() {
15645 let point = selection.head().to_display_point(&display_map);
15646 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15647 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15648 .to_point(&display_map);
15649 start..end
15650 } else {
15651 selection.range()
15652 };
15653 if display_map.folds_in_range(range).next().is_some() {
15654 self.unfold_recursive(&Default::default(), window, cx)
15655 } else {
15656 self.fold_recursive(&Default::default(), window, cx)
15657 }
15658 }
15659
15660 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15661 if self.is_singleton(cx) {
15662 let mut to_fold = Vec::new();
15663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15664 let selections = self.selections.all_adjusted(cx);
15665
15666 for selection in selections {
15667 let range = selection.range().sorted();
15668 let buffer_start_row = range.start.row;
15669
15670 if range.start.row != range.end.row {
15671 let mut found = false;
15672 let mut row = range.start.row;
15673 while row <= range.end.row {
15674 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15675 {
15676 found = true;
15677 row = crease.range().end.row + 1;
15678 to_fold.push(crease);
15679 } else {
15680 row += 1
15681 }
15682 }
15683 if found {
15684 continue;
15685 }
15686 }
15687
15688 for row in (0..=range.start.row).rev() {
15689 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15690 if crease.range().end.row >= buffer_start_row {
15691 to_fold.push(crease);
15692 if row <= range.start.row {
15693 break;
15694 }
15695 }
15696 }
15697 }
15698 }
15699
15700 self.fold_creases(to_fold, true, window, cx);
15701 } else {
15702 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15703 let buffer_ids = self
15704 .selections
15705 .disjoint_anchor_ranges()
15706 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15707 .collect::<HashSet<_>>();
15708 for buffer_id in buffer_ids {
15709 self.fold_buffer(buffer_id, cx);
15710 }
15711 }
15712 }
15713
15714 fn fold_at_level(
15715 &mut self,
15716 fold_at: &FoldAtLevel,
15717 window: &mut Window,
15718 cx: &mut Context<Self>,
15719 ) {
15720 if !self.buffer.read(cx).is_singleton() {
15721 return;
15722 }
15723
15724 let fold_at_level = fold_at.0;
15725 let snapshot = self.buffer.read(cx).snapshot(cx);
15726 let mut to_fold = Vec::new();
15727 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15728
15729 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15730 while start_row < end_row {
15731 match self
15732 .snapshot(window, cx)
15733 .crease_for_buffer_row(MultiBufferRow(start_row))
15734 {
15735 Some(crease) => {
15736 let nested_start_row = crease.range().start.row + 1;
15737 let nested_end_row = crease.range().end.row;
15738
15739 if current_level < fold_at_level {
15740 stack.push((nested_start_row, nested_end_row, current_level + 1));
15741 } else if current_level == fold_at_level {
15742 to_fold.push(crease);
15743 }
15744
15745 start_row = nested_end_row + 1;
15746 }
15747 None => start_row += 1,
15748 }
15749 }
15750 }
15751
15752 self.fold_creases(to_fold, true, window, cx);
15753 }
15754
15755 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15756 if self.buffer.read(cx).is_singleton() {
15757 let mut fold_ranges = Vec::new();
15758 let snapshot = self.buffer.read(cx).snapshot(cx);
15759
15760 for row in 0..snapshot.max_row().0 {
15761 if let Some(foldable_range) = self
15762 .snapshot(window, cx)
15763 .crease_for_buffer_row(MultiBufferRow(row))
15764 {
15765 fold_ranges.push(foldable_range);
15766 }
15767 }
15768
15769 self.fold_creases(fold_ranges, true, window, cx);
15770 } else {
15771 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15772 editor
15773 .update_in(cx, |editor, _, cx| {
15774 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15775 editor.fold_buffer(buffer_id, cx);
15776 }
15777 })
15778 .ok();
15779 });
15780 }
15781 }
15782
15783 pub fn fold_function_bodies(
15784 &mut self,
15785 _: &actions::FoldFunctionBodies,
15786 window: &mut Window,
15787 cx: &mut Context<Self>,
15788 ) {
15789 let snapshot = self.buffer.read(cx).snapshot(cx);
15790
15791 let ranges = snapshot
15792 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15793 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15794 .collect::<Vec<_>>();
15795
15796 let creases = ranges
15797 .into_iter()
15798 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15799 .collect();
15800
15801 self.fold_creases(creases, true, window, cx);
15802 }
15803
15804 pub fn fold_recursive(
15805 &mut self,
15806 _: &actions::FoldRecursive,
15807 window: &mut Window,
15808 cx: &mut Context<Self>,
15809 ) {
15810 let mut to_fold = Vec::new();
15811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15812 let selections = self.selections.all_adjusted(cx);
15813
15814 for selection in selections {
15815 let range = selection.range().sorted();
15816 let buffer_start_row = range.start.row;
15817
15818 if range.start.row != range.end.row {
15819 let mut found = false;
15820 for row in range.start.row..=range.end.row {
15821 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15822 found = true;
15823 to_fold.push(crease);
15824 }
15825 }
15826 if found {
15827 continue;
15828 }
15829 }
15830
15831 for row in (0..=range.start.row).rev() {
15832 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15833 if crease.range().end.row >= buffer_start_row {
15834 to_fold.push(crease);
15835 } else {
15836 break;
15837 }
15838 }
15839 }
15840 }
15841
15842 self.fold_creases(to_fold, true, window, cx);
15843 }
15844
15845 pub fn fold_at(
15846 &mut self,
15847 buffer_row: MultiBufferRow,
15848 window: &mut Window,
15849 cx: &mut Context<Self>,
15850 ) {
15851 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15852
15853 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15854 let autoscroll = self
15855 .selections
15856 .all::<Point>(cx)
15857 .iter()
15858 .any(|selection| crease.range().overlaps(&selection.range()));
15859
15860 self.fold_creases(vec![crease], autoscroll, window, cx);
15861 }
15862 }
15863
15864 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15865 if self.is_singleton(cx) {
15866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15867 let buffer = &display_map.buffer_snapshot;
15868 let selections = self.selections.all::<Point>(cx);
15869 let ranges = selections
15870 .iter()
15871 .map(|s| {
15872 let range = s.display_range(&display_map).sorted();
15873 let mut start = range.start.to_point(&display_map);
15874 let mut end = range.end.to_point(&display_map);
15875 start.column = 0;
15876 end.column = buffer.line_len(MultiBufferRow(end.row));
15877 start..end
15878 })
15879 .collect::<Vec<_>>();
15880
15881 self.unfold_ranges(&ranges, true, true, cx);
15882 } else {
15883 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15884 let buffer_ids = self
15885 .selections
15886 .disjoint_anchor_ranges()
15887 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15888 .collect::<HashSet<_>>();
15889 for buffer_id in buffer_ids {
15890 self.unfold_buffer(buffer_id, cx);
15891 }
15892 }
15893 }
15894
15895 pub fn unfold_recursive(
15896 &mut self,
15897 _: &UnfoldRecursive,
15898 _window: &mut Window,
15899 cx: &mut Context<Self>,
15900 ) {
15901 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15902 let selections = self.selections.all::<Point>(cx);
15903 let ranges = selections
15904 .iter()
15905 .map(|s| {
15906 let mut range = s.display_range(&display_map).sorted();
15907 *range.start.column_mut() = 0;
15908 *range.end.column_mut() = display_map.line_len(range.end.row());
15909 let start = range.start.to_point(&display_map);
15910 let end = range.end.to_point(&display_map);
15911 start..end
15912 })
15913 .collect::<Vec<_>>();
15914
15915 self.unfold_ranges(&ranges, true, true, cx);
15916 }
15917
15918 pub fn unfold_at(
15919 &mut self,
15920 buffer_row: MultiBufferRow,
15921 _window: &mut Window,
15922 cx: &mut Context<Self>,
15923 ) {
15924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15925
15926 let intersection_range = Point::new(buffer_row.0, 0)
15927 ..Point::new(
15928 buffer_row.0,
15929 display_map.buffer_snapshot.line_len(buffer_row),
15930 );
15931
15932 let autoscroll = self
15933 .selections
15934 .all::<Point>(cx)
15935 .iter()
15936 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15937
15938 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15939 }
15940
15941 pub fn unfold_all(
15942 &mut self,
15943 _: &actions::UnfoldAll,
15944 _window: &mut Window,
15945 cx: &mut Context<Self>,
15946 ) {
15947 if self.buffer.read(cx).is_singleton() {
15948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15949 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15950 } else {
15951 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15952 editor
15953 .update(cx, |editor, cx| {
15954 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15955 editor.unfold_buffer(buffer_id, cx);
15956 }
15957 })
15958 .ok();
15959 });
15960 }
15961 }
15962
15963 pub fn fold_selected_ranges(
15964 &mut self,
15965 _: &FoldSelectedRanges,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) {
15969 let selections = self.selections.all_adjusted(cx);
15970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15971 let ranges = selections
15972 .into_iter()
15973 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15974 .collect::<Vec<_>>();
15975 self.fold_creases(ranges, true, window, cx);
15976 }
15977
15978 pub fn fold_ranges<T: ToOffset + Clone>(
15979 &mut self,
15980 ranges: Vec<Range<T>>,
15981 auto_scroll: bool,
15982 window: &mut Window,
15983 cx: &mut Context<Self>,
15984 ) {
15985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15986 let ranges = ranges
15987 .into_iter()
15988 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15989 .collect::<Vec<_>>();
15990 self.fold_creases(ranges, auto_scroll, window, cx);
15991 }
15992
15993 pub fn fold_creases<T: ToOffset + Clone>(
15994 &mut self,
15995 creases: Vec<Crease<T>>,
15996 auto_scroll: bool,
15997 _window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 if creases.is_empty() {
16001 return;
16002 }
16003
16004 let mut buffers_affected = HashSet::default();
16005 let multi_buffer = self.buffer().read(cx);
16006 for crease in &creases {
16007 if let Some((_, buffer, _)) =
16008 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16009 {
16010 buffers_affected.insert(buffer.read(cx).remote_id());
16011 };
16012 }
16013
16014 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16015
16016 if auto_scroll {
16017 self.request_autoscroll(Autoscroll::fit(), cx);
16018 }
16019
16020 cx.notify();
16021
16022 self.scrollbar_marker_state.dirty = true;
16023 self.folds_did_change(cx);
16024 }
16025
16026 /// Removes any folds whose ranges intersect any of the given ranges.
16027 pub fn unfold_ranges<T: ToOffset + Clone>(
16028 &mut self,
16029 ranges: &[Range<T>],
16030 inclusive: bool,
16031 auto_scroll: bool,
16032 cx: &mut Context<Self>,
16033 ) {
16034 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16035 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16036 });
16037 self.folds_did_change(cx);
16038 }
16039
16040 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16041 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16042 return;
16043 }
16044 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16045 self.display_map.update(cx, |display_map, cx| {
16046 display_map.fold_buffers([buffer_id], cx)
16047 });
16048 cx.emit(EditorEvent::BufferFoldToggled {
16049 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16050 folded: true,
16051 });
16052 cx.notify();
16053 }
16054
16055 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16056 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16057 return;
16058 }
16059 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16060 self.display_map.update(cx, |display_map, cx| {
16061 display_map.unfold_buffers([buffer_id], cx);
16062 });
16063 cx.emit(EditorEvent::BufferFoldToggled {
16064 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16065 folded: false,
16066 });
16067 cx.notify();
16068 }
16069
16070 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16071 self.display_map.read(cx).is_buffer_folded(buffer)
16072 }
16073
16074 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16075 self.display_map.read(cx).folded_buffers()
16076 }
16077
16078 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16079 self.display_map.update(cx, |display_map, cx| {
16080 display_map.disable_header_for_buffer(buffer_id, cx);
16081 });
16082 cx.notify();
16083 }
16084
16085 /// Removes any folds with the given ranges.
16086 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16087 &mut self,
16088 ranges: &[Range<T>],
16089 type_id: TypeId,
16090 auto_scroll: bool,
16091 cx: &mut Context<Self>,
16092 ) {
16093 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16094 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16095 });
16096 self.folds_did_change(cx);
16097 }
16098
16099 fn remove_folds_with<T: ToOffset + Clone>(
16100 &mut self,
16101 ranges: &[Range<T>],
16102 auto_scroll: bool,
16103 cx: &mut Context<Self>,
16104 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16105 ) {
16106 if ranges.is_empty() {
16107 return;
16108 }
16109
16110 let mut buffers_affected = HashSet::default();
16111 let multi_buffer = self.buffer().read(cx);
16112 for range in ranges {
16113 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16114 buffers_affected.insert(buffer.read(cx).remote_id());
16115 };
16116 }
16117
16118 self.display_map.update(cx, update);
16119
16120 if auto_scroll {
16121 self.request_autoscroll(Autoscroll::fit(), cx);
16122 }
16123
16124 cx.notify();
16125 self.scrollbar_marker_state.dirty = true;
16126 self.active_indent_guides_state.dirty = true;
16127 }
16128
16129 pub fn update_fold_widths(
16130 &mut self,
16131 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16132 cx: &mut Context<Self>,
16133 ) -> bool {
16134 self.display_map
16135 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16136 }
16137
16138 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16139 self.display_map.read(cx).fold_placeholder.clone()
16140 }
16141
16142 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16143 self.buffer.update(cx, |buffer, cx| {
16144 buffer.set_all_diff_hunks_expanded(cx);
16145 });
16146 }
16147
16148 pub fn expand_all_diff_hunks(
16149 &mut self,
16150 _: &ExpandAllDiffHunks,
16151 _window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) {
16154 self.buffer.update(cx, |buffer, cx| {
16155 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16156 });
16157 }
16158
16159 pub fn toggle_selected_diff_hunks(
16160 &mut self,
16161 _: &ToggleSelectedDiffHunks,
16162 _window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) {
16165 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16166 self.toggle_diff_hunks_in_ranges(ranges, cx);
16167 }
16168
16169 pub fn diff_hunks_in_ranges<'a>(
16170 &'a self,
16171 ranges: &'a [Range<Anchor>],
16172 buffer: &'a MultiBufferSnapshot,
16173 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16174 ranges.iter().flat_map(move |range| {
16175 let end_excerpt_id = range.end.excerpt_id;
16176 let range = range.to_point(buffer);
16177 let mut peek_end = range.end;
16178 if range.end.row < buffer.max_row().0 {
16179 peek_end = Point::new(range.end.row + 1, 0);
16180 }
16181 buffer
16182 .diff_hunks_in_range(range.start..peek_end)
16183 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16184 })
16185 }
16186
16187 pub fn has_stageable_diff_hunks_in_ranges(
16188 &self,
16189 ranges: &[Range<Anchor>],
16190 snapshot: &MultiBufferSnapshot,
16191 ) -> bool {
16192 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16193 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16194 }
16195
16196 pub fn toggle_staged_selected_diff_hunks(
16197 &mut self,
16198 _: &::git::ToggleStaged,
16199 _: &mut Window,
16200 cx: &mut Context<Self>,
16201 ) {
16202 let snapshot = self.buffer.read(cx).snapshot(cx);
16203 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16204 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16205 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16206 }
16207
16208 pub fn set_render_diff_hunk_controls(
16209 &mut self,
16210 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16211 cx: &mut Context<Self>,
16212 ) {
16213 self.render_diff_hunk_controls = render_diff_hunk_controls;
16214 cx.notify();
16215 }
16216
16217 pub fn stage_and_next(
16218 &mut self,
16219 _: &::git::StageAndNext,
16220 window: &mut Window,
16221 cx: &mut Context<Self>,
16222 ) {
16223 self.do_stage_or_unstage_and_next(true, window, cx);
16224 }
16225
16226 pub fn unstage_and_next(
16227 &mut self,
16228 _: &::git::UnstageAndNext,
16229 window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) {
16232 self.do_stage_or_unstage_and_next(false, window, cx);
16233 }
16234
16235 pub fn stage_or_unstage_diff_hunks(
16236 &mut self,
16237 stage: bool,
16238 ranges: Vec<Range<Anchor>>,
16239 cx: &mut Context<Self>,
16240 ) {
16241 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16242 cx.spawn(async move |this, cx| {
16243 task.await?;
16244 this.update(cx, |this, cx| {
16245 let snapshot = this.buffer.read(cx).snapshot(cx);
16246 let chunk_by = this
16247 .diff_hunks_in_ranges(&ranges, &snapshot)
16248 .chunk_by(|hunk| hunk.buffer_id);
16249 for (buffer_id, hunks) in &chunk_by {
16250 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16251 }
16252 })
16253 })
16254 .detach_and_log_err(cx);
16255 }
16256
16257 fn save_buffers_for_ranges_if_needed(
16258 &mut self,
16259 ranges: &[Range<Anchor>],
16260 cx: &mut Context<Editor>,
16261 ) -> Task<Result<()>> {
16262 let multibuffer = self.buffer.read(cx);
16263 let snapshot = multibuffer.read(cx);
16264 let buffer_ids: HashSet<_> = ranges
16265 .iter()
16266 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16267 .collect();
16268 drop(snapshot);
16269
16270 let mut buffers = HashSet::default();
16271 for buffer_id in buffer_ids {
16272 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16273 let buffer = buffer_entity.read(cx);
16274 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16275 {
16276 buffers.insert(buffer_entity);
16277 }
16278 }
16279 }
16280
16281 if let Some(project) = &self.project {
16282 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16283 } else {
16284 Task::ready(Ok(()))
16285 }
16286 }
16287
16288 fn do_stage_or_unstage_and_next(
16289 &mut self,
16290 stage: bool,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16295
16296 if ranges.iter().any(|range| range.start != range.end) {
16297 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16298 return;
16299 }
16300
16301 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16302 let snapshot = self.snapshot(window, cx);
16303 let position = self.selections.newest::<Point>(cx).head();
16304 let mut row = snapshot
16305 .buffer_snapshot
16306 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16307 .find(|hunk| hunk.row_range.start.0 > position.row)
16308 .map(|hunk| hunk.row_range.start);
16309
16310 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16311 // Outside of the project diff editor, wrap around to the beginning.
16312 if !all_diff_hunks_expanded {
16313 row = row.or_else(|| {
16314 snapshot
16315 .buffer_snapshot
16316 .diff_hunks_in_range(Point::zero()..position)
16317 .find(|hunk| hunk.row_range.end.0 < position.row)
16318 .map(|hunk| hunk.row_range.start)
16319 });
16320 }
16321
16322 if let Some(row) = row {
16323 let destination = Point::new(row.0, 0);
16324 let autoscroll = Autoscroll::center();
16325
16326 self.unfold_ranges(&[destination..destination], false, false, cx);
16327 self.change_selections(Some(autoscroll), window, cx, |s| {
16328 s.select_ranges([destination..destination]);
16329 });
16330 }
16331 }
16332
16333 fn do_stage_or_unstage(
16334 &self,
16335 stage: bool,
16336 buffer_id: BufferId,
16337 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16338 cx: &mut App,
16339 ) -> Option<()> {
16340 let project = self.project.as_ref()?;
16341 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16342 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16343 let buffer_snapshot = buffer.read(cx).snapshot();
16344 let file_exists = buffer_snapshot
16345 .file()
16346 .is_some_and(|file| file.disk_state().exists());
16347 diff.update(cx, |diff, cx| {
16348 diff.stage_or_unstage_hunks(
16349 stage,
16350 &hunks
16351 .map(|hunk| buffer_diff::DiffHunk {
16352 buffer_range: hunk.buffer_range,
16353 diff_base_byte_range: hunk.diff_base_byte_range,
16354 secondary_status: hunk.secondary_status,
16355 range: Point::zero()..Point::zero(), // unused
16356 })
16357 .collect::<Vec<_>>(),
16358 &buffer_snapshot,
16359 file_exists,
16360 cx,
16361 )
16362 });
16363 None
16364 }
16365
16366 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16367 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16368 self.buffer
16369 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16370 }
16371
16372 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16373 self.buffer.update(cx, |buffer, cx| {
16374 let ranges = vec![Anchor::min()..Anchor::max()];
16375 if !buffer.all_diff_hunks_expanded()
16376 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16377 {
16378 buffer.collapse_diff_hunks(ranges, cx);
16379 true
16380 } else {
16381 false
16382 }
16383 })
16384 }
16385
16386 fn toggle_diff_hunks_in_ranges(
16387 &mut self,
16388 ranges: Vec<Range<Anchor>>,
16389 cx: &mut Context<Editor>,
16390 ) {
16391 self.buffer.update(cx, |buffer, cx| {
16392 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16393 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16394 })
16395 }
16396
16397 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16398 self.buffer.update(cx, |buffer, cx| {
16399 let snapshot = buffer.snapshot(cx);
16400 let excerpt_id = range.end.excerpt_id;
16401 let point_range = range.to_point(&snapshot);
16402 let expand = !buffer.single_hunk_is_expanded(range, cx);
16403 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16404 })
16405 }
16406
16407 pub(crate) fn apply_all_diff_hunks(
16408 &mut self,
16409 _: &ApplyAllDiffHunks,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) {
16413 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16414
16415 let buffers = self.buffer.read(cx).all_buffers();
16416 for branch_buffer in buffers {
16417 branch_buffer.update(cx, |branch_buffer, cx| {
16418 branch_buffer.merge_into_base(Vec::new(), cx);
16419 });
16420 }
16421
16422 if let Some(project) = self.project.clone() {
16423 self.save(true, project, window, cx).detach_and_log_err(cx);
16424 }
16425 }
16426
16427 pub(crate) fn apply_selected_diff_hunks(
16428 &mut self,
16429 _: &ApplyDiffHunk,
16430 window: &mut Window,
16431 cx: &mut Context<Self>,
16432 ) {
16433 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16434 let snapshot = self.snapshot(window, cx);
16435 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16436 let mut ranges_by_buffer = HashMap::default();
16437 self.transact(window, cx, |editor, _window, cx| {
16438 for hunk in hunks {
16439 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16440 ranges_by_buffer
16441 .entry(buffer.clone())
16442 .or_insert_with(Vec::new)
16443 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16444 }
16445 }
16446
16447 for (buffer, ranges) in ranges_by_buffer {
16448 buffer.update(cx, |buffer, cx| {
16449 buffer.merge_into_base(ranges, cx);
16450 });
16451 }
16452 });
16453
16454 if let Some(project) = self.project.clone() {
16455 self.save(true, project, window, cx).detach_and_log_err(cx);
16456 }
16457 }
16458
16459 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16460 if hovered != self.gutter_hovered {
16461 self.gutter_hovered = hovered;
16462 cx.notify();
16463 }
16464 }
16465
16466 pub fn insert_blocks(
16467 &mut self,
16468 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16469 autoscroll: Option<Autoscroll>,
16470 cx: &mut Context<Self>,
16471 ) -> Vec<CustomBlockId> {
16472 let blocks = self
16473 .display_map
16474 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16475 if let Some(autoscroll) = autoscroll {
16476 self.request_autoscroll(autoscroll, cx);
16477 }
16478 cx.notify();
16479 blocks
16480 }
16481
16482 pub fn resize_blocks(
16483 &mut self,
16484 heights: HashMap<CustomBlockId, u32>,
16485 autoscroll: Option<Autoscroll>,
16486 cx: &mut Context<Self>,
16487 ) {
16488 self.display_map
16489 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16490 if let Some(autoscroll) = autoscroll {
16491 self.request_autoscroll(autoscroll, cx);
16492 }
16493 cx.notify();
16494 }
16495
16496 pub fn replace_blocks(
16497 &mut self,
16498 renderers: HashMap<CustomBlockId, RenderBlock>,
16499 autoscroll: Option<Autoscroll>,
16500 cx: &mut Context<Self>,
16501 ) {
16502 self.display_map
16503 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16504 if let Some(autoscroll) = autoscroll {
16505 self.request_autoscroll(autoscroll, cx);
16506 }
16507 cx.notify();
16508 }
16509
16510 pub fn remove_blocks(
16511 &mut self,
16512 block_ids: HashSet<CustomBlockId>,
16513 autoscroll: Option<Autoscroll>,
16514 cx: &mut Context<Self>,
16515 ) {
16516 self.display_map.update(cx, |display_map, cx| {
16517 display_map.remove_blocks(block_ids, cx)
16518 });
16519 if let Some(autoscroll) = autoscroll {
16520 self.request_autoscroll(autoscroll, cx);
16521 }
16522 cx.notify();
16523 }
16524
16525 pub fn row_for_block(
16526 &self,
16527 block_id: CustomBlockId,
16528 cx: &mut Context<Self>,
16529 ) -> Option<DisplayRow> {
16530 self.display_map
16531 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16532 }
16533
16534 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16535 self.focused_block = Some(focused_block);
16536 }
16537
16538 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16539 self.focused_block.take()
16540 }
16541
16542 pub fn insert_creases(
16543 &mut self,
16544 creases: impl IntoIterator<Item = Crease<Anchor>>,
16545 cx: &mut Context<Self>,
16546 ) -> Vec<CreaseId> {
16547 self.display_map
16548 .update(cx, |map, cx| map.insert_creases(creases, cx))
16549 }
16550
16551 pub fn remove_creases(
16552 &mut self,
16553 ids: impl IntoIterator<Item = CreaseId>,
16554 cx: &mut Context<Self>,
16555 ) -> Vec<(CreaseId, Range<Anchor>)> {
16556 self.display_map
16557 .update(cx, |map, cx| map.remove_creases(ids, cx))
16558 }
16559
16560 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16561 self.display_map
16562 .update(cx, |map, cx| map.snapshot(cx))
16563 .longest_row()
16564 }
16565
16566 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16567 self.display_map
16568 .update(cx, |map, cx| map.snapshot(cx))
16569 .max_point()
16570 }
16571
16572 pub fn text(&self, cx: &App) -> String {
16573 self.buffer.read(cx).read(cx).text()
16574 }
16575
16576 pub fn is_empty(&self, cx: &App) -> bool {
16577 self.buffer.read(cx).read(cx).is_empty()
16578 }
16579
16580 pub fn text_option(&self, cx: &App) -> Option<String> {
16581 let text = self.text(cx);
16582 let text = text.trim();
16583
16584 if text.is_empty() {
16585 return None;
16586 }
16587
16588 Some(text.to_string())
16589 }
16590
16591 pub fn set_text(
16592 &mut self,
16593 text: impl Into<Arc<str>>,
16594 window: &mut Window,
16595 cx: &mut Context<Self>,
16596 ) {
16597 self.transact(window, cx, |this, _, cx| {
16598 this.buffer
16599 .read(cx)
16600 .as_singleton()
16601 .expect("you can only call set_text on editors for singleton buffers")
16602 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16603 });
16604 }
16605
16606 pub fn display_text(&self, cx: &mut App) -> String {
16607 self.display_map
16608 .update(cx, |map, cx| map.snapshot(cx))
16609 .text()
16610 }
16611
16612 fn create_minimap(
16613 &self,
16614 minimap_settings: MinimapSettings,
16615 window: &mut Window,
16616 cx: &mut Context<Self>,
16617 ) -> Option<Entity<Self>> {
16618 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16619 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16620 }
16621
16622 fn initialize_new_minimap(
16623 &self,
16624 minimap_settings: MinimapSettings,
16625 window: &mut Window,
16626 cx: &mut Context<Self>,
16627 ) -> Entity<Self> {
16628 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16629
16630 let mut minimap = Editor::new_internal(
16631 EditorMode::Minimap {
16632 parent: cx.weak_entity(),
16633 },
16634 self.buffer.clone(),
16635 self.project.clone(),
16636 Some(self.display_map.clone()),
16637 window,
16638 cx,
16639 );
16640 minimap.scroll_manager.clone_state(&self.scroll_manager);
16641 minimap.set_text_style_refinement(TextStyleRefinement {
16642 font_size: Some(MINIMAP_FONT_SIZE),
16643 font_weight: Some(MINIMAP_FONT_WEIGHT),
16644 ..Default::default()
16645 });
16646 minimap.update_minimap_configuration(minimap_settings, cx);
16647 cx.new(|_| minimap)
16648 }
16649
16650 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16651 let current_line_highlight = minimap_settings
16652 .current_line_highlight
16653 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16654 self.set_current_line_highlight(Some(current_line_highlight));
16655 }
16656
16657 pub fn minimap(&self) -> Option<&Entity<Self>> {
16658 self.minimap
16659 .as_ref()
16660 .filter(|_| self.minimap_visibility.visible())
16661 }
16662
16663 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16664 let mut wrap_guides = smallvec::smallvec![];
16665
16666 if self.show_wrap_guides == Some(false) {
16667 return wrap_guides;
16668 }
16669
16670 let settings = self.buffer.read(cx).language_settings(cx);
16671 if settings.show_wrap_guides {
16672 match self.soft_wrap_mode(cx) {
16673 SoftWrap::Column(soft_wrap) => {
16674 wrap_guides.push((soft_wrap as usize, true));
16675 }
16676 SoftWrap::Bounded(soft_wrap) => {
16677 wrap_guides.push((soft_wrap as usize, true));
16678 }
16679 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16680 }
16681 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16682 }
16683
16684 wrap_guides
16685 }
16686
16687 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16688 let settings = self.buffer.read(cx).language_settings(cx);
16689 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16690 match mode {
16691 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16692 SoftWrap::None
16693 }
16694 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16695 language_settings::SoftWrap::PreferredLineLength => {
16696 SoftWrap::Column(settings.preferred_line_length)
16697 }
16698 language_settings::SoftWrap::Bounded => {
16699 SoftWrap::Bounded(settings.preferred_line_length)
16700 }
16701 }
16702 }
16703
16704 pub fn set_soft_wrap_mode(
16705 &mut self,
16706 mode: language_settings::SoftWrap,
16707
16708 cx: &mut Context<Self>,
16709 ) {
16710 self.soft_wrap_mode_override = Some(mode);
16711 cx.notify();
16712 }
16713
16714 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16715 self.hard_wrap = hard_wrap;
16716 cx.notify();
16717 }
16718
16719 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16720 self.text_style_refinement = Some(style);
16721 }
16722
16723 /// called by the Element so we know what style we were most recently rendered with.
16724 pub(crate) fn set_style(
16725 &mut self,
16726 style: EditorStyle,
16727 window: &mut Window,
16728 cx: &mut Context<Self>,
16729 ) {
16730 // We intentionally do not inform the display map about the minimap style
16731 // so that wrapping is not recalculated and stays consistent for the editor
16732 // and its linked minimap.
16733 if !self.mode.is_minimap() {
16734 let rem_size = window.rem_size();
16735 self.display_map.update(cx, |map, cx| {
16736 map.set_font(
16737 style.text.font(),
16738 style.text.font_size.to_pixels(rem_size),
16739 cx,
16740 )
16741 });
16742 }
16743 self.style = Some(style);
16744 }
16745
16746 pub fn style(&self) -> Option<&EditorStyle> {
16747 self.style.as_ref()
16748 }
16749
16750 // Called by the element. This method is not designed to be called outside of the editor
16751 // element's layout code because it does not notify when rewrapping is computed synchronously.
16752 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16753 self.display_map
16754 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16755 }
16756
16757 pub fn set_soft_wrap(&mut self) {
16758 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16759 }
16760
16761 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16762 if self.soft_wrap_mode_override.is_some() {
16763 self.soft_wrap_mode_override.take();
16764 } else {
16765 let soft_wrap = match self.soft_wrap_mode(cx) {
16766 SoftWrap::GitDiff => return,
16767 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16768 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16769 language_settings::SoftWrap::None
16770 }
16771 };
16772 self.soft_wrap_mode_override = Some(soft_wrap);
16773 }
16774 cx.notify();
16775 }
16776
16777 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16778 let Some(workspace) = self.workspace() else {
16779 return;
16780 };
16781 let fs = workspace.read(cx).app_state().fs.clone();
16782 let current_show = TabBarSettings::get_global(cx).show;
16783 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16784 setting.show = Some(!current_show);
16785 });
16786 }
16787
16788 pub fn toggle_indent_guides(
16789 &mut self,
16790 _: &ToggleIndentGuides,
16791 _: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) {
16794 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16795 self.buffer
16796 .read(cx)
16797 .language_settings(cx)
16798 .indent_guides
16799 .enabled
16800 });
16801 self.show_indent_guides = Some(!currently_enabled);
16802 cx.notify();
16803 }
16804
16805 fn should_show_indent_guides(&self) -> Option<bool> {
16806 self.show_indent_guides
16807 }
16808
16809 pub fn toggle_line_numbers(
16810 &mut self,
16811 _: &ToggleLineNumbers,
16812 _: &mut Window,
16813 cx: &mut Context<Self>,
16814 ) {
16815 let mut editor_settings = EditorSettings::get_global(cx).clone();
16816 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16817 EditorSettings::override_global(editor_settings, cx);
16818 }
16819
16820 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16821 if let Some(show_line_numbers) = self.show_line_numbers {
16822 return show_line_numbers;
16823 }
16824 EditorSettings::get_global(cx).gutter.line_numbers
16825 }
16826
16827 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16828 self.use_relative_line_numbers
16829 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16830 }
16831
16832 pub fn toggle_relative_line_numbers(
16833 &mut self,
16834 _: &ToggleRelativeLineNumbers,
16835 _: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 let is_relative = self.should_use_relative_line_numbers(cx);
16839 self.set_relative_line_number(Some(!is_relative), cx)
16840 }
16841
16842 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16843 self.use_relative_line_numbers = is_relative;
16844 cx.notify();
16845 }
16846
16847 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16848 self.show_gutter = show_gutter;
16849 cx.notify();
16850 }
16851
16852 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16853 self.show_scrollbars = show_scrollbars;
16854 cx.notify();
16855 }
16856
16857 pub fn set_minimap_visibility(
16858 &mut self,
16859 minimap_visibility: MinimapVisibility,
16860 window: &mut Window,
16861 cx: &mut Context<Self>,
16862 ) {
16863 if self.minimap_visibility != minimap_visibility {
16864 if minimap_visibility.visible() && self.minimap.is_none() {
16865 let minimap_settings = EditorSettings::get_global(cx).minimap;
16866 self.minimap =
16867 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16868 }
16869 self.minimap_visibility = minimap_visibility;
16870 cx.notify();
16871 }
16872 }
16873
16874 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16875 self.set_show_scrollbars(false, cx);
16876 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16877 }
16878
16879 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16880 self.show_line_numbers = Some(show_line_numbers);
16881 cx.notify();
16882 }
16883
16884 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16885 self.disable_expand_excerpt_buttons = true;
16886 cx.notify();
16887 }
16888
16889 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16890 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16891 cx.notify();
16892 }
16893
16894 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16895 self.show_code_actions = Some(show_code_actions);
16896 cx.notify();
16897 }
16898
16899 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16900 self.show_runnables = Some(show_runnables);
16901 cx.notify();
16902 }
16903
16904 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16905 self.show_breakpoints = Some(show_breakpoints);
16906 cx.notify();
16907 }
16908
16909 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16910 if self.display_map.read(cx).masked != masked {
16911 self.display_map.update(cx, |map, _| map.masked = masked);
16912 }
16913 cx.notify()
16914 }
16915
16916 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16917 self.show_wrap_guides = Some(show_wrap_guides);
16918 cx.notify();
16919 }
16920
16921 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16922 self.show_indent_guides = Some(show_indent_guides);
16923 cx.notify();
16924 }
16925
16926 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16927 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16928 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16929 if let Some(dir) = file.abs_path(cx).parent() {
16930 return Some(dir.to_owned());
16931 }
16932 }
16933
16934 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16935 return Some(project_path.path.to_path_buf());
16936 }
16937 }
16938
16939 None
16940 }
16941
16942 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16943 self.active_excerpt(cx)?
16944 .1
16945 .read(cx)
16946 .file()
16947 .and_then(|f| f.as_local())
16948 }
16949
16950 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16951 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16952 let buffer = buffer.read(cx);
16953 if let Some(project_path) = buffer.project_path(cx) {
16954 let project = self.project.as_ref()?.read(cx);
16955 project.absolute_path(&project_path, cx)
16956 } else {
16957 buffer
16958 .file()
16959 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16960 }
16961 })
16962 }
16963
16964 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16965 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16966 let project_path = buffer.read(cx).project_path(cx)?;
16967 let project = self.project.as_ref()?.read(cx);
16968 let entry = project.entry_for_path(&project_path, cx)?;
16969 let path = entry.path.to_path_buf();
16970 Some(path)
16971 })
16972 }
16973
16974 pub fn reveal_in_finder(
16975 &mut self,
16976 _: &RevealInFileManager,
16977 _window: &mut Window,
16978 cx: &mut Context<Self>,
16979 ) {
16980 if let Some(target) = self.target_file(cx) {
16981 cx.reveal_path(&target.abs_path(cx));
16982 }
16983 }
16984
16985 pub fn copy_path(
16986 &mut self,
16987 _: &zed_actions::workspace::CopyPath,
16988 _window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 if let Some(path) = self.target_file_abs_path(cx) {
16992 if let Some(path) = path.to_str() {
16993 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16994 }
16995 }
16996 }
16997
16998 pub fn copy_relative_path(
16999 &mut self,
17000 _: &zed_actions::workspace::CopyRelativePath,
17001 _window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) {
17004 if let Some(path) = self.target_file_path(cx) {
17005 if let Some(path) = path.to_str() {
17006 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17007 }
17008 }
17009 }
17010
17011 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17012 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17013 buffer.read(cx).project_path(cx)
17014 } else {
17015 None
17016 }
17017 }
17018
17019 // Returns true if the editor handled a go-to-line request
17020 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17021 maybe!({
17022 let breakpoint_store = self.breakpoint_store.as_ref()?;
17023
17024 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17025 else {
17026 self.clear_row_highlights::<ActiveDebugLine>();
17027 return None;
17028 };
17029
17030 let position = active_stack_frame.position;
17031 let buffer_id = position.buffer_id?;
17032 let snapshot = self
17033 .project
17034 .as_ref()?
17035 .read(cx)
17036 .buffer_for_id(buffer_id, cx)?
17037 .read(cx)
17038 .snapshot();
17039
17040 let mut handled = false;
17041 for (id, ExcerptRange { context, .. }) in
17042 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17043 {
17044 if context.start.cmp(&position, &snapshot).is_ge()
17045 || context.end.cmp(&position, &snapshot).is_lt()
17046 {
17047 continue;
17048 }
17049 let snapshot = self.buffer.read(cx).snapshot(cx);
17050 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17051
17052 handled = true;
17053 self.clear_row_highlights::<ActiveDebugLine>();
17054
17055 self.go_to_line::<ActiveDebugLine>(
17056 multibuffer_anchor,
17057 Some(cx.theme().colors().editor_debugger_active_line_background),
17058 window,
17059 cx,
17060 );
17061
17062 cx.notify();
17063 }
17064
17065 handled.then_some(())
17066 })
17067 .is_some()
17068 }
17069
17070 pub fn copy_file_name_without_extension(
17071 &mut self,
17072 _: &CopyFileNameWithoutExtension,
17073 _: &mut Window,
17074 cx: &mut Context<Self>,
17075 ) {
17076 if let Some(file) = self.target_file(cx) {
17077 if let Some(file_stem) = file.path().file_stem() {
17078 if let Some(name) = file_stem.to_str() {
17079 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17080 }
17081 }
17082 }
17083 }
17084
17085 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17086 if let Some(file) = self.target_file(cx) {
17087 if let Some(file_name) = file.path().file_name() {
17088 if let Some(name) = file_name.to_str() {
17089 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17090 }
17091 }
17092 }
17093 }
17094
17095 pub fn toggle_git_blame(
17096 &mut self,
17097 _: &::git::Blame,
17098 window: &mut Window,
17099 cx: &mut Context<Self>,
17100 ) {
17101 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17102
17103 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17104 self.start_git_blame(true, window, cx);
17105 }
17106
17107 cx.notify();
17108 }
17109
17110 pub fn toggle_git_blame_inline(
17111 &mut self,
17112 _: &ToggleGitBlameInline,
17113 window: &mut Window,
17114 cx: &mut Context<Self>,
17115 ) {
17116 self.toggle_git_blame_inline_internal(true, window, cx);
17117 cx.notify();
17118 }
17119
17120 pub fn open_git_blame_commit(
17121 &mut self,
17122 _: &OpenGitBlameCommit,
17123 window: &mut Window,
17124 cx: &mut Context<Self>,
17125 ) {
17126 self.open_git_blame_commit_internal(window, cx);
17127 }
17128
17129 fn open_git_blame_commit_internal(
17130 &mut self,
17131 window: &mut Window,
17132 cx: &mut Context<Self>,
17133 ) -> Option<()> {
17134 let blame = self.blame.as_ref()?;
17135 let snapshot = self.snapshot(window, cx);
17136 let cursor = self.selections.newest::<Point>(cx).head();
17137 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17138 let blame_entry = blame
17139 .update(cx, |blame, cx| {
17140 blame
17141 .blame_for_rows(
17142 &[RowInfo {
17143 buffer_id: Some(buffer.remote_id()),
17144 buffer_row: Some(point.row),
17145 ..Default::default()
17146 }],
17147 cx,
17148 )
17149 .next()
17150 })
17151 .flatten()?;
17152 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17153 let repo = blame.read(cx).repository(cx)?;
17154 let workspace = self.workspace()?.downgrade();
17155 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17156 None
17157 }
17158
17159 pub fn git_blame_inline_enabled(&self) -> bool {
17160 self.git_blame_inline_enabled
17161 }
17162
17163 pub fn toggle_selection_menu(
17164 &mut self,
17165 _: &ToggleSelectionMenu,
17166 _: &mut Window,
17167 cx: &mut Context<Self>,
17168 ) {
17169 self.show_selection_menu = self
17170 .show_selection_menu
17171 .map(|show_selections_menu| !show_selections_menu)
17172 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17173
17174 cx.notify();
17175 }
17176
17177 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17178 self.show_selection_menu
17179 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17180 }
17181
17182 fn start_git_blame(
17183 &mut self,
17184 user_triggered: bool,
17185 window: &mut Window,
17186 cx: &mut Context<Self>,
17187 ) {
17188 if let Some(project) = self.project.as_ref() {
17189 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17190 return;
17191 };
17192
17193 if buffer.read(cx).file().is_none() {
17194 return;
17195 }
17196
17197 let focused = self.focus_handle(cx).contains_focused(window, cx);
17198
17199 let project = project.clone();
17200 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17201 self.blame_subscription =
17202 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17203 self.blame = Some(blame);
17204 }
17205 }
17206
17207 fn toggle_git_blame_inline_internal(
17208 &mut self,
17209 user_triggered: bool,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) {
17213 if self.git_blame_inline_enabled {
17214 self.git_blame_inline_enabled = false;
17215 self.show_git_blame_inline = false;
17216 self.show_git_blame_inline_delay_task.take();
17217 } else {
17218 self.git_blame_inline_enabled = true;
17219 self.start_git_blame_inline(user_triggered, window, cx);
17220 }
17221
17222 cx.notify();
17223 }
17224
17225 fn start_git_blame_inline(
17226 &mut self,
17227 user_triggered: bool,
17228 window: &mut Window,
17229 cx: &mut Context<Self>,
17230 ) {
17231 self.start_git_blame(user_triggered, window, cx);
17232
17233 if ProjectSettings::get_global(cx)
17234 .git
17235 .inline_blame_delay()
17236 .is_some()
17237 {
17238 self.start_inline_blame_timer(window, cx);
17239 } else {
17240 self.show_git_blame_inline = true
17241 }
17242 }
17243
17244 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17245 self.blame.as_ref()
17246 }
17247
17248 pub fn show_git_blame_gutter(&self) -> bool {
17249 self.show_git_blame_gutter
17250 }
17251
17252 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17253 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17254 }
17255
17256 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17257 self.show_git_blame_inline
17258 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17259 && !self.newest_selection_head_on_empty_line(cx)
17260 && self.has_blame_entries(cx)
17261 }
17262
17263 fn has_blame_entries(&self, cx: &App) -> bool {
17264 self.blame()
17265 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17266 }
17267
17268 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17269 let cursor_anchor = self.selections.newest_anchor().head();
17270
17271 let snapshot = self.buffer.read(cx).snapshot(cx);
17272 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17273
17274 snapshot.line_len(buffer_row) == 0
17275 }
17276
17277 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17278 let buffer_and_selection = maybe!({
17279 let selection = self.selections.newest::<Point>(cx);
17280 let selection_range = selection.range();
17281
17282 let multi_buffer = self.buffer().read(cx);
17283 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17284 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17285
17286 let (buffer, range, _) = if selection.reversed {
17287 buffer_ranges.first()
17288 } else {
17289 buffer_ranges.last()
17290 }?;
17291
17292 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17293 ..text::ToPoint::to_point(&range.end, &buffer).row;
17294 Some((
17295 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17296 selection,
17297 ))
17298 });
17299
17300 let Some((buffer, selection)) = buffer_and_selection else {
17301 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17302 };
17303
17304 let Some(project) = self.project.as_ref() else {
17305 return Task::ready(Err(anyhow!("editor does not have project")));
17306 };
17307
17308 project.update(cx, |project, cx| {
17309 project.get_permalink_to_line(&buffer, selection, cx)
17310 })
17311 }
17312
17313 pub fn copy_permalink_to_line(
17314 &mut self,
17315 _: &CopyPermalinkToLine,
17316 window: &mut Window,
17317 cx: &mut Context<Self>,
17318 ) {
17319 let permalink_task = self.get_permalink_to_line(cx);
17320 let workspace = self.workspace();
17321
17322 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17323 Ok(permalink) => {
17324 cx.update(|_, cx| {
17325 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17326 })
17327 .ok();
17328 }
17329 Err(err) => {
17330 let message = format!("Failed to copy permalink: {err}");
17331
17332 Err::<(), anyhow::Error>(err).log_err();
17333
17334 if let Some(workspace) = workspace {
17335 workspace
17336 .update_in(cx, |workspace, _, cx| {
17337 struct CopyPermalinkToLine;
17338
17339 workspace.show_toast(
17340 Toast::new(
17341 NotificationId::unique::<CopyPermalinkToLine>(),
17342 message,
17343 ),
17344 cx,
17345 )
17346 })
17347 .ok();
17348 }
17349 }
17350 })
17351 .detach();
17352 }
17353
17354 pub fn copy_file_location(
17355 &mut self,
17356 _: &CopyFileLocation,
17357 _: &mut Window,
17358 cx: &mut Context<Self>,
17359 ) {
17360 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17361 if let Some(file) = self.target_file(cx) {
17362 if let Some(path) = file.path().to_str() {
17363 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17364 }
17365 }
17366 }
17367
17368 pub fn open_permalink_to_line(
17369 &mut self,
17370 _: &OpenPermalinkToLine,
17371 window: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) {
17374 let permalink_task = self.get_permalink_to_line(cx);
17375 let workspace = self.workspace();
17376
17377 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17378 Ok(permalink) => {
17379 cx.update(|_, cx| {
17380 cx.open_url(permalink.as_ref());
17381 })
17382 .ok();
17383 }
17384 Err(err) => {
17385 let message = format!("Failed to open permalink: {err}");
17386
17387 Err::<(), anyhow::Error>(err).log_err();
17388
17389 if let Some(workspace) = workspace {
17390 workspace
17391 .update(cx, |workspace, cx| {
17392 struct OpenPermalinkToLine;
17393
17394 workspace.show_toast(
17395 Toast::new(
17396 NotificationId::unique::<OpenPermalinkToLine>(),
17397 message,
17398 ),
17399 cx,
17400 )
17401 })
17402 .ok();
17403 }
17404 }
17405 })
17406 .detach();
17407 }
17408
17409 pub fn insert_uuid_v4(
17410 &mut self,
17411 _: &InsertUuidV4,
17412 window: &mut Window,
17413 cx: &mut Context<Self>,
17414 ) {
17415 self.insert_uuid(UuidVersion::V4, window, cx);
17416 }
17417
17418 pub fn insert_uuid_v7(
17419 &mut self,
17420 _: &InsertUuidV7,
17421 window: &mut Window,
17422 cx: &mut Context<Self>,
17423 ) {
17424 self.insert_uuid(UuidVersion::V7, window, cx);
17425 }
17426
17427 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17428 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17429 self.transact(window, cx, |this, window, cx| {
17430 let edits = this
17431 .selections
17432 .all::<Point>(cx)
17433 .into_iter()
17434 .map(|selection| {
17435 let uuid = match version {
17436 UuidVersion::V4 => uuid::Uuid::new_v4(),
17437 UuidVersion::V7 => uuid::Uuid::now_v7(),
17438 };
17439
17440 (selection.range(), uuid.to_string())
17441 });
17442 this.edit(edits, cx);
17443 this.refresh_inline_completion(true, false, window, cx);
17444 });
17445 }
17446
17447 pub fn open_selections_in_multibuffer(
17448 &mut self,
17449 _: &OpenSelectionsInMultibuffer,
17450 window: &mut Window,
17451 cx: &mut Context<Self>,
17452 ) {
17453 let multibuffer = self.buffer.read(cx);
17454
17455 let Some(buffer) = multibuffer.as_singleton() else {
17456 return;
17457 };
17458
17459 let Some(workspace) = self.workspace() else {
17460 return;
17461 };
17462
17463 let locations = self
17464 .selections
17465 .disjoint_anchors()
17466 .iter()
17467 .map(|range| Location {
17468 buffer: buffer.clone(),
17469 range: range.start.text_anchor..range.end.text_anchor,
17470 })
17471 .collect::<Vec<_>>();
17472
17473 let title = multibuffer.title(cx).to_string();
17474
17475 cx.spawn_in(window, async move |_, cx| {
17476 workspace.update_in(cx, |workspace, window, cx| {
17477 Self::open_locations_in_multibuffer(
17478 workspace,
17479 locations,
17480 format!("Selections for '{title}'"),
17481 false,
17482 MultibufferSelectionMode::All,
17483 window,
17484 cx,
17485 );
17486 })
17487 })
17488 .detach();
17489 }
17490
17491 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17492 /// last highlight added will be used.
17493 ///
17494 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17495 pub fn highlight_rows<T: 'static>(
17496 &mut self,
17497 range: Range<Anchor>,
17498 color: Hsla,
17499 options: RowHighlightOptions,
17500 cx: &mut Context<Self>,
17501 ) {
17502 let snapshot = self.buffer().read(cx).snapshot(cx);
17503 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17504 let ix = row_highlights.binary_search_by(|highlight| {
17505 Ordering::Equal
17506 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17507 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17508 });
17509
17510 if let Err(mut ix) = ix {
17511 let index = post_inc(&mut self.highlight_order);
17512
17513 // If this range intersects with the preceding highlight, then merge it with
17514 // the preceding highlight. Otherwise insert a new highlight.
17515 let mut merged = false;
17516 if ix > 0 {
17517 let prev_highlight = &mut row_highlights[ix - 1];
17518 if prev_highlight
17519 .range
17520 .end
17521 .cmp(&range.start, &snapshot)
17522 .is_ge()
17523 {
17524 ix -= 1;
17525 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17526 prev_highlight.range.end = range.end;
17527 }
17528 merged = true;
17529 prev_highlight.index = index;
17530 prev_highlight.color = color;
17531 prev_highlight.options = options;
17532 }
17533 }
17534
17535 if !merged {
17536 row_highlights.insert(
17537 ix,
17538 RowHighlight {
17539 range: range.clone(),
17540 index,
17541 color,
17542 options,
17543 type_id: TypeId::of::<T>(),
17544 },
17545 );
17546 }
17547
17548 // If any of the following highlights intersect with this one, merge them.
17549 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17550 let highlight = &row_highlights[ix];
17551 if next_highlight
17552 .range
17553 .start
17554 .cmp(&highlight.range.end, &snapshot)
17555 .is_le()
17556 {
17557 if next_highlight
17558 .range
17559 .end
17560 .cmp(&highlight.range.end, &snapshot)
17561 .is_gt()
17562 {
17563 row_highlights[ix].range.end = next_highlight.range.end;
17564 }
17565 row_highlights.remove(ix + 1);
17566 } else {
17567 break;
17568 }
17569 }
17570 }
17571 }
17572
17573 /// Remove any highlighted row ranges of the given type that intersect the
17574 /// given ranges.
17575 pub fn remove_highlighted_rows<T: 'static>(
17576 &mut self,
17577 ranges_to_remove: Vec<Range<Anchor>>,
17578 cx: &mut Context<Self>,
17579 ) {
17580 let snapshot = self.buffer().read(cx).snapshot(cx);
17581 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17582 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17583 row_highlights.retain(|highlight| {
17584 while let Some(range_to_remove) = ranges_to_remove.peek() {
17585 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17586 Ordering::Less | Ordering::Equal => {
17587 ranges_to_remove.next();
17588 }
17589 Ordering::Greater => {
17590 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17591 Ordering::Less | Ordering::Equal => {
17592 return false;
17593 }
17594 Ordering::Greater => break,
17595 }
17596 }
17597 }
17598 }
17599
17600 true
17601 })
17602 }
17603
17604 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17605 pub fn clear_row_highlights<T: 'static>(&mut self) {
17606 self.highlighted_rows.remove(&TypeId::of::<T>());
17607 }
17608
17609 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17610 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17611 self.highlighted_rows
17612 .get(&TypeId::of::<T>())
17613 .map_or(&[] as &[_], |vec| vec.as_slice())
17614 .iter()
17615 .map(|highlight| (highlight.range.clone(), highlight.color))
17616 }
17617
17618 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17619 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17620 /// Allows to ignore certain kinds of highlights.
17621 pub fn highlighted_display_rows(
17622 &self,
17623 window: &mut Window,
17624 cx: &mut App,
17625 ) -> BTreeMap<DisplayRow, LineHighlight> {
17626 let snapshot = self.snapshot(window, cx);
17627 let mut used_highlight_orders = HashMap::default();
17628 self.highlighted_rows
17629 .iter()
17630 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17631 .fold(
17632 BTreeMap::<DisplayRow, LineHighlight>::new(),
17633 |mut unique_rows, highlight| {
17634 let start = highlight.range.start.to_display_point(&snapshot);
17635 let end = highlight.range.end.to_display_point(&snapshot);
17636 let start_row = start.row().0;
17637 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17638 && end.column() == 0
17639 {
17640 end.row().0.saturating_sub(1)
17641 } else {
17642 end.row().0
17643 };
17644 for row in start_row..=end_row {
17645 let used_index =
17646 used_highlight_orders.entry(row).or_insert(highlight.index);
17647 if highlight.index >= *used_index {
17648 *used_index = highlight.index;
17649 unique_rows.insert(
17650 DisplayRow(row),
17651 LineHighlight {
17652 include_gutter: highlight.options.include_gutter,
17653 border: None,
17654 background: highlight.color.into(),
17655 type_id: Some(highlight.type_id),
17656 },
17657 );
17658 }
17659 }
17660 unique_rows
17661 },
17662 )
17663 }
17664
17665 pub fn highlighted_display_row_for_autoscroll(
17666 &self,
17667 snapshot: &DisplaySnapshot,
17668 ) -> Option<DisplayRow> {
17669 self.highlighted_rows
17670 .values()
17671 .flat_map(|highlighted_rows| highlighted_rows.iter())
17672 .filter_map(|highlight| {
17673 if highlight.options.autoscroll {
17674 Some(highlight.range.start.to_display_point(snapshot).row())
17675 } else {
17676 None
17677 }
17678 })
17679 .min()
17680 }
17681
17682 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17683 self.highlight_background::<SearchWithinRange>(
17684 ranges,
17685 |colors| colors.editor_document_highlight_read_background,
17686 cx,
17687 )
17688 }
17689
17690 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17691 self.breadcrumb_header = Some(new_header);
17692 }
17693
17694 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17695 self.clear_background_highlights::<SearchWithinRange>(cx);
17696 }
17697
17698 pub fn highlight_background<T: 'static>(
17699 &mut self,
17700 ranges: &[Range<Anchor>],
17701 color_fetcher: fn(&ThemeColors) -> Hsla,
17702 cx: &mut Context<Self>,
17703 ) {
17704 self.background_highlights
17705 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17706 self.scrollbar_marker_state.dirty = true;
17707 cx.notify();
17708 }
17709
17710 pub fn clear_background_highlights<T: 'static>(
17711 &mut self,
17712 cx: &mut Context<Self>,
17713 ) -> Option<BackgroundHighlight> {
17714 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17715 if !text_highlights.1.is_empty() {
17716 self.scrollbar_marker_state.dirty = true;
17717 cx.notify();
17718 }
17719 Some(text_highlights)
17720 }
17721
17722 pub fn highlight_gutter<T: 'static>(
17723 &mut self,
17724 ranges: &[Range<Anchor>],
17725 color_fetcher: fn(&App) -> Hsla,
17726 cx: &mut Context<Self>,
17727 ) {
17728 self.gutter_highlights
17729 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17730 cx.notify();
17731 }
17732
17733 pub fn clear_gutter_highlights<T: 'static>(
17734 &mut self,
17735 cx: &mut Context<Self>,
17736 ) -> Option<GutterHighlight> {
17737 cx.notify();
17738 self.gutter_highlights.remove(&TypeId::of::<T>())
17739 }
17740
17741 #[cfg(feature = "test-support")]
17742 pub fn all_text_background_highlights(
17743 &self,
17744 window: &mut Window,
17745 cx: &mut Context<Self>,
17746 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17747 let snapshot = self.snapshot(window, cx);
17748 let buffer = &snapshot.buffer_snapshot;
17749 let start = buffer.anchor_before(0);
17750 let end = buffer.anchor_after(buffer.len());
17751 let theme = cx.theme().colors();
17752 self.background_highlights_in_range(start..end, &snapshot, theme)
17753 }
17754
17755 #[cfg(feature = "test-support")]
17756 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17757 let snapshot = self.buffer().read(cx).snapshot(cx);
17758
17759 let highlights = self
17760 .background_highlights
17761 .get(&TypeId::of::<items::BufferSearchHighlights>());
17762
17763 if let Some((_color, ranges)) = highlights {
17764 ranges
17765 .iter()
17766 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17767 .collect_vec()
17768 } else {
17769 vec![]
17770 }
17771 }
17772
17773 fn document_highlights_for_position<'a>(
17774 &'a self,
17775 position: Anchor,
17776 buffer: &'a MultiBufferSnapshot,
17777 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17778 let read_highlights = self
17779 .background_highlights
17780 .get(&TypeId::of::<DocumentHighlightRead>())
17781 .map(|h| &h.1);
17782 let write_highlights = self
17783 .background_highlights
17784 .get(&TypeId::of::<DocumentHighlightWrite>())
17785 .map(|h| &h.1);
17786 let left_position = position.bias_left(buffer);
17787 let right_position = position.bias_right(buffer);
17788 read_highlights
17789 .into_iter()
17790 .chain(write_highlights)
17791 .flat_map(move |ranges| {
17792 let start_ix = match ranges.binary_search_by(|probe| {
17793 let cmp = probe.end.cmp(&left_position, buffer);
17794 if cmp.is_ge() {
17795 Ordering::Greater
17796 } else {
17797 Ordering::Less
17798 }
17799 }) {
17800 Ok(i) | Err(i) => i,
17801 };
17802
17803 ranges[start_ix..]
17804 .iter()
17805 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17806 })
17807 }
17808
17809 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17810 self.background_highlights
17811 .get(&TypeId::of::<T>())
17812 .map_or(false, |(_, highlights)| !highlights.is_empty())
17813 }
17814
17815 pub fn background_highlights_in_range(
17816 &self,
17817 search_range: Range<Anchor>,
17818 display_snapshot: &DisplaySnapshot,
17819 theme: &ThemeColors,
17820 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17821 let mut results = Vec::new();
17822 for (color_fetcher, ranges) in self.background_highlights.values() {
17823 let color = color_fetcher(theme);
17824 let start_ix = match ranges.binary_search_by(|probe| {
17825 let cmp = probe
17826 .end
17827 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17828 if cmp.is_gt() {
17829 Ordering::Greater
17830 } else {
17831 Ordering::Less
17832 }
17833 }) {
17834 Ok(i) | Err(i) => i,
17835 };
17836 for range in &ranges[start_ix..] {
17837 if range
17838 .start
17839 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17840 .is_ge()
17841 {
17842 break;
17843 }
17844
17845 let start = range.start.to_display_point(display_snapshot);
17846 let end = range.end.to_display_point(display_snapshot);
17847 results.push((start..end, color))
17848 }
17849 }
17850 results
17851 }
17852
17853 pub fn background_highlight_row_ranges<T: 'static>(
17854 &self,
17855 search_range: Range<Anchor>,
17856 display_snapshot: &DisplaySnapshot,
17857 count: usize,
17858 ) -> Vec<RangeInclusive<DisplayPoint>> {
17859 let mut results = Vec::new();
17860 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17861 return vec![];
17862 };
17863
17864 let start_ix = match ranges.binary_search_by(|probe| {
17865 let cmp = probe
17866 .end
17867 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17868 if cmp.is_gt() {
17869 Ordering::Greater
17870 } else {
17871 Ordering::Less
17872 }
17873 }) {
17874 Ok(i) | Err(i) => i,
17875 };
17876 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17877 if let (Some(start_display), Some(end_display)) = (start, end) {
17878 results.push(
17879 start_display.to_display_point(display_snapshot)
17880 ..=end_display.to_display_point(display_snapshot),
17881 );
17882 }
17883 };
17884 let mut start_row: Option<Point> = None;
17885 let mut end_row: Option<Point> = None;
17886 if ranges.len() > count {
17887 return Vec::new();
17888 }
17889 for range in &ranges[start_ix..] {
17890 if range
17891 .start
17892 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17893 .is_ge()
17894 {
17895 break;
17896 }
17897 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17898 if let Some(current_row) = &end_row {
17899 if end.row == current_row.row {
17900 continue;
17901 }
17902 }
17903 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17904 if start_row.is_none() {
17905 assert_eq!(end_row, None);
17906 start_row = Some(start);
17907 end_row = Some(end);
17908 continue;
17909 }
17910 if let Some(current_end) = end_row.as_mut() {
17911 if start.row > current_end.row + 1 {
17912 push_region(start_row, end_row);
17913 start_row = Some(start);
17914 end_row = Some(end);
17915 } else {
17916 // Merge two hunks.
17917 *current_end = end;
17918 }
17919 } else {
17920 unreachable!();
17921 }
17922 }
17923 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17924 push_region(start_row, end_row);
17925 results
17926 }
17927
17928 pub fn gutter_highlights_in_range(
17929 &self,
17930 search_range: Range<Anchor>,
17931 display_snapshot: &DisplaySnapshot,
17932 cx: &App,
17933 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17934 let mut results = Vec::new();
17935 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17936 let color = color_fetcher(cx);
17937 let start_ix = match ranges.binary_search_by(|probe| {
17938 let cmp = probe
17939 .end
17940 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17941 if cmp.is_gt() {
17942 Ordering::Greater
17943 } else {
17944 Ordering::Less
17945 }
17946 }) {
17947 Ok(i) | Err(i) => i,
17948 };
17949 for range in &ranges[start_ix..] {
17950 if range
17951 .start
17952 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17953 .is_ge()
17954 {
17955 break;
17956 }
17957
17958 let start = range.start.to_display_point(display_snapshot);
17959 let end = range.end.to_display_point(display_snapshot);
17960 results.push((start..end, color))
17961 }
17962 }
17963 results
17964 }
17965
17966 /// Get the text ranges corresponding to the redaction query
17967 pub fn redacted_ranges(
17968 &self,
17969 search_range: Range<Anchor>,
17970 display_snapshot: &DisplaySnapshot,
17971 cx: &App,
17972 ) -> Vec<Range<DisplayPoint>> {
17973 display_snapshot
17974 .buffer_snapshot
17975 .redacted_ranges(search_range, |file| {
17976 if let Some(file) = file {
17977 file.is_private()
17978 && EditorSettings::get(
17979 Some(SettingsLocation {
17980 worktree_id: file.worktree_id(cx),
17981 path: file.path().as_ref(),
17982 }),
17983 cx,
17984 )
17985 .redact_private_values
17986 } else {
17987 false
17988 }
17989 })
17990 .map(|range| {
17991 range.start.to_display_point(display_snapshot)
17992 ..range.end.to_display_point(display_snapshot)
17993 })
17994 .collect()
17995 }
17996
17997 pub fn highlight_text<T: 'static>(
17998 &mut self,
17999 ranges: Vec<Range<Anchor>>,
18000 style: HighlightStyle,
18001 cx: &mut Context<Self>,
18002 ) {
18003 self.display_map.update(cx, |map, _| {
18004 map.highlight_text(TypeId::of::<T>(), ranges, style)
18005 });
18006 cx.notify();
18007 }
18008
18009 pub(crate) fn highlight_inlays<T: 'static>(
18010 &mut self,
18011 highlights: Vec<InlayHighlight>,
18012 style: HighlightStyle,
18013 cx: &mut Context<Self>,
18014 ) {
18015 self.display_map.update(cx, |map, _| {
18016 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18017 });
18018 cx.notify();
18019 }
18020
18021 pub fn text_highlights<'a, T: 'static>(
18022 &'a self,
18023 cx: &'a App,
18024 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18025 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18026 }
18027
18028 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18029 let cleared = self
18030 .display_map
18031 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18032 if cleared {
18033 cx.notify();
18034 }
18035 }
18036
18037 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18038 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18039 && self.focus_handle.is_focused(window)
18040 }
18041
18042 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18043 self.show_cursor_when_unfocused = is_enabled;
18044 cx.notify();
18045 }
18046
18047 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18048 cx.notify();
18049 }
18050
18051 fn on_debug_session_event(
18052 &mut self,
18053 _session: Entity<Session>,
18054 event: &SessionEvent,
18055 cx: &mut Context<Self>,
18056 ) {
18057 match event {
18058 SessionEvent::InvalidateInlineValue => {
18059 self.refresh_inline_values(cx);
18060 }
18061 _ => {}
18062 }
18063 }
18064
18065 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18066 let Some(project) = self.project.clone() else {
18067 return;
18068 };
18069
18070 if !self.inline_value_cache.enabled {
18071 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18072 self.splice_inlays(&inlays, Vec::new(), cx);
18073 return;
18074 }
18075
18076 let current_execution_position = self
18077 .highlighted_rows
18078 .get(&TypeId::of::<ActiveDebugLine>())
18079 .and_then(|lines| lines.last().map(|line| line.range.start));
18080
18081 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18082 let inline_values = editor
18083 .update(cx, |editor, cx| {
18084 let Some(current_execution_position) = current_execution_position else {
18085 return Some(Task::ready(Ok(Vec::new())));
18086 };
18087
18088 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18089 let snapshot = buffer.snapshot(cx);
18090
18091 let excerpt = snapshot.excerpt_containing(
18092 current_execution_position..current_execution_position,
18093 )?;
18094
18095 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18096 })?;
18097
18098 let range =
18099 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18100
18101 project.inline_values(buffer, range, cx)
18102 })
18103 .ok()
18104 .flatten()?
18105 .await
18106 .context("refreshing debugger inlays")
18107 .log_err()?;
18108
18109 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18110
18111 for (buffer_id, inline_value) in inline_values
18112 .into_iter()
18113 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18114 {
18115 buffer_inline_values
18116 .entry(buffer_id)
18117 .or_default()
18118 .push(inline_value);
18119 }
18120
18121 editor
18122 .update(cx, |editor, cx| {
18123 let snapshot = editor.buffer.read(cx).snapshot(cx);
18124 let mut new_inlays = Vec::default();
18125
18126 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18127 let buffer_id = buffer_snapshot.remote_id();
18128 buffer_inline_values
18129 .get(&buffer_id)
18130 .into_iter()
18131 .flatten()
18132 .for_each(|hint| {
18133 let inlay = Inlay::debugger_hint(
18134 post_inc(&mut editor.next_inlay_id),
18135 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18136 hint.text(),
18137 );
18138
18139 new_inlays.push(inlay);
18140 });
18141 }
18142
18143 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18144 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18145
18146 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18147 })
18148 .ok()?;
18149 Some(())
18150 });
18151 }
18152
18153 fn on_buffer_event(
18154 &mut self,
18155 multibuffer: &Entity<MultiBuffer>,
18156 event: &multi_buffer::Event,
18157 window: &mut Window,
18158 cx: &mut Context<Self>,
18159 ) {
18160 match event {
18161 multi_buffer::Event::Edited {
18162 singleton_buffer_edited,
18163 edited_buffer: buffer_edited,
18164 } => {
18165 self.scrollbar_marker_state.dirty = true;
18166 self.active_indent_guides_state.dirty = true;
18167 self.refresh_active_diagnostics(cx);
18168 self.refresh_code_actions(window, cx);
18169 self.refresh_selected_text_highlights(true, window, cx);
18170 refresh_matching_bracket_highlights(self, window, cx);
18171 if self.has_active_inline_completion() {
18172 self.update_visible_inline_completion(window, cx);
18173 }
18174 if let Some(buffer) = buffer_edited {
18175 let buffer_id = buffer.read(cx).remote_id();
18176 if !self.registered_buffers.contains_key(&buffer_id) {
18177 if let Some(project) = self.project.as_ref() {
18178 project.update(cx, |project, cx| {
18179 self.registered_buffers.insert(
18180 buffer_id,
18181 project.register_buffer_with_language_servers(&buffer, cx),
18182 );
18183 })
18184 }
18185 }
18186 }
18187 cx.emit(EditorEvent::BufferEdited);
18188 cx.emit(SearchEvent::MatchesInvalidated);
18189 if *singleton_buffer_edited {
18190 if let Some(project) = &self.project {
18191 #[allow(clippy::mutable_key_type)]
18192 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18193 multibuffer
18194 .all_buffers()
18195 .into_iter()
18196 .filter_map(|buffer| {
18197 buffer.update(cx, |buffer, cx| {
18198 let language = buffer.language()?;
18199 let should_discard = project.update(cx, |project, cx| {
18200 project.is_local()
18201 && !project.has_language_servers_for(buffer, cx)
18202 });
18203 should_discard.not().then_some(language.clone())
18204 })
18205 })
18206 .collect::<HashSet<_>>()
18207 });
18208 if !languages_affected.is_empty() {
18209 self.refresh_inlay_hints(
18210 InlayHintRefreshReason::BufferEdited(languages_affected),
18211 cx,
18212 );
18213 }
18214 }
18215 }
18216
18217 let Some(project) = &self.project else { return };
18218 let (telemetry, is_via_ssh) = {
18219 let project = project.read(cx);
18220 let telemetry = project.client().telemetry().clone();
18221 let is_via_ssh = project.is_via_ssh();
18222 (telemetry, is_via_ssh)
18223 };
18224 refresh_linked_ranges(self, window, cx);
18225 telemetry.log_edit_event("editor", is_via_ssh);
18226 }
18227 multi_buffer::Event::ExcerptsAdded {
18228 buffer,
18229 predecessor,
18230 excerpts,
18231 } => {
18232 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18233 let buffer_id = buffer.read(cx).remote_id();
18234 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18235 if let Some(project) = &self.project {
18236 update_uncommitted_diff_for_buffer(
18237 cx.entity(),
18238 project,
18239 [buffer.clone()],
18240 self.buffer.clone(),
18241 cx,
18242 )
18243 .detach();
18244 }
18245 }
18246 cx.emit(EditorEvent::ExcerptsAdded {
18247 buffer: buffer.clone(),
18248 predecessor: *predecessor,
18249 excerpts: excerpts.clone(),
18250 });
18251 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18252 }
18253 multi_buffer::Event::ExcerptsRemoved {
18254 ids,
18255 removed_buffer_ids,
18256 } => {
18257 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18258 let buffer = self.buffer.read(cx);
18259 self.registered_buffers
18260 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18261 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18262 cx.emit(EditorEvent::ExcerptsRemoved {
18263 ids: ids.clone(),
18264 removed_buffer_ids: removed_buffer_ids.clone(),
18265 })
18266 }
18267 multi_buffer::Event::ExcerptsEdited {
18268 excerpt_ids,
18269 buffer_ids,
18270 } => {
18271 self.display_map.update(cx, |map, cx| {
18272 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18273 });
18274 cx.emit(EditorEvent::ExcerptsEdited {
18275 ids: excerpt_ids.clone(),
18276 })
18277 }
18278 multi_buffer::Event::ExcerptsExpanded { ids } => {
18279 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18280 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18281 }
18282 multi_buffer::Event::Reparsed(buffer_id) => {
18283 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18284 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18285
18286 cx.emit(EditorEvent::Reparsed(*buffer_id));
18287 }
18288 multi_buffer::Event::DiffHunksToggled => {
18289 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18290 }
18291 multi_buffer::Event::LanguageChanged(buffer_id) => {
18292 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18293 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18294 cx.emit(EditorEvent::Reparsed(*buffer_id));
18295 cx.notify();
18296 }
18297 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18298 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18299 multi_buffer::Event::FileHandleChanged
18300 | multi_buffer::Event::Reloaded
18301 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18302 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18303 multi_buffer::Event::DiagnosticsUpdated => {
18304 self.refresh_active_diagnostics(cx);
18305 self.refresh_inline_diagnostics(true, window, cx);
18306 self.scrollbar_marker_state.dirty = true;
18307 cx.notify();
18308 }
18309 _ => {}
18310 };
18311 }
18312
18313 pub fn start_temporary_diff_override(&mut self) {
18314 self.load_diff_task.take();
18315 self.temporary_diff_override = true;
18316 }
18317
18318 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18319 self.temporary_diff_override = false;
18320 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18321 self.buffer.update(cx, |buffer, cx| {
18322 buffer.set_all_diff_hunks_collapsed(cx);
18323 });
18324
18325 if let Some(project) = self.project.clone() {
18326 self.load_diff_task = Some(
18327 update_uncommitted_diff_for_buffer(
18328 cx.entity(),
18329 &project,
18330 self.buffer.read(cx).all_buffers(),
18331 self.buffer.clone(),
18332 cx,
18333 )
18334 .shared(),
18335 );
18336 }
18337 }
18338
18339 fn on_display_map_changed(
18340 &mut self,
18341 _: Entity<DisplayMap>,
18342 _: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 cx.notify();
18346 }
18347
18348 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18349 let new_severity = if self.diagnostics_enabled() {
18350 EditorSettings::get_global(cx)
18351 .diagnostics_max_severity
18352 .unwrap_or(DiagnosticSeverity::Hint)
18353 } else {
18354 DiagnosticSeverity::Off
18355 };
18356 self.set_max_diagnostics_severity(new_severity, cx);
18357 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18358 self.update_edit_prediction_settings(cx);
18359 self.refresh_inline_completion(true, false, window, cx);
18360 self.refresh_inlay_hints(
18361 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18362 self.selections.newest_anchor().head(),
18363 &self.buffer.read(cx).snapshot(cx),
18364 cx,
18365 )),
18366 cx,
18367 );
18368
18369 let old_cursor_shape = self.cursor_shape;
18370
18371 {
18372 let editor_settings = EditorSettings::get_global(cx);
18373 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18374 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18375 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18376 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18377 }
18378
18379 if old_cursor_shape != self.cursor_shape {
18380 cx.emit(EditorEvent::CursorShapeChanged);
18381 }
18382
18383 let project_settings = ProjectSettings::get_global(cx);
18384 self.serialize_dirty_buffers =
18385 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18386
18387 if self.mode.is_full() {
18388 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18389 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18390 if self.show_inline_diagnostics != show_inline_diagnostics {
18391 self.show_inline_diagnostics = show_inline_diagnostics;
18392 self.refresh_inline_diagnostics(false, window, cx);
18393 }
18394
18395 if self.git_blame_inline_enabled != inline_blame_enabled {
18396 self.toggle_git_blame_inline_internal(false, window, cx);
18397 }
18398
18399 let minimap_settings = EditorSettings::get_global(cx).minimap;
18400 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18401 self.set_minimap_visibility(
18402 self.minimap_visibility.toggle_visibility(),
18403 window,
18404 cx,
18405 );
18406 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18407 minimap_entity.update(cx, |minimap_editor, cx| {
18408 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18409 })
18410 }
18411 }
18412
18413 cx.notify();
18414 }
18415
18416 pub fn set_searchable(&mut self, searchable: bool) {
18417 self.searchable = searchable;
18418 }
18419
18420 pub fn searchable(&self) -> bool {
18421 self.searchable
18422 }
18423
18424 fn open_proposed_changes_editor(
18425 &mut self,
18426 _: &OpenProposedChangesEditor,
18427 window: &mut Window,
18428 cx: &mut Context<Self>,
18429 ) {
18430 let Some(workspace) = self.workspace() else {
18431 cx.propagate();
18432 return;
18433 };
18434
18435 let selections = self.selections.all::<usize>(cx);
18436 let multi_buffer = self.buffer.read(cx);
18437 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18438 let mut new_selections_by_buffer = HashMap::default();
18439 for selection in selections {
18440 for (buffer, range, _) in
18441 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18442 {
18443 let mut range = range.to_point(buffer);
18444 range.start.column = 0;
18445 range.end.column = buffer.line_len(range.end.row);
18446 new_selections_by_buffer
18447 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18448 .or_insert(Vec::new())
18449 .push(range)
18450 }
18451 }
18452
18453 let proposed_changes_buffers = new_selections_by_buffer
18454 .into_iter()
18455 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18456 .collect::<Vec<_>>();
18457 let proposed_changes_editor = cx.new(|cx| {
18458 ProposedChangesEditor::new(
18459 "Proposed changes",
18460 proposed_changes_buffers,
18461 self.project.clone(),
18462 window,
18463 cx,
18464 )
18465 });
18466
18467 window.defer(cx, move |window, cx| {
18468 workspace.update(cx, |workspace, cx| {
18469 workspace.active_pane().update(cx, |pane, cx| {
18470 pane.add_item(
18471 Box::new(proposed_changes_editor),
18472 true,
18473 true,
18474 None,
18475 window,
18476 cx,
18477 );
18478 });
18479 });
18480 });
18481 }
18482
18483 pub fn open_excerpts_in_split(
18484 &mut self,
18485 _: &OpenExcerptsSplit,
18486 window: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) {
18489 self.open_excerpts_common(None, true, window, cx)
18490 }
18491
18492 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18493 self.open_excerpts_common(None, false, window, cx)
18494 }
18495
18496 fn open_excerpts_common(
18497 &mut self,
18498 jump_data: Option<JumpData>,
18499 split: bool,
18500 window: &mut Window,
18501 cx: &mut Context<Self>,
18502 ) {
18503 let Some(workspace) = self.workspace() else {
18504 cx.propagate();
18505 return;
18506 };
18507
18508 if self.buffer.read(cx).is_singleton() {
18509 cx.propagate();
18510 return;
18511 }
18512
18513 let mut new_selections_by_buffer = HashMap::default();
18514 match &jump_data {
18515 Some(JumpData::MultiBufferPoint {
18516 excerpt_id,
18517 position,
18518 anchor,
18519 line_offset_from_top,
18520 }) => {
18521 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18522 if let Some(buffer) = multi_buffer_snapshot
18523 .buffer_id_for_excerpt(*excerpt_id)
18524 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18525 {
18526 let buffer_snapshot = buffer.read(cx).snapshot();
18527 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18528 language::ToPoint::to_point(anchor, &buffer_snapshot)
18529 } else {
18530 buffer_snapshot.clip_point(*position, Bias::Left)
18531 };
18532 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18533 new_selections_by_buffer.insert(
18534 buffer,
18535 (
18536 vec![jump_to_offset..jump_to_offset],
18537 Some(*line_offset_from_top),
18538 ),
18539 );
18540 }
18541 }
18542 Some(JumpData::MultiBufferRow {
18543 row,
18544 line_offset_from_top,
18545 }) => {
18546 let point = MultiBufferPoint::new(row.0, 0);
18547 if let Some((buffer, buffer_point, _)) =
18548 self.buffer.read(cx).point_to_buffer_point(point, cx)
18549 {
18550 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18551 new_selections_by_buffer
18552 .entry(buffer)
18553 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18554 .0
18555 .push(buffer_offset..buffer_offset)
18556 }
18557 }
18558 None => {
18559 let selections = self.selections.all::<usize>(cx);
18560 let multi_buffer = self.buffer.read(cx);
18561 for selection in selections {
18562 for (snapshot, range, _, anchor) in multi_buffer
18563 .snapshot(cx)
18564 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18565 {
18566 if let Some(anchor) = anchor {
18567 // selection is in a deleted hunk
18568 let Some(buffer_id) = anchor.buffer_id else {
18569 continue;
18570 };
18571 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18572 continue;
18573 };
18574 let offset = text::ToOffset::to_offset(
18575 &anchor.text_anchor,
18576 &buffer_handle.read(cx).snapshot(),
18577 );
18578 let range = offset..offset;
18579 new_selections_by_buffer
18580 .entry(buffer_handle)
18581 .or_insert((Vec::new(), None))
18582 .0
18583 .push(range)
18584 } else {
18585 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18586 else {
18587 continue;
18588 };
18589 new_selections_by_buffer
18590 .entry(buffer_handle)
18591 .or_insert((Vec::new(), None))
18592 .0
18593 .push(range)
18594 }
18595 }
18596 }
18597 }
18598 }
18599
18600 new_selections_by_buffer
18601 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18602
18603 if new_selections_by_buffer.is_empty() {
18604 return;
18605 }
18606
18607 // We defer the pane interaction because we ourselves are a workspace item
18608 // and activating a new item causes the pane to call a method on us reentrantly,
18609 // which panics if we're on the stack.
18610 window.defer(cx, move |window, cx| {
18611 workspace.update(cx, |workspace, cx| {
18612 let pane = if split {
18613 workspace.adjacent_pane(window, cx)
18614 } else {
18615 workspace.active_pane().clone()
18616 };
18617
18618 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18619 let editor = buffer
18620 .read(cx)
18621 .file()
18622 .is_none()
18623 .then(|| {
18624 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18625 // so `workspace.open_project_item` will never find them, always opening a new editor.
18626 // Instead, we try to activate the existing editor in the pane first.
18627 let (editor, pane_item_index) =
18628 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18629 let editor = item.downcast::<Editor>()?;
18630 let singleton_buffer =
18631 editor.read(cx).buffer().read(cx).as_singleton()?;
18632 if singleton_buffer == buffer {
18633 Some((editor, i))
18634 } else {
18635 None
18636 }
18637 })?;
18638 pane.update(cx, |pane, cx| {
18639 pane.activate_item(pane_item_index, true, true, window, cx)
18640 });
18641 Some(editor)
18642 })
18643 .flatten()
18644 .unwrap_or_else(|| {
18645 workspace.open_project_item::<Self>(
18646 pane.clone(),
18647 buffer,
18648 true,
18649 true,
18650 window,
18651 cx,
18652 )
18653 });
18654
18655 editor.update(cx, |editor, cx| {
18656 let autoscroll = match scroll_offset {
18657 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18658 None => Autoscroll::newest(),
18659 };
18660 let nav_history = editor.nav_history.take();
18661 editor.change_selections(Some(autoscroll), window, cx, |s| {
18662 s.select_ranges(ranges);
18663 });
18664 editor.nav_history = nav_history;
18665 });
18666 }
18667 })
18668 });
18669 }
18670
18671 // For now, don't allow opening excerpts in buffers that aren't backed by
18672 // regular project files.
18673 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18674 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18675 }
18676
18677 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18678 let snapshot = self.buffer.read(cx).read(cx);
18679 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18680 Some(
18681 ranges
18682 .iter()
18683 .map(move |range| {
18684 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18685 })
18686 .collect(),
18687 )
18688 }
18689
18690 fn selection_replacement_ranges(
18691 &self,
18692 range: Range<OffsetUtf16>,
18693 cx: &mut App,
18694 ) -> Vec<Range<OffsetUtf16>> {
18695 let selections = self.selections.all::<OffsetUtf16>(cx);
18696 let newest_selection = selections
18697 .iter()
18698 .max_by_key(|selection| selection.id)
18699 .unwrap();
18700 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18701 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18702 let snapshot = self.buffer.read(cx).read(cx);
18703 selections
18704 .into_iter()
18705 .map(|mut selection| {
18706 selection.start.0 =
18707 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18708 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18709 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18710 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18711 })
18712 .collect()
18713 }
18714
18715 fn report_editor_event(
18716 &self,
18717 event_type: &'static str,
18718 file_extension: Option<String>,
18719 cx: &App,
18720 ) {
18721 if cfg!(any(test, feature = "test-support")) {
18722 return;
18723 }
18724
18725 let Some(project) = &self.project else { return };
18726
18727 // If None, we are in a file without an extension
18728 let file = self
18729 .buffer
18730 .read(cx)
18731 .as_singleton()
18732 .and_then(|b| b.read(cx).file());
18733 let file_extension = file_extension.or(file
18734 .as_ref()
18735 .and_then(|file| Path::new(file.file_name(cx)).extension())
18736 .and_then(|e| e.to_str())
18737 .map(|a| a.to_string()));
18738
18739 let vim_mode = vim_enabled(cx);
18740
18741 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18742 let copilot_enabled = edit_predictions_provider
18743 == language::language_settings::EditPredictionProvider::Copilot;
18744 let copilot_enabled_for_language = self
18745 .buffer
18746 .read(cx)
18747 .language_settings(cx)
18748 .show_edit_predictions;
18749
18750 let project = project.read(cx);
18751 telemetry::event!(
18752 event_type,
18753 file_extension,
18754 vim_mode,
18755 copilot_enabled,
18756 copilot_enabled_for_language,
18757 edit_predictions_provider,
18758 is_via_ssh = project.is_via_ssh(),
18759 );
18760 }
18761
18762 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18763 /// with each line being an array of {text, highlight} objects.
18764 fn copy_highlight_json(
18765 &mut self,
18766 _: &CopyHighlightJson,
18767 window: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) {
18770 #[derive(Serialize)]
18771 struct Chunk<'a> {
18772 text: String,
18773 highlight: Option<&'a str>,
18774 }
18775
18776 let snapshot = self.buffer.read(cx).snapshot(cx);
18777 let range = self
18778 .selected_text_range(false, window, cx)
18779 .and_then(|selection| {
18780 if selection.range.is_empty() {
18781 None
18782 } else {
18783 Some(selection.range)
18784 }
18785 })
18786 .unwrap_or_else(|| 0..snapshot.len());
18787
18788 let chunks = snapshot.chunks(range, true);
18789 let mut lines = Vec::new();
18790 let mut line: VecDeque<Chunk> = VecDeque::new();
18791
18792 let Some(style) = self.style.as_ref() else {
18793 return;
18794 };
18795
18796 for chunk in chunks {
18797 let highlight = chunk
18798 .syntax_highlight_id
18799 .and_then(|id| id.name(&style.syntax));
18800 let mut chunk_lines = chunk.text.split('\n').peekable();
18801 while let Some(text) = chunk_lines.next() {
18802 let mut merged_with_last_token = false;
18803 if let Some(last_token) = line.back_mut() {
18804 if last_token.highlight == highlight {
18805 last_token.text.push_str(text);
18806 merged_with_last_token = true;
18807 }
18808 }
18809
18810 if !merged_with_last_token {
18811 line.push_back(Chunk {
18812 text: text.into(),
18813 highlight,
18814 });
18815 }
18816
18817 if chunk_lines.peek().is_some() {
18818 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18819 line.pop_front();
18820 }
18821 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18822 line.pop_back();
18823 }
18824
18825 lines.push(mem::take(&mut line));
18826 }
18827 }
18828 }
18829
18830 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18831 return;
18832 };
18833 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18834 }
18835
18836 pub fn open_context_menu(
18837 &mut self,
18838 _: &OpenContextMenu,
18839 window: &mut Window,
18840 cx: &mut Context<Self>,
18841 ) {
18842 self.request_autoscroll(Autoscroll::newest(), cx);
18843 let position = self.selections.newest_display(cx).start;
18844 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18845 }
18846
18847 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18848 &self.inlay_hint_cache
18849 }
18850
18851 pub fn replay_insert_event(
18852 &mut self,
18853 text: &str,
18854 relative_utf16_range: Option<Range<isize>>,
18855 window: &mut Window,
18856 cx: &mut Context<Self>,
18857 ) {
18858 if !self.input_enabled {
18859 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18860 return;
18861 }
18862 if let Some(relative_utf16_range) = relative_utf16_range {
18863 let selections = self.selections.all::<OffsetUtf16>(cx);
18864 self.change_selections(None, window, cx, |s| {
18865 let new_ranges = selections.into_iter().map(|range| {
18866 let start = OffsetUtf16(
18867 range
18868 .head()
18869 .0
18870 .saturating_add_signed(relative_utf16_range.start),
18871 );
18872 let end = OffsetUtf16(
18873 range
18874 .head()
18875 .0
18876 .saturating_add_signed(relative_utf16_range.end),
18877 );
18878 start..end
18879 });
18880 s.select_ranges(new_ranges);
18881 });
18882 }
18883
18884 self.handle_input(text, window, cx);
18885 }
18886
18887 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18888 let Some(provider) = self.semantics_provider.as_ref() else {
18889 return false;
18890 };
18891
18892 let mut supports = false;
18893 self.buffer().update(cx, |this, cx| {
18894 this.for_each_buffer(|buffer| {
18895 supports |= provider.supports_inlay_hints(buffer, cx);
18896 });
18897 });
18898
18899 supports
18900 }
18901
18902 pub fn is_focused(&self, window: &Window) -> bool {
18903 self.focus_handle.is_focused(window)
18904 }
18905
18906 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18907 cx.emit(EditorEvent::Focused);
18908
18909 if let Some(descendant) = self
18910 .last_focused_descendant
18911 .take()
18912 .and_then(|descendant| descendant.upgrade())
18913 {
18914 window.focus(&descendant);
18915 } else {
18916 if let Some(blame) = self.blame.as_ref() {
18917 blame.update(cx, GitBlame::focus)
18918 }
18919
18920 self.blink_manager.update(cx, BlinkManager::enable);
18921 self.show_cursor_names(window, cx);
18922 self.buffer.update(cx, |buffer, cx| {
18923 buffer.finalize_last_transaction(cx);
18924 if self.leader_id.is_none() {
18925 buffer.set_active_selections(
18926 &self.selections.disjoint_anchors(),
18927 self.selections.line_mode,
18928 self.cursor_shape,
18929 cx,
18930 );
18931 }
18932 });
18933 }
18934 }
18935
18936 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18937 cx.emit(EditorEvent::FocusedIn)
18938 }
18939
18940 fn handle_focus_out(
18941 &mut self,
18942 event: FocusOutEvent,
18943 _window: &mut Window,
18944 cx: &mut Context<Self>,
18945 ) {
18946 if event.blurred != self.focus_handle {
18947 self.last_focused_descendant = Some(event.blurred);
18948 }
18949 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18950 }
18951
18952 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18953 self.blink_manager.update(cx, BlinkManager::disable);
18954 self.buffer
18955 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18956
18957 if let Some(blame) = self.blame.as_ref() {
18958 blame.update(cx, GitBlame::blur)
18959 }
18960 if !self.hover_state.focused(window, cx) {
18961 hide_hover(self, cx);
18962 }
18963 if !self
18964 .context_menu
18965 .borrow()
18966 .as_ref()
18967 .is_some_and(|context_menu| context_menu.focused(window, cx))
18968 {
18969 self.hide_context_menu(window, cx);
18970 }
18971 self.discard_inline_completion(false, cx);
18972 cx.emit(EditorEvent::Blurred);
18973 cx.notify();
18974 }
18975
18976 pub fn register_action<A: Action>(
18977 &mut self,
18978 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18979 ) -> Subscription {
18980 let id = self.next_editor_action_id.post_inc();
18981 let listener = Arc::new(listener);
18982 self.editor_actions.borrow_mut().insert(
18983 id,
18984 Box::new(move |window, _| {
18985 let listener = listener.clone();
18986 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18987 let action = action.downcast_ref().unwrap();
18988 if phase == DispatchPhase::Bubble {
18989 listener(action, window, cx)
18990 }
18991 })
18992 }),
18993 );
18994
18995 let editor_actions = self.editor_actions.clone();
18996 Subscription::new(move || {
18997 editor_actions.borrow_mut().remove(&id);
18998 })
18999 }
19000
19001 pub fn file_header_size(&self) -> u32 {
19002 FILE_HEADER_HEIGHT
19003 }
19004
19005 pub fn restore(
19006 &mut self,
19007 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19008 window: &mut Window,
19009 cx: &mut Context<Self>,
19010 ) {
19011 let workspace = self.workspace();
19012 let project = self.project.as_ref();
19013 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19014 let mut tasks = Vec::new();
19015 for (buffer_id, changes) in revert_changes {
19016 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19017 buffer.update(cx, |buffer, cx| {
19018 buffer.edit(
19019 changes
19020 .into_iter()
19021 .map(|(range, text)| (range, text.to_string())),
19022 None,
19023 cx,
19024 );
19025 });
19026
19027 if let Some(project) =
19028 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19029 {
19030 project.update(cx, |project, cx| {
19031 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19032 })
19033 }
19034 }
19035 }
19036 tasks
19037 });
19038 cx.spawn_in(window, async move |_, cx| {
19039 for (buffer, task) in save_tasks {
19040 let result = task.await;
19041 if result.is_err() {
19042 let Some(path) = buffer
19043 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19044 .ok()
19045 else {
19046 continue;
19047 };
19048 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19049 let Some(task) = cx
19050 .update_window_entity(&workspace, |workspace, window, cx| {
19051 workspace
19052 .open_path_preview(path, None, false, false, false, window, cx)
19053 })
19054 .ok()
19055 else {
19056 continue;
19057 };
19058 task.await.log_err();
19059 }
19060 }
19061 }
19062 })
19063 .detach();
19064 self.change_selections(None, window, cx, |selections| selections.refresh());
19065 }
19066
19067 pub fn to_pixel_point(
19068 &self,
19069 source: multi_buffer::Anchor,
19070 editor_snapshot: &EditorSnapshot,
19071 window: &mut Window,
19072 ) -> Option<gpui::Point<Pixels>> {
19073 let source_point = source.to_display_point(editor_snapshot);
19074 self.display_to_pixel_point(source_point, editor_snapshot, window)
19075 }
19076
19077 pub fn display_to_pixel_point(
19078 &self,
19079 source: DisplayPoint,
19080 editor_snapshot: &EditorSnapshot,
19081 window: &mut Window,
19082 ) -> Option<gpui::Point<Pixels>> {
19083 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19084 let text_layout_details = self.text_layout_details(window);
19085 let scroll_top = text_layout_details
19086 .scroll_anchor
19087 .scroll_position(editor_snapshot)
19088 .y;
19089
19090 if source.row().as_f32() < scroll_top.floor() {
19091 return None;
19092 }
19093 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19094 let source_y = line_height * (source.row().as_f32() - scroll_top);
19095 Some(gpui::Point::new(source_x, source_y))
19096 }
19097
19098 pub fn has_visible_completions_menu(&self) -> bool {
19099 !self.edit_prediction_preview_is_active()
19100 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19101 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19102 })
19103 }
19104
19105 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19106 if self.mode.is_minimap() {
19107 return;
19108 }
19109 self.addons
19110 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19111 }
19112
19113 pub fn unregister_addon<T: Addon>(&mut self) {
19114 self.addons.remove(&std::any::TypeId::of::<T>());
19115 }
19116
19117 pub fn addon<T: Addon>(&self) -> Option<&T> {
19118 let type_id = std::any::TypeId::of::<T>();
19119 self.addons
19120 .get(&type_id)
19121 .and_then(|item| item.to_any().downcast_ref::<T>())
19122 }
19123
19124 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19125 let type_id = std::any::TypeId::of::<T>();
19126 self.addons
19127 .get_mut(&type_id)
19128 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19129 }
19130
19131 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19132 let text_layout_details = self.text_layout_details(window);
19133 let style = &text_layout_details.editor_style;
19134 let font_id = window.text_system().resolve_font(&style.text.font());
19135 let font_size = style.text.font_size.to_pixels(window.rem_size());
19136 let line_height = style.text.line_height_in_pixels(window.rem_size());
19137 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19138
19139 gpui::Size::new(em_width, line_height)
19140 }
19141
19142 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19143 self.load_diff_task.clone()
19144 }
19145
19146 fn read_metadata_from_db(
19147 &mut self,
19148 item_id: u64,
19149 workspace_id: WorkspaceId,
19150 window: &mut Window,
19151 cx: &mut Context<Editor>,
19152 ) {
19153 if self.is_singleton(cx)
19154 && !self.mode.is_minimap()
19155 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19156 {
19157 let buffer_snapshot = OnceCell::new();
19158
19159 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19160 if !folds.is_empty() {
19161 let snapshot =
19162 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19163 self.fold_ranges(
19164 folds
19165 .into_iter()
19166 .map(|(start, end)| {
19167 snapshot.clip_offset(start, Bias::Left)
19168 ..snapshot.clip_offset(end, Bias::Right)
19169 })
19170 .collect(),
19171 false,
19172 window,
19173 cx,
19174 );
19175 }
19176 }
19177
19178 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19179 if !selections.is_empty() {
19180 let snapshot =
19181 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19182 self.change_selections(None, window, cx, |s| {
19183 s.select_ranges(selections.into_iter().map(|(start, end)| {
19184 snapshot.clip_offset(start, Bias::Left)
19185 ..snapshot.clip_offset(end, Bias::Right)
19186 }));
19187 });
19188 }
19189 };
19190 }
19191
19192 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19193 }
19194}
19195
19196fn vim_enabled(cx: &App) -> bool {
19197 cx.global::<SettingsStore>()
19198 .raw_user_settings()
19199 .get("vim_mode")
19200 == Some(&serde_json::Value::Bool(true))
19201}
19202
19203// Consider user intent and default settings
19204fn choose_completion_range(
19205 completion: &Completion,
19206 intent: CompletionIntent,
19207 buffer: &Entity<Buffer>,
19208 cx: &mut Context<Editor>,
19209) -> Range<usize> {
19210 fn should_replace(
19211 completion: &Completion,
19212 insert_range: &Range<text::Anchor>,
19213 intent: CompletionIntent,
19214 completion_mode_setting: LspInsertMode,
19215 buffer: &Buffer,
19216 ) -> bool {
19217 // specific actions take precedence over settings
19218 match intent {
19219 CompletionIntent::CompleteWithInsert => return false,
19220 CompletionIntent::CompleteWithReplace => return true,
19221 CompletionIntent::Complete | CompletionIntent::Compose => {}
19222 }
19223
19224 match completion_mode_setting {
19225 LspInsertMode::Insert => false,
19226 LspInsertMode::Replace => true,
19227 LspInsertMode::ReplaceSubsequence => {
19228 let mut text_to_replace = buffer.chars_for_range(
19229 buffer.anchor_before(completion.replace_range.start)
19230 ..buffer.anchor_after(completion.replace_range.end),
19231 );
19232 let mut completion_text = completion.new_text.chars();
19233
19234 // is `text_to_replace` a subsequence of `completion_text`
19235 text_to_replace
19236 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19237 }
19238 LspInsertMode::ReplaceSuffix => {
19239 let range_after_cursor = insert_range.end..completion.replace_range.end;
19240
19241 let text_after_cursor = buffer
19242 .text_for_range(
19243 buffer.anchor_before(range_after_cursor.start)
19244 ..buffer.anchor_after(range_after_cursor.end),
19245 )
19246 .collect::<String>();
19247 completion.new_text.ends_with(&text_after_cursor)
19248 }
19249 }
19250 }
19251
19252 let buffer = buffer.read(cx);
19253
19254 if let CompletionSource::Lsp {
19255 insert_range: Some(insert_range),
19256 ..
19257 } = &completion.source
19258 {
19259 let completion_mode_setting =
19260 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19261 .completions
19262 .lsp_insert_mode;
19263
19264 if !should_replace(
19265 completion,
19266 &insert_range,
19267 intent,
19268 completion_mode_setting,
19269 buffer,
19270 ) {
19271 return insert_range.to_offset(buffer);
19272 }
19273 }
19274
19275 completion.replace_range.to_offset(buffer)
19276}
19277
19278fn insert_extra_newline_brackets(
19279 buffer: &MultiBufferSnapshot,
19280 range: Range<usize>,
19281 language: &language::LanguageScope,
19282) -> bool {
19283 let leading_whitespace_len = buffer
19284 .reversed_chars_at(range.start)
19285 .take_while(|c| c.is_whitespace() && *c != '\n')
19286 .map(|c| c.len_utf8())
19287 .sum::<usize>();
19288 let trailing_whitespace_len = buffer
19289 .chars_at(range.end)
19290 .take_while(|c| c.is_whitespace() && *c != '\n')
19291 .map(|c| c.len_utf8())
19292 .sum::<usize>();
19293 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19294
19295 language.brackets().any(|(pair, enabled)| {
19296 let pair_start = pair.start.trim_end();
19297 let pair_end = pair.end.trim_start();
19298
19299 enabled
19300 && pair.newline
19301 && buffer.contains_str_at(range.end, pair_end)
19302 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19303 })
19304}
19305
19306fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19307 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19308 [(buffer, range, _)] => (*buffer, range.clone()),
19309 _ => return false,
19310 };
19311 let pair = {
19312 let mut result: Option<BracketMatch> = None;
19313
19314 for pair in buffer
19315 .all_bracket_ranges(range.clone())
19316 .filter(move |pair| {
19317 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19318 })
19319 {
19320 let len = pair.close_range.end - pair.open_range.start;
19321
19322 if let Some(existing) = &result {
19323 let existing_len = existing.close_range.end - existing.open_range.start;
19324 if len > existing_len {
19325 continue;
19326 }
19327 }
19328
19329 result = Some(pair);
19330 }
19331
19332 result
19333 };
19334 let Some(pair) = pair else {
19335 return false;
19336 };
19337 pair.newline_only
19338 && buffer
19339 .chars_for_range(pair.open_range.end..range.start)
19340 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19341 .all(|c| c.is_whitespace() && c != '\n')
19342}
19343
19344fn update_uncommitted_diff_for_buffer(
19345 editor: Entity<Editor>,
19346 project: &Entity<Project>,
19347 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19348 buffer: Entity<MultiBuffer>,
19349 cx: &mut App,
19350) -> Task<()> {
19351 let mut tasks = Vec::new();
19352 project.update(cx, |project, cx| {
19353 for buffer in buffers {
19354 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19355 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19356 }
19357 }
19358 });
19359 cx.spawn(async move |cx| {
19360 let diffs = future::join_all(tasks).await;
19361 if editor
19362 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19363 .unwrap_or(false)
19364 {
19365 return;
19366 }
19367
19368 buffer
19369 .update(cx, |buffer, cx| {
19370 for diff in diffs.into_iter().flatten() {
19371 buffer.add_diff(diff, cx);
19372 }
19373 })
19374 .ok();
19375 })
19376}
19377
19378fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19379 let tab_size = tab_size.get() as usize;
19380 let mut width = offset;
19381
19382 for ch in text.chars() {
19383 width += if ch == '\t' {
19384 tab_size - (width % tab_size)
19385 } else {
19386 1
19387 };
19388 }
19389
19390 width - offset
19391}
19392
19393#[cfg(test)]
19394mod tests {
19395 use super::*;
19396
19397 #[test]
19398 fn test_string_size_with_expanded_tabs() {
19399 let nz = |val| NonZeroU32::new(val).unwrap();
19400 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19401 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19402 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19403 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19404 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19405 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19406 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19407 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19408 }
19409}
19410
19411/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19412struct WordBreakingTokenizer<'a> {
19413 input: &'a str,
19414}
19415
19416impl<'a> WordBreakingTokenizer<'a> {
19417 fn new(input: &'a str) -> Self {
19418 Self { input }
19419 }
19420}
19421
19422fn is_char_ideographic(ch: char) -> bool {
19423 use unicode_script::Script::*;
19424 use unicode_script::UnicodeScript;
19425 matches!(ch.script(), Han | Tangut | Yi)
19426}
19427
19428fn is_grapheme_ideographic(text: &str) -> bool {
19429 text.chars().any(is_char_ideographic)
19430}
19431
19432fn is_grapheme_whitespace(text: &str) -> bool {
19433 text.chars().any(|x| x.is_whitespace())
19434}
19435
19436fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19437 text.chars().next().map_or(false, |ch| {
19438 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19439 })
19440}
19441
19442#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19443enum WordBreakToken<'a> {
19444 Word { token: &'a str, grapheme_len: usize },
19445 InlineWhitespace { token: &'a str, grapheme_len: usize },
19446 Newline,
19447}
19448
19449impl<'a> Iterator for WordBreakingTokenizer<'a> {
19450 /// Yields a span, the count of graphemes in the token, and whether it was
19451 /// whitespace. Note that it also breaks at word boundaries.
19452 type Item = WordBreakToken<'a>;
19453
19454 fn next(&mut self) -> Option<Self::Item> {
19455 use unicode_segmentation::UnicodeSegmentation;
19456 if self.input.is_empty() {
19457 return None;
19458 }
19459
19460 let mut iter = self.input.graphemes(true).peekable();
19461 let mut offset = 0;
19462 let mut grapheme_len = 0;
19463 if let Some(first_grapheme) = iter.next() {
19464 let is_newline = first_grapheme == "\n";
19465 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19466 offset += first_grapheme.len();
19467 grapheme_len += 1;
19468 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19469 if let Some(grapheme) = iter.peek().copied() {
19470 if should_stay_with_preceding_ideograph(grapheme) {
19471 offset += grapheme.len();
19472 grapheme_len += 1;
19473 }
19474 }
19475 } else {
19476 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19477 let mut next_word_bound = words.peek().copied();
19478 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19479 next_word_bound = words.next();
19480 }
19481 while let Some(grapheme) = iter.peek().copied() {
19482 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19483 break;
19484 };
19485 if is_grapheme_whitespace(grapheme) != is_whitespace
19486 || (grapheme == "\n") != is_newline
19487 {
19488 break;
19489 };
19490 offset += grapheme.len();
19491 grapheme_len += 1;
19492 iter.next();
19493 }
19494 }
19495 let token = &self.input[..offset];
19496 self.input = &self.input[offset..];
19497 if token == "\n" {
19498 Some(WordBreakToken::Newline)
19499 } else if is_whitespace {
19500 Some(WordBreakToken::InlineWhitespace {
19501 token,
19502 grapheme_len,
19503 })
19504 } else {
19505 Some(WordBreakToken::Word {
19506 token,
19507 grapheme_len,
19508 })
19509 }
19510 } else {
19511 None
19512 }
19513 }
19514}
19515
19516#[test]
19517fn test_word_breaking_tokenizer() {
19518 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19519 ("", &[]),
19520 (" ", &[whitespace(" ", 2)]),
19521 ("Ʒ", &[word("Ʒ", 1)]),
19522 ("Ǽ", &[word("Ǽ", 1)]),
19523 ("⋑", &[word("⋑", 1)]),
19524 ("⋑⋑", &[word("⋑⋑", 2)]),
19525 (
19526 "原理,进而",
19527 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19528 ),
19529 (
19530 "hello world",
19531 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19532 ),
19533 (
19534 "hello, world",
19535 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19536 ),
19537 (
19538 " hello world",
19539 &[
19540 whitespace(" ", 2),
19541 word("hello", 5),
19542 whitespace(" ", 1),
19543 word("world", 5),
19544 ],
19545 ),
19546 (
19547 "这是什么 \n 钢笔",
19548 &[
19549 word("这", 1),
19550 word("是", 1),
19551 word("什", 1),
19552 word("么", 1),
19553 whitespace(" ", 1),
19554 newline(),
19555 whitespace(" ", 1),
19556 word("钢", 1),
19557 word("笔", 1),
19558 ],
19559 ),
19560 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19561 ];
19562
19563 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19564 WordBreakToken::Word {
19565 token,
19566 grapheme_len,
19567 }
19568 }
19569
19570 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19571 WordBreakToken::InlineWhitespace {
19572 token,
19573 grapheme_len,
19574 }
19575 }
19576
19577 fn newline() -> WordBreakToken<'static> {
19578 WordBreakToken::Newline
19579 }
19580
19581 for (input, result) in tests {
19582 assert_eq!(
19583 WordBreakingTokenizer::new(input)
19584 .collect::<Vec<_>>()
19585 .as_slice(),
19586 *result,
19587 );
19588 }
19589}
19590
19591fn wrap_with_prefix(
19592 line_prefix: String,
19593 unwrapped_text: String,
19594 wrap_column: usize,
19595 tab_size: NonZeroU32,
19596 preserve_existing_whitespace: bool,
19597) -> String {
19598 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19599 let mut wrapped_text = String::new();
19600 let mut current_line = line_prefix.clone();
19601
19602 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19603 let mut current_line_len = line_prefix_len;
19604 let mut in_whitespace = false;
19605 for token in tokenizer {
19606 let have_preceding_whitespace = in_whitespace;
19607 match token {
19608 WordBreakToken::Word {
19609 token,
19610 grapheme_len,
19611 } => {
19612 in_whitespace = false;
19613 if current_line_len + grapheme_len > wrap_column
19614 && current_line_len != line_prefix_len
19615 {
19616 wrapped_text.push_str(current_line.trim_end());
19617 wrapped_text.push('\n');
19618 current_line.truncate(line_prefix.len());
19619 current_line_len = line_prefix_len;
19620 }
19621 current_line.push_str(token);
19622 current_line_len += grapheme_len;
19623 }
19624 WordBreakToken::InlineWhitespace {
19625 mut token,
19626 mut grapheme_len,
19627 } => {
19628 in_whitespace = true;
19629 if have_preceding_whitespace && !preserve_existing_whitespace {
19630 continue;
19631 }
19632 if !preserve_existing_whitespace {
19633 token = " ";
19634 grapheme_len = 1;
19635 }
19636 if current_line_len + grapheme_len > wrap_column {
19637 wrapped_text.push_str(current_line.trim_end());
19638 wrapped_text.push('\n');
19639 current_line.truncate(line_prefix.len());
19640 current_line_len = line_prefix_len;
19641 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19642 current_line.push_str(token);
19643 current_line_len += grapheme_len;
19644 }
19645 }
19646 WordBreakToken::Newline => {
19647 in_whitespace = true;
19648 if preserve_existing_whitespace {
19649 wrapped_text.push_str(current_line.trim_end());
19650 wrapped_text.push('\n');
19651 current_line.truncate(line_prefix.len());
19652 current_line_len = line_prefix_len;
19653 } else if have_preceding_whitespace {
19654 continue;
19655 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19656 {
19657 wrapped_text.push_str(current_line.trim_end());
19658 wrapped_text.push('\n');
19659 current_line.truncate(line_prefix.len());
19660 current_line_len = line_prefix_len;
19661 } else if current_line_len != line_prefix_len {
19662 current_line.push(' ');
19663 current_line_len += 1;
19664 }
19665 }
19666 }
19667 }
19668
19669 if !current_line.is_empty() {
19670 wrapped_text.push_str(¤t_line);
19671 }
19672 wrapped_text
19673}
19674
19675#[test]
19676fn test_wrap_with_prefix() {
19677 assert_eq!(
19678 wrap_with_prefix(
19679 "# ".to_string(),
19680 "abcdefg".to_string(),
19681 4,
19682 NonZeroU32::new(4).unwrap(),
19683 false,
19684 ),
19685 "# abcdefg"
19686 );
19687 assert_eq!(
19688 wrap_with_prefix(
19689 "".to_string(),
19690 "\thello world".to_string(),
19691 8,
19692 NonZeroU32::new(4).unwrap(),
19693 false,
19694 ),
19695 "hello\nworld"
19696 );
19697 assert_eq!(
19698 wrap_with_prefix(
19699 "// ".to_string(),
19700 "xx \nyy zz aa bb cc".to_string(),
19701 12,
19702 NonZeroU32::new(4).unwrap(),
19703 false,
19704 ),
19705 "// xx yy zz\n// aa bb cc"
19706 );
19707 assert_eq!(
19708 wrap_with_prefix(
19709 String::new(),
19710 "这是什么 \n 钢笔".to_string(),
19711 3,
19712 NonZeroU32::new(4).unwrap(),
19713 false,
19714 ),
19715 "这是什\n么 钢\n笔"
19716 );
19717}
19718
19719pub trait CollaborationHub {
19720 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19721 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19722 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19723}
19724
19725impl CollaborationHub for Entity<Project> {
19726 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19727 self.read(cx).collaborators()
19728 }
19729
19730 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19731 self.read(cx).user_store().read(cx).participant_indices()
19732 }
19733
19734 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19735 let this = self.read(cx);
19736 let user_ids = this.collaborators().values().map(|c| c.user_id);
19737 this.user_store().read_with(cx, |user_store, cx| {
19738 user_store.participant_names(user_ids, cx)
19739 })
19740 }
19741}
19742
19743pub trait SemanticsProvider {
19744 fn hover(
19745 &self,
19746 buffer: &Entity<Buffer>,
19747 position: text::Anchor,
19748 cx: &mut App,
19749 ) -> Option<Task<Vec<project::Hover>>>;
19750
19751 fn inline_values(
19752 &self,
19753 buffer_handle: Entity<Buffer>,
19754 range: Range<text::Anchor>,
19755 cx: &mut App,
19756 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19757
19758 fn inlay_hints(
19759 &self,
19760 buffer_handle: Entity<Buffer>,
19761 range: Range<text::Anchor>,
19762 cx: &mut App,
19763 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19764
19765 fn resolve_inlay_hint(
19766 &self,
19767 hint: InlayHint,
19768 buffer_handle: Entity<Buffer>,
19769 server_id: LanguageServerId,
19770 cx: &mut App,
19771 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19772
19773 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19774
19775 fn document_highlights(
19776 &self,
19777 buffer: &Entity<Buffer>,
19778 position: text::Anchor,
19779 cx: &mut App,
19780 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19781
19782 fn definitions(
19783 &self,
19784 buffer: &Entity<Buffer>,
19785 position: text::Anchor,
19786 kind: GotoDefinitionKind,
19787 cx: &mut App,
19788 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19789
19790 fn range_for_rename(
19791 &self,
19792 buffer: &Entity<Buffer>,
19793 position: text::Anchor,
19794 cx: &mut App,
19795 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19796
19797 fn perform_rename(
19798 &self,
19799 buffer: &Entity<Buffer>,
19800 position: text::Anchor,
19801 new_name: String,
19802 cx: &mut App,
19803 ) -> Option<Task<Result<ProjectTransaction>>>;
19804}
19805
19806pub trait CompletionProvider {
19807 fn completions(
19808 &self,
19809 excerpt_id: ExcerptId,
19810 buffer: &Entity<Buffer>,
19811 buffer_position: text::Anchor,
19812 trigger: CompletionContext,
19813 window: &mut Window,
19814 cx: &mut Context<Editor>,
19815 ) -> Task<Result<Option<Vec<Completion>>>>;
19816
19817 fn resolve_completions(
19818 &self,
19819 buffer: Entity<Buffer>,
19820 completion_indices: Vec<usize>,
19821 completions: Rc<RefCell<Box<[Completion]>>>,
19822 cx: &mut Context<Editor>,
19823 ) -> Task<Result<bool>>;
19824
19825 fn apply_additional_edits_for_completion(
19826 &self,
19827 _buffer: Entity<Buffer>,
19828 _completions: Rc<RefCell<Box<[Completion]>>>,
19829 _completion_index: usize,
19830 _push_to_history: bool,
19831 _cx: &mut Context<Editor>,
19832 ) -> Task<Result<Option<language::Transaction>>> {
19833 Task::ready(Ok(None))
19834 }
19835
19836 fn is_completion_trigger(
19837 &self,
19838 buffer: &Entity<Buffer>,
19839 position: language::Anchor,
19840 text: &str,
19841 trigger_in_words: bool,
19842 cx: &mut Context<Editor>,
19843 ) -> bool;
19844
19845 fn sort_completions(&self) -> bool {
19846 true
19847 }
19848
19849 fn filter_completions(&self) -> bool {
19850 true
19851 }
19852}
19853
19854pub trait CodeActionProvider {
19855 fn id(&self) -> Arc<str>;
19856
19857 fn code_actions(
19858 &self,
19859 buffer: &Entity<Buffer>,
19860 range: Range<text::Anchor>,
19861 window: &mut Window,
19862 cx: &mut App,
19863 ) -> Task<Result<Vec<CodeAction>>>;
19864
19865 fn apply_code_action(
19866 &self,
19867 buffer_handle: Entity<Buffer>,
19868 action: CodeAction,
19869 excerpt_id: ExcerptId,
19870 push_to_history: bool,
19871 window: &mut Window,
19872 cx: &mut App,
19873 ) -> Task<Result<ProjectTransaction>>;
19874}
19875
19876impl CodeActionProvider for Entity<Project> {
19877 fn id(&self) -> Arc<str> {
19878 "project".into()
19879 }
19880
19881 fn code_actions(
19882 &self,
19883 buffer: &Entity<Buffer>,
19884 range: Range<text::Anchor>,
19885 _window: &mut Window,
19886 cx: &mut App,
19887 ) -> Task<Result<Vec<CodeAction>>> {
19888 self.update(cx, |project, cx| {
19889 let code_lens = project.code_lens(buffer, range.clone(), cx);
19890 let code_actions = project.code_actions(buffer, range, None, cx);
19891 cx.background_spawn(async move {
19892 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19893 Ok(code_lens
19894 .context("code lens fetch")?
19895 .into_iter()
19896 .chain(code_actions.context("code action fetch")?)
19897 .collect())
19898 })
19899 })
19900 }
19901
19902 fn apply_code_action(
19903 &self,
19904 buffer_handle: Entity<Buffer>,
19905 action: CodeAction,
19906 _excerpt_id: ExcerptId,
19907 push_to_history: bool,
19908 _window: &mut Window,
19909 cx: &mut App,
19910 ) -> Task<Result<ProjectTransaction>> {
19911 self.update(cx, |project, cx| {
19912 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19913 })
19914 }
19915}
19916
19917fn snippet_completions(
19918 project: &Project,
19919 buffer: &Entity<Buffer>,
19920 buffer_position: text::Anchor,
19921 cx: &mut App,
19922) -> Task<Result<Vec<Completion>>> {
19923 let languages = buffer.read(cx).languages_at(buffer_position);
19924 let snippet_store = project.snippets().read(cx);
19925
19926 let scopes: Vec<_> = languages
19927 .iter()
19928 .filter_map(|language| {
19929 let language_name = language.lsp_id();
19930 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19931
19932 if snippets.is_empty() {
19933 None
19934 } else {
19935 Some((language.default_scope(), snippets))
19936 }
19937 })
19938 .collect();
19939
19940 if scopes.is_empty() {
19941 return Task::ready(Ok(vec![]));
19942 }
19943
19944 let snapshot = buffer.read(cx).text_snapshot();
19945 let chars: String = snapshot
19946 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19947 .collect();
19948 let executor = cx.background_executor().clone();
19949
19950 cx.background_spawn(async move {
19951 let mut all_results: Vec<Completion> = Vec::new();
19952 for (scope, snippets) in scopes.into_iter() {
19953 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19954 let mut last_word = chars
19955 .chars()
19956 .take_while(|c| classifier.is_word(*c))
19957 .collect::<String>();
19958 last_word = last_word.chars().rev().collect();
19959
19960 if last_word.is_empty() {
19961 return Ok(vec![]);
19962 }
19963
19964 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19965 let to_lsp = |point: &text::Anchor| {
19966 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19967 point_to_lsp(end)
19968 };
19969 let lsp_end = to_lsp(&buffer_position);
19970
19971 let candidates = snippets
19972 .iter()
19973 .enumerate()
19974 .flat_map(|(ix, snippet)| {
19975 snippet
19976 .prefix
19977 .iter()
19978 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19979 })
19980 .collect::<Vec<StringMatchCandidate>>();
19981
19982 let mut matches = fuzzy::match_strings(
19983 &candidates,
19984 &last_word,
19985 last_word.chars().any(|c| c.is_uppercase()),
19986 100,
19987 &Default::default(),
19988 executor.clone(),
19989 )
19990 .await;
19991
19992 // Remove all candidates where the query's start does not match the start of any word in the candidate
19993 if let Some(query_start) = last_word.chars().next() {
19994 matches.retain(|string_match| {
19995 split_words(&string_match.string).any(|word| {
19996 // Check that the first codepoint of the word as lowercase matches the first
19997 // codepoint of the query as lowercase
19998 word.chars()
19999 .flat_map(|codepoint| codepoint.to_lowercase())
20000 .zip(query_start.to_lowercase())
20001 .all(|(word_cp, query_cp)| word_cp == query_cp)
20002 })
20003 });
20004 }
20005
20006 let matched_strings = matches
20007 .into_iter()
20008 .map(|m| m.string)
20009 .collect::<HashSet<_>>();
20010
20011 let mut result: Vec<Completion> = snippets
20012 .iter()
20013 .filter_map(|snippet| {
20014 let matching_prefix = snippet
20015 .prefix
20016 .iter()
20017 .find(|prefix| matched_strings.contains(*prefix))?;
20018 let start = as_offset - last_word.len();
20019 let start = snapshot.anchor_before(start);
20020 let range = start..buffer_position;
20021 let lsp_start = to_lsp(&start);
20022 let lsp_range = lsp::Range {
20023 start: lsp_start,
20024 end: lsp_end,
20025 };
20026 Some(Completion {
20027 replace_range: range,
20028 new_text: snippet.body.clone(),
20029 source: CompletionSource::Lsp {
20030 insert_range: None,
20031 server_id: LanguageServerId(usize::MAX),
20032 resolved: true,
20033 lsp_completion: Box::new(lsp::CompletionItem {
20034 label: snippet.prefix.first().unwrap().clone(),
20035 kind: Some(CompletionItemKind::SNIPPET),
20036 label_details: snippet.description.as_ref().map(|description| {
20037 lsp::CompletionItemLabelDetails {
20038 detail: Some(description.clone()),
20039 description: None,
20040 }
20041 }),
20042 insert_text_format: Some(InsertTextFormat::SNIPPET),
20043 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20044 lsp::InsertReplaceEdit {
20045 new_text: snippet.body.clone(),
20046 insert: lsp_range,
20047 replace: lsp_range,
20048 },
20049 )),
20050 filter_text: Some(snippet.body.clone()),
20051 sort_text: Some(char::MAX.to_string()),
20052 ..lsp::CompletionItem::default()
20053 }),
20054 lsp_defaults: None,
20055 },
20056 label: CodeLabel {
20057 text: matching_prefix.clone(),
20058 runs: Vec::new(),
20059 filter_range: 0..matching_prefix.len(),
20060 },
20061 icon_path: None,
20062 documentation: Some(
20063 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20064 single_line: snippet.name.clone().into(),
20065 plain_text: snippet
20066 .description
20067 .clone()
20068 .map(|description| description.into()),
20069 },
20070 ),
20071 insert_text_mode: None,
20072 confirm: None,
20073 })
20074 })
20075 .collect();
20076
20077 all_results.append(&mut result);
20078 }
20079
20080 Ok(all_results)
20081 })
20082}
20083
20084impl CompletionProvider for Entity<Project> {
20085 fn completions(
20086 &self,
20087 _excerpt_id: ExcerptId,
20088 buffer: &Entity<Buffer>,
20089 buffer_position: text::Anchor,
20090 options: CompletionContext,
20091 _window: &mut Window,
20092 cx: &mut Context<Editor>,
20093 ) -> Task<Result<Option<Vec<Completion>>>> {
20094 self.update(cx, |project, cx| {
20095 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20096 let project_completions = project.completions(buffer, buffer_position, options, cx);
20097 cx.background_spawn(async move {
20098 let snippets_completions = snippets.await?;
20099 match project_completions.await? {
20100 Some(mut completions) => {
20101 completions.extend(snippets_completions);
20102 Ok(Some(completions))
20103 }
20104 None => {
20105 if snippets_completions.is_empty() {
20106 Ok(None)
20107 } else {
20108 Ok(Some(snippets_completions))
20109 }
20110 }
20111 }
20112 })
20113 })
20114 }
20115
20116 fn resolve_completions(
20117 &self,
20118 buffer: Entity<Buffer>,
20119 completion_indices: Vec<usize>,
20120 completions: Rc<RefCell<Box<[Completion]>>>,
20121 cx: &mut Context<Editor>,
20122 ) -> Task<Result<bool>> {
20123 self.update(cx, |project, cx| {
20124 project.lsp_store().update(cx, |lsp_store, cx| {
20125 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20126 })
20127 })
20128 }
20129
20130 fn apply_additional_edits_for_completion(
20131 &self,
20132 buffer: Entity<Buffer>,
20133 completions: Rc<RefCell<Box<[Completion]>>>,
20134 completion_index: usize,
20135 push_to_history: bool,
20136 cx: &mut Context<Editor>,
20137 ) -> Task<Result<Option<language::Transaction>>> {
20138 self.update(cx, |project, cx| {
20139 project.lsp_store().update(cx, |lsp_store, cx| {
20140 lsp_store.apply_additional_edits_for_completion(
20141 buffer,
20142 completions,
20143 completion_index,
20144 push_to_history,
20145 cx,
20146 )
20147 })
20148 })
20149 }
20150
20151 fn is_completion_trigger(
20152 &self,
20153 buffer: &Entity<Buffer>,
20154 position: language::Anchor,
20155 text: &str,
20156 trigger_in_words: bool,
20157 cx: &mut Context<Editor>,
20158 ) -> bool {
20159 let mut chars = text.chars();
20160 let char = if let Some(char) = chars.next() {
20161 char
20162 } else {
20163 return false;
20164 };
20165 if chars.next().is_some() {
20166 return false;
20167 }
20168
20169 let buffer = buffer.read(cx);
20170 let snapshot = buffer.snapshot();
20171 if !snapshot.settings_at(position, cx).show_completions_on_input {
20172 return false;
20173 }
20174 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20175 if trigger_in_words && classifier.is_word(char) {
20176 return true;
20177 }
20178
20179 buffer.completion_triggers().contains(text)
20180 }
20181}
20182
20183impl SemanticsProvider for Entity<Project> {
20184 fn hover(
20185 &self,
20186 buffer: &Entity<Buffer>,
20187 position: text::Anchor,
20188 cx: &mut App,
20189 ) -> Option<Task<Vec<project::Hover>>> {
20190 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20191 }
20192
20193 fn document_highlights(
20194 &self,
20195 buffer: &Entity<Buffer>,
20196 position: text::Anchor,
20197 cx: &mut App,
20198 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20199 Some(self.update(cx, |project, cx| {
20200 project.document_highlights(buffer, position, cx)
20201 }))
20202 }
20203
20204 fn definitions(
20205 &self,
20206 buffer: &Entity<Buffer>,
20207 position: text::Anchor,
20208 kind: GotoDefinitionKind,
20209 cx: &mut App,
20210 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20211 Some(self.update(cx, |project, cx| match kind {
20212 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20213 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20214 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20215 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20216 }))
20217 }
20218
20219 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20220 // TODO: make this work for remote projects
20221 self.update(cx, |project, cx| {
20222 if project
20223 .active_debug_session(cx)
20224 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20225 {
20226 return true;
20227 }
20228
20229 buffer.update(cx, |buffer, cx| {
20230 project.any_language_server_supports_inlay_hints(buffer, cx)
20231 })
20232 })
20233 }
20234
20235 fn inline_values(
20236 &self,
20237 buffer_handle: Entity<Buffer>,
20238 range: Range<text::Anchor>,
20239 cx: &mut App,
20240 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20241 self.update(cx, |project, cx| {
20242 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20243
20244 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20245 })
20246 }
20247
20248 fn inlay_hints(
20249 &self,
20250 buffer_handle: Entity<Buffer>,
20251 range: Range<text::Anchor>,
20252 cx: &mut App,
20253 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20254 Some(self.update(cx, |project, cx| {
20255 project.inlay_hints(buffer_handle, range, cx)
20256 }))
20257 }
20258
20259 fn resolve_inlay_hint(
20260 &self,
20261 hint: InlayHint,
20262 buffer_handle: Entity<Buffer>,
20263 server_id: LanguageServerId,
20264 cx: &mut App,
20265 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20266 Some(self.update(cx, |project, cx| {
20267 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20268 }))
20269 }
20270
20271 fn range_for_rename(
20272 &self,
20273 buffer: &Entity<Buffer>,
20274 position: text::Anchor,
20275 cx: &mut App,
20276 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20277 Some(self.update(cx, |project, cx| {
20278 let buffer = buffer.clone();
20279 let task = project.prepare_rename(buffer.clone(), position, cx);
20280 cx.spawn(async move |_, cx| {
20281 Ok(match task.await? {
20282 PrepareRenameResponse::Success(range) => Some(range),
20283 PrepareRenameResponse::InvalidPosition => None,
20284 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20285 // Fallback on using TreeSitter info to determine identifier range
20286 buffer.update(cx, |buffer, _| {
20287 let snapshot = buffer.snapshot();
20288 let (range, kind) = snapshot.surrounding_word(position);
20289 if kind != Some(CharKind::Word) {
20290 return None;
20291 }
20292 Some(
20293 snapshot.anchor_before(range.start)
20294 ..snapshot.anchor_after(range.end),
20295 )
20296 })?
20297 }
20298 })
20299 })
20300 }))
20301 }
20302
20303 fn perform_rename(
20304 &self,
20305 buffer: &Entity<Buffer>,
20306 position: text::Anchor,
20307 new_name: String,
20308 cx: &mut App,
20309 ) -> Option<Task<Result<ProjectTransaction>>> {
20310 Some(self.update(cx, |project, cx| {
20311 project.perform_rename(buffer.clone(), position, new_name, cx)
20312 }))
20313 }
20314}
20315
20316fn inlay_hint_settings(
20317 location: Anchor,
20318 snapshot: &MultiBufferSnapshot,
20319 cx: &mut Context<Editor>,
20320) -> InlayHintSettings {
20321 let file = snapshot.file_at(location);
20322 let language = snapshot.language_at(location).map(|l| l.name());
20323 language_settings(language, file, cx).inlay_hints
20324}
20325
20326fn consume_contiguous_rows(
20327 contiguous_row_selections: &mut Vec<Selection<Point>>,
20328 selection: &Selection<Point>,
20329 display_map: &DisplaySnapshot,
20330 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20331) -> (MultiBufferRow, MultiBufferRow) {
20332 contiguous_row_selections.push(selection.clone());
20333 let start_row = MultiBufferRow(selection.start.row);
20334 let mut end_row = ending_row(selection, display_map);
20335
20336 while let Some(next_selection) = selections.peek() {
20337 if next_selection.start.row <= end_row.0 {
20338 end_row = ending_row(next_selection, display_map);
20339 contiguous_row_selections.push(selections.next().unwrap().clone());
20340 } else {
20341 break;
20342 }
20343 }
20344 (start_row, end_row)
20345}
20346
20347fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20348 if next_selection.end.column > 0 || next_selection.is_empty() {
20349 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20350 } else {
20351 MultiBufferRow(next_selection.end.row)
20352 }
20353}
20354
20355impl EditorSnapshot {
20356 pub fn remote_selections_in_range<'a>(
20357 &'a self,
20358 range: &'a Range<Anchor>,
20359 collaboration_hub: &dyn CollaborationHub,
20360 cx: &'a App,
20361 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20362 let participant_names = collaboration_hub.user_names(cx);
20363 let participant_indices = collaboration_hub.user_participant_indices(cx);
20364 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20365 let collaborators_by_replica_id = collaborators_by_peer_id
20366 .values()
20367 .map(|collaborator| (collaborator.replica_id, collaborator))
20368 .collect::<HashMap<_, _>>();
20369 self.buffer_snapshot
20370 .selections_in_range(range, false)
20371 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20372 if replica_id == AGENT_REPLICA_ID {
20373 Some(RemoteSelection {
20374 replica_id,
20375 selection,
20376 cursor_shape,
20377 line_mode,
20378 collaborator_id: CollaboratorId::Agent,
20379 user_name: Some("Agent".into()),
20380 color: cx.theme().players().agent(),
20381 })
20382 } else {
20383 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20384 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20385 let user_name = participant_names.get(&collaborator.user_id).cloned();
20386 Some(RemoteSelection {
20387 replica_id,
20388 selection,
20389 cursor_shape,
20390 line_mode,
20391 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20392 user_name,
20393 color: if let Some(index) = participant_index {
20394 cx.theme().players().color_for_participant(index.0)
20395 } else {
20396 cx.theme().players().absent()
20397 },
20398 })
20399 }
20400 })
20401 }
20402
20403 pub fn hunks_for_ranges(
20404 &self,
20405 ranges: impl IntoIterator<Item = Range<Point>>,
20406 ) -> Vec<MultiBufferDiffHunk> {
20407 let mut hunks = Vec::new();
20408 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20409 HashMap::default();
20410 for query_range in ranges {
20411 let query_rows =
20412 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20413 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20414 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20415 ) {
20416 // Include deleted hunks that are adjacent to the query range, because
20417 // otherwise they would be missed.
20418 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20419 if hunk.status().is_deleted() {
20420 intersects_range |= hunk.row_range.start == query_rows.end;
20421 intersects_range |= hunk.row_range.end == query_rows.start;
20422 }
20423 if intersects_range {
20424 if !processed_buffer_rows
20425 .entry(hunk.buffer_id)
20426 .or_default()
20427 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20428 {
20429 continue;
20430 }
20431 hunks.push(hunk);
20432 }
20433 }
20434 }
20435
20436 hunks
20437 }
20438
20439 fn display_diff_hunks_for_rows<'a>(
20440 &'a self,
20441 display_rows: Range<DisplayRow>,
20442 folded_buffers: &'a HashSet<BufferId>,
20443 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20444 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20445 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20446
20447 self.buffer_snapshot
20448 .diff_hunks_in_range(buffer_start..buffer_end)
20449 .filter_map(|hunk| {
20450 if folded_buffers.contains(&hunk.buffer_id) {
20451 return None;
20452 }
20453
20454 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20455 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20456
20457 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20458 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20459
20460 let display_hunk = if hunk_display_start.column() != 0 {
20461 DisplayDiffHunk::Folded {
20462 display_row: hunk_display_start.row(),
20463 }
20464 } else {
20465 let mut end_row = hunk_display_end.row();
20466 if hunk_display_end.column() > 0 {
20467 end_row.0 += 1;
20468 }
20469 let is_created_file = hunk.is_created_file();
20470 DisplayDiffHunk::Unfolded {
20471 status: hunk.status(),
20472 diff_base_byte_range: hunk.diff_base_byte_range,
20473 display_row_range: hunk_display_start.row()..end_row,
20474 multi_buffer_range: Anchor::range_in_buffer(
20475 hunk.excerpt_id,
20476 hunk.buffer_id,
20477 hunk.buffer_range,
20478 ),
20479 is_created_file,
20480 }
20481 };
20482
20483 Some(display_hunk)
20484 })
20485 }
20486
20487 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20488 self.display_snapshot.buffer_snapshot.language_at(position)
20489 }
20490
20491 pub fn is_focused(&self) -> bool {
20492 self.is_focused
20493 }
20494
20495 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20496 self.placeholder_text.as_ref()
20497 }
20498
20499 pub fn scroll_position(&self) -> gpui::Point<f32> {
20500 self.scroll_anchor.scroll_position(&self.display_snapshot)
20501 }
20502
20503 fn gutter_dimensions(
20504 &self,
20505 font_id: FontId,
20506 font_size: Pixels,
20507 max_line_number_width: Pixels,
20508 cx: &App,
20509 ) -> Option<GutterDimensions> {
20510 if !self.show_gutter {
20511 return None;
20512 }
20513
20514 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20515 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20516
20517 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20518 matches!(
20519 ProjectSettings::get_global(cx).git.git_gutter,
20520 Some(GitGutterSetting::TrackedFiles)
20521 )
20522 });
20523 let gutter_settings = EditorSettings::get_global(cx).gutter;
20524 let show_line_numbers = self
20525 .show_line_numbers
20526 .unwrap_or(gutter_settings.line_numbers);
20527 let line_gutter_width = if show_line_numbers {
20528 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20529 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20530 max_line_number_width.max(min_width_for_number_on_gutter)
20531 } else {
20532 0.0.into()
20533 };
20534
20535 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20536 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20537
20538 let git_blame_entries_width =
20539 self.git_blame_gutter_max_author_length
20540 .map(|max_author_length| {
20541 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20542 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20543
20544 /// The number of characters to dedicate to gaps and margins.
20545 const SPACING_WIDTH: usize = 4;
20546
20547 let max_char_count = max_author_length.min(renderer.max_author_length())
20548 + ::git::SHORT_SHA_LENGTH
20549 + MAX_RELATIVE_TIMESTAMP.len()
20550 + SPACING_WIDTH;
20551
20552 em_advance * max_char_count
20553 });
20554
20555 let is_singleton = self.buffer_snapshot.is_singleton();
20556
20557 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20558 left_padding += if !is_singleton {
20559 em_width * 4.0
20560 } else if show_runnables || show_breakpoints {
20561 em_width * 3.0
20562 } else if show_git_gutter && show_line_numbers {
20563 em_width * 2.0
20564 } else if show_git_gutter || show_line_numbers {
20565 em_width
20566 } else {
20567 px(0.)
20568 };
20569
20570 let shows_folds = is_singleton && gutter_settings.folds;
20571
20572 let right_padding = if shows_folds && show_line_numbers {
20573 em_width * 4.0
20574 } else if shows_folds || (!is_singleton && show_line_numbers) {
20575 em_width * 3.0
20576 } else if show_line_numbers {
20577 em_width
20578 } else {
20579 px(0.)
20580 };
20581
20582 Some(GutterDimensions {
20583 left_padding,
20584 right_padding,
20585 width: line_gutter_width + left_padding + right_padding,
20586 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20587 git_blame_entries_width,
20588 })
20589 }
20590
20591 pub fn render_crease_toggle(
20592 &self,
20593 buffer_row: MultiBufferRow,
20594 row_contains_cursor: bool,
20595 editor: Entity<Editor>,
20596 window: &mut Window,
20597 cx: &mut App,
20598 ) -> Option<AnyElement> {
20599 let folded = self.is_line_folded(buffer_row);
20600 let mut is_foldable = false;
20601
20602 if let Some(crease) = self
20603 .crease_snapshot
20604 .query_row(buffer_row, &self.buffer_snapshot)
20605 {
20606 is_foldable = true;
20607 match crease {
20608 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20609 if let Some(render_toggle) = render_toggle {
20610 let toggle_callback =
20611 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20612 if folded {
20613 editor.update(cx, |editor, cx| {
20614 editor.fold_at(buffer_row, window, cx)
20615 });
20616 } else {
20617 editor.update(cx, |editor, cx| {
20618 editor.unfold_at(buffer_row, window, cx)
20619 });
20620 }
20621 });
20622 return Some((render_toggle)(
20623 buffer_row,
20624 folded,
20625 toggle_callback,
20626 window,
20627 cx,
20628 ));
20629 }
20630 }
20631 }
20632 }
20633
20634 is_foldable |= self.starts_indent(buffer_row);
20635
20636 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20637 Some(
20638 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20639 .toggle_state(folded)
20640 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20641 if folded {
20642 this.unfold_at(buffer_row, window, cx);
20643 } else {
20644 this.fold_at(buffer_row, window, cx);
20645 }
20646 }))
20647 .into_any_element(),
20648 )
20649 } else {
20650 None
20651 }
20652 }
20653
20654 pub fn render_crease_trailer(
20655 &self,
20656 buffer_row: MultiBufferRow,
20657 window: &mut Window,
20658 cx: &mut App,
20659 ) -> Option<AnyElement> {
20660 let folded = self.is_line_folded(buffer_row);
20661 if let Crease::Inline { render_trailer, .. } = self
20662 .crease_snapshot
20663 .query_row(buffer_row, &self.buffer_snapshot)?
20664 {
20665 let render_trailer = render_trailer.as_ref()?;
20666 Some(render_trailer(buffer_row, folded, window, cx))
20667 } else {
20668 None
20669 }
20670 }
20671}
20672
20673impl Deref for EditorSnapshot {
20674 type Target = DisplaySnapshot;
20675
20676 fn deref(&self) -> &Self::Target {
20677 &self.display_snapshot
20678 }
20679}
20680
20681#[derive(Clone, Debug, PartialEq, Eq)]
20682pub enum EditorEvent {
20683 InputIgnored {
20684 text: Arc<str>,
20685 },
20686 InputHandled {
20687 utf16_range_to_replace: Option<Range<isize>>,
20688 text: Arc<str>,
20689 },
20690 ExcerptsAdded {
20691 buffer: Entity<Buffer>,
20692 predecessor: ExcerptId,
20693 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20694 },
20695 ExcerptsRemoved {
20696 ids: Vec<ExcerptId>,
20697 removed_buffer_ids: Vec<BufferId>,
20698 },
20699 BufferFoldToggled {
20700 ids: Vec<ExcerptId>,
20701 folded: bool,
20702 },
20703 ExcerptsEdited {
20704 ids: Vec<ExcerptId>,
20705 },
20706 ExcerptsExpanded {
20707 ids: Vec<ExcerptId>,
20708 },
20709 BufferEdited,
20710 Edited {
20711 transaction_id: clock::Lamport,
20712 },
20713 Reparsed(BufferId),
20714 Focused,
20715 FocusedIn,
20716 Blurred,
20717 DirtyChanged,
20718 Saved,
20719 TitleChanged,
20720 DiffBaseChanged,
20721 SelectionsChanged {
20722 local: bool,
20723 },
20724 ScrollPositionChanged {
20725 local: bool,
20726 autoscroll: bool,
20727 },
20728 Closed,
20729 TransactionUndone {
20730 transaction_id: clock::Lamport,
20731 },
20732 TransactionBegun {
20733 transaction_id: clock::Lamport,
20734 },
20735 Reloaded,
20736 CursorShapeChanged,
20737 PushedToNavHistory {
20738 anchor: Anchor,
20739 is_deactivate: bool,
20740 },
20741}
20742
20743impl EventEmitter<EditorEvent> for Editor {}
20744
20745impl Focusable for Editor {
20746 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20747 self.focus_handle.clone()
20748 }
20749}
20750
20751impl Render for Editor {
20752 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20753 let settings = ThemeSettings::get_global(cx);
20754
20755 let mut text_style = match self.mode {
20756 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20757 color: cx.theme().colors().editor_foreground,
20758 font_family: settings.ui_font.family.clone(),
20759 font_features: settings.ui_font.features.clone(),
20760 font_fallbacks: settings.ui_font.fallbacks.clone(),
20761 font_size: rems(0.875).into(),
20762 font_weight: settings.ui_font.weight,
20763 line_height: relative(settings.buffer_line_height.value()),
20764 ..Default::default()
20765 },
20766 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20767 color: cx.theme().colors().editor_foreground,
20768 font_family: settings.buffer_font.family.clone(),
20769 font_features: settings.buffer_font.features.clone(),
20770 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20771 font_size: settings.buffer_font_size(cx).into(),
20772 font_weight: settings.buffer_font.weight,
20773 line_height: relative(settings.buffer_line_height.value()),
20774 ..Default::default()
20775 },
20776 };
20777 if let Some(text_style_refinement) = &self.text_style_refinement {
20778 text_style.refine(text_style_refinement)
20779 }
20780
20781 let background = match self.mode {
20782 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20783 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20784 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20785 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20786 };
20787
20788 EditorElement::new(
20789 &cx.entity(),
20790 EditorStyle {
20791 background,
20792 local_player: cx.theme().players().local(),
20793 text: text_style,
20794 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20795 syntax: cx.theme().syntax().clone(),
20796 status: cx.theme().status().clone(),
20797 inlay_hints_style: make_inlay_hints_style(cx),
20798 inline_completion_styles: make_suggestion_styles(cx),
20799 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20800 show_underlines: !self.mode.is_minimap(),
20801 },
20802 )
20803 }
20804}
20805
20806impl EntityInputHandler for Editor {
20807 fn text_for_range(
20808 &mut self,
20809 range_utf16: Range<usize>,
20810 adjusted_range: &mut Option<Range<usize>>,
20811 _: &mut Window,
20812 cx: &mut Context<Self>,
20813 ) -> Option<String> {
20814 let snapshot = self.buffer.read(cx).read(cx);
20815 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20816 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20817 if (start.0..end.0) != range_utf16 {
20818 adjusted_range.replace(start.0..end.0);
20819 }
20820 Some(snapshot.text_for_range(start..end).collect())
20821 }
20822
20823 fn selected_text_range(
20824 &mut self,
20825 ignore_disabled_input: bool,
20826 _: &mut Window,
20827 cx: &mut Context<Self>,
20828 ) -> Option<UTF16Selection> {
20829 // Prevent the IME menu from appearing when holding down an alphabetic key
20830 // while input is disabled.
20831 if !ignore_disabled_input && !self.input_enabled {
20832 return None;
20833 }
20834
20835 let selection = self.selections.newest::<OffsetUtf16>(cx);
20836 let range = selection.range();
20837
20838 Some(UTF16Selection {
20839 range: range.start.0..range.end.0,
20840 reversed: selection.reversed,
20841 })
20842 }
20843
20844 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20845 let snapshot = self.buffer.read(cx).read(cx);
20846 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20847 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20848 }
20849
20850 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20851 self.clear_highlights::<InputComposition>(cx);
20852 self.ime_transaction.take();
20853 }
20854
20855 fn replace_text_in_range(
20856 &mut self,
20857 range_utf16: Option<Range<usize>>,
20858 text: &str,
20859 window: &mut Window,
20860 cx: &mut Context<Self>,
20861 ) {
20862 if !self.input_enabled {
20863 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20864 return;
20865 }
20866
20867 self.transact(window, cx, |this, window, cx| {
20868 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20869 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20870 Some(this.selection_replacement_ranges(range_utf16, cx))
20871 } else {
20872 this.marked_text_ranges(cx)
20873 };
20874
20875 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20876 let newest_selection_id = this.selections.newest_anchor().id;
20877 this.selections
20878 .all::<OffsetUtf16>(cx)
20879 .iter()
20880 .zip(ranges_to_replace.iter())
20881 .find_map(|(selection, range)| {
20882 if selection.id == newest_selection_id {
20883 Some(
20884 (range.start.0 as isize - selection.head().0 as isize)
20885 ..(range.end.0 as isize - selection.head().0 as isize),
20886 )
20887 } else {
20888 None
20889 }
20890 })
20891 });
20892
20893 cx.emit(EditorEvent::InputHandled {
20894 utf16_range_to_replace: range_to_replace,
20895 text: text.into(),
20896 });
20897
20898 if let Some(new_selected_ranges) = new_selected_ranges {
20899 this.change_selections(None, window, cx, |selections| {
20900 selections.select_ranges(new_selected_ranges)
20901 });
20902 this.backspace(&Default::default(), window, cx);
20903 }
20904
20905 this.handle_input(text, window, cx);
20906 });
20907
20908 if let Some(transaction) = self.ime_transaction {
20909 self.buffer.update(cx, |buffer, cx| {
20910 buffer.group_until_transaction(transaction, cx);
20911 });
20912 }
20913
20914 self.unmark_text(window, cx);
20915 }
20916
20917 fn replace_and_mark_text_in_range(
20918 &mut self,
20919 range_utf16: Option<Range<usize>>,
20920 text: &str,
20921 new_selected_range_utf16: Option<Range<usize>>,
20922 window: &mut Window,
20923 cx: &mut Context<Self>,
20924 ) {
20925 if !self.input_enabled {
20926 return;
20927 }
20928
20929 let transaction = self.transact(window, cx, |this, window, cx| {
20930 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20931 let snapshot = this.buffer.read(cx).read(cx);
20932 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20933 for marked_range in &mut marked_ranges {
20934 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20935 marked_range.start.0 += relative_range_utf16.start;
20936 marked_range.start =
20937 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20938 marked_range.end =
20939 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20940 }
20941 }
20942 Some(marked_ranges)
20943 } else if let Some(range_utf16) = range_utf16 {
20944 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20945 Some(this.selection_replacement_ranges(range_utf16, cx))
20946 } else {
20947 None
20948 };
20949
20950 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20951 let newest_selection_id = this.selections.newest_anchor().id;
20952 this.selections
20953 .all::<OffsetUtf16>(cx)
20954 .iter()
20955 .zip(ranges_to_replace.iter())
20956 .find_map(|(selection, range)| {
20957 if selection.id == newest_selection_id {
20958 Some(
20959 (range.start.0 as isize - selection.head().0 as isize)
20960 ..(range.end.0 as isize - selection.head().0 as isize),
20961 )
20962 } else {
20963 None
20964 }
20965 })
20966 });
20967
20968 cx.emit(EditorEvent::InputHandled {
20969 utf16_range_to_replace: range_to_replace,
20970 text: text.into(),
20971 });
20972
20973 if let Some(ranges) = ranges_to_replace {
20974 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20975 }
20976
20977 let marked_ranges = {
20978 let snapshot = this.buffer.read(cx).read(cx);
20979 this.selections
20980 .disjoint_anchors()
20981 .iter()
20982 .map(|selection| {
20983 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20984 })
20985 .collect::<Vec<_>>()
20986 };
20987
20988 if text.is_empty() {
20989 this.unmark_text(window, cx);
20990 } else {
20991 this.highlight_text::<InputComposition>(
20992 marked_ranges.clone(),
20993 HighlightStyle {
20994 underline: Some(UnderlineStyle {
20995 thickness: px(1.),
20996 color: None,
20997 wavy: false,
20998 }),
20999 ..Default::default()
21000 },
21001 cx,
21002 );
21003 }
21004
21005 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21006 let use_autoclose = this.use_autoclose;
21007 let use_auto_surround = this.use_auto_surround;
21008 this.set_use_autoclose(false);
21009 this.set_use_auto_surround(false);
21010 this.handle_input(text, window, cx);
21011 this.set_use_autoclose(use_autoclose);
21012 this.set_use_auto_surround(use_auto_surround);
21013
21014 if let Some(new_selected_range) = new_selected_range_utf16 {
21015 let snapshot = this.buffer.read(cx).read(cx);
21016 let new_selected_ranges = marked_ranges
21017 .into_iter()
21018 .map(|marked_range| {
21019 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21020 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21021 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21022 snapshot.clip_offset_utf16(new_start, Bias::Left)
21023 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21024 })
21025 .collect::<Vec<_>>();
21026
21027 drop(snapshot);
21028 this.change_selections(None, window, cx, |selections| {
21029 selections.select_ranges(new_selected_ranges)
21030 });
21031 }
21032 });
21033
21034 self.ime_transaction = self.ime_transaction.or(transaction);
21035 if let Some(transaction) = self.ime_transaction {
21036 self.buffer.update(cx, |buffer, cx| {
21037 buffer.group_until_transaction(transaction, cx);
21038 });
21039 }
21040
21041 if self.text_highlights::<InputComposition>(cx).is_none() {
21042 self.ime_transaction.take();
21043 }
21044 }
21045
21046 fn bounds_for_range(
21047 &mut self,
21048 range_utf16: Range<usize>,
21049 element_bounds: gpui::Bounds<Pixels>,
21050 window: &mut Window,
21051 cx: &mut Context<Self>,
21052 ) -> Option<gpui::Bounds<Pixels>> {
21053 let text_layout_details = self.text_layout_details(window);
21054 let gpui::Size {
21055 width: em_width,
21056 height: line_height,
21057 } = self.character_size(window);
21058
21059 let snapshot = self.snapshot(window, cx);
21060 let scroll_position = snapshot.scroll_position();
21061 let scroll_left = scroll_position.x * em_width;
21062
21063 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21064 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21065 + self.gutter_dimensions.width
21066 + self.gutter_dimensions.margin;
21067 let y = line_height * (start.row().as_f32() - scroll_position.y);
21068
21069 Some(Bounds {
21070 origin: element_bounds.origin + point(x, y),
21071 size: size(em_width, line_height),
21072 })
21073 }
21074
21075 fn character_index_for_point(
21076 &mut self,
21077 point: gpui::Point<Pixels>,
21078 _window: &mut Window,
21079 _cx: &mut Context<Self>,
21080 ) -> Option<usize> {
21081 let position_map = self.last_position_map.as_ref()?;
21082 if !position_map.text_hitbox.contains(&point) {
21083 return None;
21084 }
21085 let display_point = position_map.point_for_position(point).previous_valid;
21086 let anchor = position_map
21087 .snapshot
21088 .display_point_to_anchor(display_point, Bias::Left);
21089 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21090 Some(utf16_offset.0)
21091 }
21092}
21093
21094trait SelectionExt {
21095 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21096 fn spanned_rows(
21097 &self,
21098 include_end_if_at_line_start: bool,
21099 map: &DisplaySnapshot,
21100 ) -> Range<MultiBufferRow>;
21101}
21102
21103impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21104 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21105 let start = self
21106 .start
21107 .to_point(&map.buffer_snapshot)
21108 .to_display_point(map);
21109 let end = self
21110 .end
21111 .to_point(&map.buffer_snapshot)
21112 .to_display_point(map);
21113 if self.reversed {
21114 end..start
21115 } else {
21116 start..end
21117 }
21118 }
21119
21120 fn spanned_rows(
21121 &self,
21122 include_end_if_at_line_start: bool,
21123 map: &DisplaySnapshot,
21124 ) -> Range<MultiBufferRow> {
21125 let start = self.start.to_point(&map.buffer_snapshot);
21126 let mut end = self.end.to_point(&map.buffer_snapshot);
21127 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21128 end.row -= 1;
21129 }
21130
21131 let buffer_start = map.prev_line_boundary(start).0;
21132 let buffer_end = map.next_line_boundary(end).0;
21133 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21134 }
21135}
21136
21137impl<T: InvalidationRegion> InvalidationStack<T> {
21138 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21139 where
21140 S: Clone + ToOffset,
21141 {
21142 while let Some(region) = self.last() {
21143 let all_selections_inside_invalidation_ranges =
21144 if selections.len() == region.ranges().len() {
21145 selections
21146 .iter()
21147 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21148 .all(|(selection, invalidation_range)| {
21149 let head = selection.head().to_offset(buffer);
21150 invalidation_range.start <= head && invalidation_range.end >= head
21151 })
21152 } else {
21153 false
21154 };
21155
21156 if all_selections_inside_invalidation_ranges {
21157 break;
21158 } else {
21159 self.pop();
21160 }
21161 }
21162 }
21163}
21164
21165impl<T> Default for InvalidationStack<T> {
21166 fn default() -> Self {
21167 Self(Default::default())
21168 }
21169}
21170
21171impl<T> Deref for InvalidationStack<T> {
21172 type Target = Vec<T>;
21173
21174 fn deref(&self) -> &Self::Target {
21175 &self.0
21176 }
21177}
21178
21179impl<T> DerefMut for InvalidationStack<T> {
21180 fn deref_mut(&mut self) -> &mut Self::Target {
21181 &mut self.0
21182 }
21183}
21184
21185impl InvalidationRegion for SnippetState {
21186 fn ranges(&self) -> &[Range<Anchor>] {
21187 &self.ranges[self.active_index]
21188 }
21189}
21190
21191fn inline_completion_edit_text(
21192 current_snapshot: &BufferSnapshot,
21193 edits: &[(Range<Anchor>, String)],
21194 edit_preview: &EditPreview,
21195 include_deletions: bool,
21196 cx: &App,
21197) -> HighlightedText {
21198 let edits = edits
21199 .iter()
21200 .map(|(anchor, text)| {
21201 (
21202 anchor.start.text_anchor..anchor.end.text_anchor,
21203 text.clone(),
21204 )
21205 })
21206 .collect::<Vec<_>>();
21207
21208 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21209}
21210
21211pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21212 match severity {
21213 lsp::DiagnosticSeverity::ERROR => colors.error,
21214 lsp::DiagnosticSeverity::WARNING => colors.warning,
21215 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21216 lsp::DiagnosticSeverity::HINT => colors.info,
21217 _ => colors.ignored,
21218 }
21219}
21220
21221pub fn styled_runs_for_code_label<'a>(
21222 label: &'a CodeLabel,
21223 syntax_theme: &'a theme::SyntaxTheme,
21224) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21225 let fade_out = HighlightStyle {
21226 fade_out: Some(0.35),
21227 ..Default::default()
21228 };
21229
21230 let mut prev_end = label.filter_range.end;
21231 label
21232 .runs
21233 .iter()
21234 .enumerate()
21235 .flat_map(move |(ix, (range, highlight_id))| {
21236 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21237 style
21238 } else {
21239 return Default::default();
21240 };
21241 let mut muted_style = style;
21242 muted_style.highlight(fade_out);
21243
21244 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21245 if range.start >= label.filter_range.end {
21246 if range.start > prev_end {
21247 runs.push((prev_end..range.start, fade_out));
21248 }
21249 runs.push((range.clone(), muted_style));
21250 } else if range.end <= label.filter_range.end {
21251 runs.push((range.clone(), style));
21252 } else {
21253 runs.push((range.start..label.filter_range.end, style));
21254 runs.push((label.filter_range.end..range.end, muted_style));
21255 }
21256 prev_end = cmp::max(prev_end, range.end);
21257
21258 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21259 runs.push((prev_end..label.text.len(), fade_out));
21260 }
21261
21262 runs
21263 })
21264}
21265
21266pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21267 let mut prev_index = 0;
21268 let mut prev_codepoint: Option<char> = None;
21269 text.char_indices()
21270 .chain([(text.len(), '\0')])
21271 .filter_map(move |(index, codepoint)| {
21272 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21273 let is_boundary = index == text.len()
21274 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21275 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21276 if is_boundary {
21277 let chunk = &text[prev_index..index];
21278 prev_index = index;
21279 Some(chunk)
21280 } else {
21281 None
21282 }
21283 })
21284}
21285
21286pub trait RangeToAnchorExt: Sized {
21287 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21288
21289 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21290 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21291 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21292 }
21293}
21294
21295impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21296 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21297 let start_offset = self.start.to_offset(snapshot);
21298 let end_offset = self.end.to_offset(snapshot);
21299 if start_offset == end_offset {
21300 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21301 } else {
21302 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21303 }
21304 }
21305}
21306
21307pub trait RowExt {
21308 fn as_f32(&self) -> f32;
21309
21310 fn next_row(&self) -> Self;
21311
21312 fn previous_row(&self) -> Self;
21313
21314 fn minus(&self, other: Self) -> u32;
21315}
21316
21317impl RowExt for DisplayRow {
21318 fn as_f32(&self) -> f32 {
21319 self.0 as f32
21320 }
21321
21322 fn next_row(&self) -> Self {
21323 Self(self.0 + 1)
21324 }
21325
21326 fn previous_row(&self) -> Self {
21327 Self(self.0.saturating_sub(1))
21328 }
21329
21330 fn minus(&self, other: Self) -> u32 {
21331 self.0 - other.0
21332 }
21333}
21334
21335impl RowExt for MultiBufferRow {
21336 fn as_f32(&self) -> f32 {
21337 self.0 as f32
21338 }
21339
21340 fn next_row(&self) -> Self {
21341 Self(self.0 + 1)
21342 }
21343
21344 fn previous_row(&self) -> Self {
21345 Self(self.0.saturating_sub(1))
21346 }
21347
21348 fn minus(&self, other: Self) -> u32 {
21349 self.0 - other.0
21350 }
21351}
21352
21353trait RowRangeExt {
21354 type Row;
21355
21356 fn len(&self) -> usize;
21357
21358 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21359}
21360
21361impl RowRangeExt for Range<MultiBufferRow> {
21362 type Row = MultiBufferRow;
21363
21364 fn len(&self) -> usize {
21365 (self.end.0 - self.start.0) as usize
21366 }
21367
21368 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21369 (self.start.0..self.end.0).map(MultiBufferRow)
21370 }
21371}
21372
21373impl RowRangeExt for Range<DisplayRow> {
21374 type Row = DisplayRow;
21375
21376 fn len(&self) -> usize {
21377 (self.end.0 - self.start.0) as usize
21378 }
21379
21380 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21381 (self.start.0..self.end.0).map(DisplayRow)
21382 }
21383}
21384
21385/// If select range has more than one line, we
21386/// just point the cursor to range.start.
21387fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21388 if range.start.row == range.end.row {
21389 range
21390 } else {
21391 range.start..range.start
21392 }
21393}
21394pub struct KillRing(ClipboardItem);
21395impl Global for KillRing {}
21396
21397const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21398
21399enum BreakpointPromptEditAction {
21400 Log,
21401 Condition,
21402 HitCondition,
21403}
21404
21405struct BreakpointPromptEditor {
21406 pub(crate) prompt: Entity<Editor>,
21407 editor: WeakEntity<Editor>,
21408 breakpoint_anchor: Anchor,
21409 breakpoint: Breakpoint,
21410 edit_action: BreakpointPromptEditAction,
21411 block_ids: HashSet<CustomBlockId>,
21412 editor_margins: Arc<Mutex<EditorMargins>>,
21413 _subscriptions: Vec<Subscription>,
21414}
21415
21416impl BreakpointPromptEditor {
21417 const MAX_LINES: u8 = 4;
21418
21419 fn new(
21420 editor: WeakEntity<Editor>,
21421 breakpoint_anchor: Anchor,
21422 breakpoint: Breakpoint,
21423 edit_action: BreakpointPromptEditAction,
21424 window: &mut Window,
21425 cx: &mut Context<Self>,
21426 ) -> Self {
21427 let base_text = match edit_action {
21428 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21429 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21430 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21431 }
21432 .map(|msg| msg.to_string())
21433 .unwrap_or_default();
21434
21435 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21436 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21437
21438 let prompt = cx.new(|cx| {
21439 let mut prompt = Editor::new(
21440 EditorMode::AutoHeight {
21441 max_lines: Self::MAX_LINES as usize,
21442 },
21443 buffer,
21444 None,
21445 window,
21446 cx,
21447 );
21448 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21449 prompt.set_show_cursor_when_unfocused(false, cx);
21450 prompt.set_placeholder_text(
21451 match edit_action {
21452 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21453 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21454 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21455 },
21456 cx,
21457 );
21458
21459 prompt
21460 });
21461
21462 Self {
21463 prompt,
21464 editor,
21465 breakpoint_anchor,
21466 breakpoint,
21467 edit_action,
21468 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21469 block_ids: Default::default(),
21470 _subscriptions: vec![],
21471 }
21472 }
21473
21474 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21475 self.block_ids.extend(block_ids)
21476 }
21477
21478 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21479 if let Some(editor) = self.editor.upgrade() {
21480 let message = self
21481 .prompt
21482 .read(cx)
21483 .buffer
21484 .read(cx)
21485 .as_singleton()
21486 .expect("A multi buffer in breakpoint prompt isn't possible")
21487 .read(cx)
21488 .as_rope()
21489 .to_string();
21490
21491 editor.update(cx, |editor, cx| {
21492 editor.edit_breakpoint_at_anchor(
21493 self.breakpoint_anchor,
21494 self.breakpoint.clone(),
21495 match self.edit_action {
21496 BreakpointPromptEditAction::Log => {
21497 BreakpointEditAction::EditLogMessage(message.into())
21498 }
21499 BreakpointPromptEditAction::Condition => {
21500 BreakpointEditAction::EditCondition(message.into())
21501 }
21502 BreakpointPromptEditAction::HitCondition => {
21503 BreakpointEditAction::EditHitCondition(message.into())
21504 }
21505 },
21506 cx,
21507 );
21508
21509 editor.remove_blocks(self.block_ids.clone(), None, cx);
21510 cx.focus_self(window);
21511 });
21512 }
21513 }
21514
21515 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21516 self.editor
21517 .update(cx, |editor, cx| {
21518 editor.remove_blocks(self.block_ids.clone(), None, cx);
21519 window.focus(&editor.focus_handle);
21520 })
21521 .log_err();
21522 }
21523
21524 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21525 let settings = ThemeSettings::get_global(cx);
21526 let text_style = TextStyle {
21527 color: if self.prompt.read(cx).read_only(cx) {
21528 cx.theme().colors().text_disabled
21529 } else {
21530 cx.theme().colors().text
21531 },
21532 font_family: settings.buffer_font.family.clone(),
21533 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21534 font_size: settings.buffer_font_size(cx).into(),
21535 font_weight: settings.buffer_font.weight,
21536 line_height: relative(settings.buffer_line_height.value()),
21537 ..Default::default()
21538 };
21539 EditorElement::new(
21540 &self.prompt,
21541 EditorStyle {
21542 background: cx.theme().colors().editor_background,
21543 local_player: cx.theme().players().local(),
21544 text: text_style,
21545 ..Default::default()
21546 },
21547 )
21548 }
21549}
21550
21551impl Render for BreakpointPromptEditor {
21552 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21553 let editor_margins = *self.editor_margins.lock();
21554 let gutter_dimensions = editor_margins.gutter;
21555 h_flex()
21556 .key_context("Editor")
21557 .bg(cx.theme().colors().editor_background)
21558 .border_y_1()
21559 .border_color(cx.theme().status().info_border)
21560 .size_full()
21561 .py(window.line_height() / 2.5)
21562 .on_action(cx.listener(Self::confirm))
21563 .on_action(cx.listener(Self::cancel))
21564 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21565 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21566 }
21567}
21568
21569impl Focusable for BreakpointPromptEditor {
21570 fn focus_handle(&self, cx: &App) -> FocusHandle {
21571 self.prompt.focus_handle(cx)
21572 }
21573}
21574
21575fn all_edits_insertions_or_deletions(
21576 edits: &Vec<(Range<Anchor>, String)>,
21577 snapshot: &MultiBufferSnapshot,
21578) -> bool {
21579 let mut all_insertions = true;
21580 let mut all_deletions = true;
21581
21582 for (range, new_text) in edits.iter() {
21583 let range_is_empty = range.to_offset(&snapshot).is_empty();
21584 let text_is_empty = new_text.is_empty();
21585
21586 if range_is_empty != text_is_empty {
21587 if range_is_empty {
21588 all_deletions = false;
21589 } else {
21590 all_insertions = false;
21591 }
21592 } else {
21593 return false;
21594 }
21595
21596 if !all_insertions && !all_deletions {
21597 return false;
21598 }
21599 }
21600 all_insertions || all_deletions
21601}
21602
21603struct MissingEditPredictionKeybindingTooltip;
21604
21605impl Render for MissingEditPredictionKeybindingTooltip {
21606 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21607 ui::tooltip_container(window, cx, |container, _, cx| {
21608 container
21609 .flex_shrink_0()
21610 .max_w_80()
21611 .min_h(rems_from_px(124.))
21612 .justify_between()
21613 .child(
21614 v_flex()
21615 .flex_1()
21616 .text_ui_sm(cx)
21617 .child(Label::new("Conflict with Accept Keybinding"))
21618 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21619 )
21620 .child(
21621 h_flex()
21622 .pb_1()
21623 .gap_1()
21624 .items_end()
21625 .w_full()
21626 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21627 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21628 }))
21629 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21630 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21631 })),
21632 )
21633 })
21634 }
21635}
21636
21637#[derive(Debug, Clone, Copy, PartialEq)]
21638pub struct LineHighlight {
21639 pub background: Background,
21640 pub border: Option<gpui::Hsla>,
21641 pub include_gutter: bool,
21642 pub type_id: Option<TypeId>,
21643}
21644
21645fn render_diff_hunk_controls(
21646 row: u32,
21647 status: &DiffHunkStatus,
21648 hunk_range: Range<Anchor>,
21649 is_created_file: bool,
21650 line_height: Pixels,
21651 editor: &Entity<Editor>,
21652 _window: &mut Window,
21653 cx: &mut App,
21654) -> AnyElement {
21655 h_flex()
21656 .h(line_height)
21657 .mr_1()
21658 .gap_1()
21659 .px_0p5()
21660 .pb_1()
21661 .border_x_1()
21662 .border_b_1()
21663 .border_color(cx.theme().colors().border_variant)
21664 .rounded_b_lg()
21665 .bg(cx.theme().colors().editor_background)
21666 .gap_1()
21667 .occlude()
21668 .shadow_md()
21669 .child(if status.has_secondary_hunk() {
21670 Button::new(("stage", row as u64), "Stage")
21671 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21672 .tooltip({
21673 let focus_handle = editor.focus_handle(cx);
21674 move |window, cx| {
21675 Tooltip::for_action_in(
21676 "Stage Hunk",
21677 &::git::ToggleStaged,
21678 &focus_handle,
21679 window,
21680 cx,
21681 )
21682 }
21683 })
21684 .on_click({
21685 let editor = editor.clone();
21686 move |_event, _window, cx| {
21687 editor.update(cx, |editor, cx| {
21688 editor.stage_or_unstage_diff_hunks(
21689 true,
21690 vec![hunk_range.start..hunk_range.start],
21691 cx,
21692 );
21693 });
21694 }
21695 })
21696 } else {
21697 Button::new(("unstage", row as u64), "Unstage")
21698 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21699 .tooltip({
21700 let focus_handle = editor.focus_handle(cx);
21701 move |window, cx| {
21702 Tooltip::for_action_in(
21703 "Unstage Hunk",
21704 &::git::ToggleStaged,
21705 &focus_handle,
21706 window,
21707 cx,
21708 )
21709 }
21710 })
21711 .on_click({
21712 let editor = editor.clone();
21713 move |_event, _window, cx| {
21714 editor.update(cx, |editor, cx| {
21715 editor.stage_or_unstage_diff_hunks(
21716 false,
21717 vec![hunk_range.start..hunk_range.start],
21718 cx,
21719 );
21720 });
21721 }
21722 })
21723 })
21724 .child(
21725 Button::new(("restore", row as u64), "Restore")
21726 .tooltip({
21727 let focus_handle = editor.focus_handle(cx);
21728 move |window, cx| {
21729 Tooltip::for_action_in(
21730 "Restore Hunk",
21731 &::git::Restore,
21732 &focus_handle,
21733 window,
21734 cx,
21735 )
21736 }
21737 })
21738 .on_click({
21739 let editor = editor.clone();
21740 move |_event, window, cx| {
21741 editor.update(cx, |editor, cx| {
21742 let snapshot = editor.snapshot(window, cx);
21743 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21744 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21745 });
21746 }
21747 })
21748 .disabled(is_created_file),
21749 )
21750 .when(
21751 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21752 |el| {
21753 el.child(
21754 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21755 .shape(IconButtonShape::Square)
21756 .icon_size(IconSize::Small)
21757 // .disabled(!has_multiple_hunks)
21758 .tooltip({
21759 let focus_handle = editor.focus_handle(cx);
21760 move |window, cx| {
21761 Tooltip::for_action_in(
21762 "Next Hunk",
21763 &GoToHunk,
21764 &focus_handle,
21765 window,
21766 cx,
21767 )
21768 }
21769 })
21770 .on_click({
21771 let editor = editor.clone();
21772 move |_event, window, cx| {
21773 editor.update(cx, |editor, cx| {
21774 let snapshot = editor.snapshot(window, cx);
21775 let position =
21776 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21777 editor.go_to_hunk_before_or_after_position(
21778 &snapshot,
21779 position,
21780 Direction::Next,
21781 window,
21782 cx,
21783 );
21784 editor.expand_selected_diff_hunks(cx);
21785 });
21786 }
21787 }),
21788 )
21789 .child(
21790 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21791 .shape(IconButtonShape::Square)
21792 .icon_size(IconSize::Small)
21793 // .disabled(!has_multiple_hunks)
21794 .tooltip({
21795 let focus_handle = editor.focus_handle(cx);
21796 move |window, cx| {
21797 Tooltip::for_action_in(
21798 "Previous Hunk",
21799 &GoToPreviousHunk,
21800 &focus_handle,
21801 window,
21802 cx,
21803 )
21804 }
21805 })
21806 .on_click({
21807 let editor = editor.clone();
21808 move |_event, window, cx| {
21809 editor.update(cx, |editor, cx| {
21810 let snapshot = editor.snapshot(window, cx);
21811 let point =
21812 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21813 editor.go_to_hunk_before_or_after_position(
21814 &snapshot,
21815 point,
21816 Direction::Prev,
21817 window,
21818 cx,
21819 );
21820 editor.expand_selected_diff_hunks(cx);
21821 });
21822 }
21823 }),
21824 )
21825 },
21826 )
21827 .into_any_element()
21828}