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) = delimiters
3968 .iter()
3969 .filter_map(|delimiter| {
3970 let prefix = delimiter.trim_end();
3971 if comment_candidate.starts_with(prefix) {
3972 Some((delimiter, prefix.len()))
3973 } else {
3974 None
3975 }
3976 })
3977 .max_by_key(|(_, len)| *len)?;
3978
3979 let cursor_is_placed_after_comment_marker =
3980 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3981 if cursor_is_placed_after_comment_marker {
3982 Some(delimiter.clone())
3983 } else {
3984 None
3985 }
3986 });
3987
3988 let mut indent_on_newline = IndentSize::spaces(0);
3989 let mut indent_on_extra_newline = IndentSize::spaces(0);
3990
3991 let doc_delimiter = maybe!({
3992 if !selection_is_empty {
3993 return None;
3994 }
3995
3996 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3997 return None;
3998 }
3999
4000 let DocumentationConfig {
4001 start: start_tag,
4002 end: end_tag,
4003 prefix: delimiter,
4004 tab_size: len,
4005 } = language.documentation()?;
4006
4007 let (snapshot, range) =
4008 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4009
4010 let num_of_whitespaces = snapshot
4011 .chars_for_range(range.clone())
4012 .take_while(|c| c.is_whitespace())
4013 .count();
4014
4015 let cursor_is_after_start_tag = {
4016 let start_tag_len = start_tag.len();
4017 let start_tag_line = snapshot
4018 .chars_for_range(range.clone())
4019 .skip(num_of_whitespaces)
4020 .take(start_tag_len)
4021 .collect::<String>();
4022 if start_tag_line.starts_with(start_tag.as_ref()) {
4023 num_of_whitespaces + start_tag_len
4024 <= start_point.column as usize
4025 } else {
4026 false
4027 }
4028 };
4029
4030 let cursor_is_after_delimiter = {
4031 let delimiter_trim = delimiter.trim_end();
4032 let delimiter_line = snapshot
4033 .chars_for_range(range.clone())
4034 .skip(num_of_whitespaces)
4035 .take(delimiter_trim.len())
4036 .collect::<String>();
4037 if delimiter_line.starts_with(delimiter_trim) {
4038 num_of_whitespaces + delimiter_trim.len()
4039 <= start_point.column as usize
4040 } else {
4041 false
4042 }
4043 };
4044
4045 let cursor_is_before_end_tag_if_exists = {
4046 let num_of_whitespaces_rev = snapshot
4047 .reversed_chars_for_range(range.clone())
4048 .take_while(|c| c.is_whitespace())
4049 .count();
4050 let mut line_iter = snapshot
4051 .reversed_chars_for_range(range)
4052 .skip(num_of_whitespaces_rev);
4053 let end_tag_exists = end_tag
4054 .chars()
4055 .rev()
4056 .all(|char| line_iter.next() == Some(char));
4057 if end_tag_exists {
4058 let max_point = snapshot.line_len(start_point.row) as usize;
4059 let ordering = (num_of_whitespaces_rev
4060 + end_tag.len()
4061 + start_point.column as usize)
4062 .cmp(&max_point);
4063 let cursor_is_before_end_tag =
4064 ordering != Ordering::Greater;
4065 if cursor_is_after_start_tag {
4066 if cursor_is_before_end_tag {
4067 insert_extra_newline = true;
4068 }
4069 let cursor_is_at_start_of_end_tag =
4070 ordering == Ordering::Equal;
4071 if cursor_is_at_start_of_end_tag {
4072 indent_on_extra_newline.len = (*len).into();
4073 }
4074 }
4075 cursor_is_before_end_tag
4076 } else {
4077 true
4078 }
4079 };
4080
4081 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4082 && cursor_is_before_end_tag_if_exists
4083 {
4084 if cursor_is_after_start_tag {
4085 indent_on_newline.len = (*len).into();
4086 }
4087 Some(delimiter.clone())
4088 } else {
4089 None
4090 }
4091 });
4092
4093 (
4094 comment_delimiter,
4095 doc_delimiter,
4096 insert_extra_newline,
4097 indent_on_newline,
4098 indent_on_extra_newline,
4099 )
4100 } else {
4101 (
4102 None,
4103 None,
4104 false,
4105 IndentSize::default(),
4106 IndentSize::default(),
4107 )
4108 };
4109
4110 let prevent_auto_indent = doc_delimiter.is_some();
4111 let delimiter = comment_delimiter.or(doc_delimiter);
4112
4113 let capacity_for_delimiter =
4114 delimiter.as_deref().map(str::len).unwrap_or_default();
4115 let mut new_text = String::with_capacity(
4116 1 + capacity_for_delimiter
4117 + existing_indent.len as usize
4118 + indent_on_newline.len as usize
4119 + indent_on_extra_newline.len as usize,
4120 );
4121 new_text.push('\n');
4122 new_text.extend(existing_indent.chars());
4123 new_text.extend(indent_on_newline.chars());
4124
4125 if let Some(delimiter) = &delimiter {
4126 new_text.push_str(delimiter);
4127 }
4128
4129 if insert_extra_newline {
4130 new_text.push('\n');
4131 new_text.extend(existing_indent.chars());
4132 new_text.extend(indent_on_extra_newline.chars());
4133 }
4134
4135 let anchor = buffer.anchor_after(end);
4136 let new_selection = selection.map(|_| anchor);
4137 (
4138 ((start..end, new_text), prevent_auto_indent),
4139 (insert_extra_newline, new_selection),
4140 )
4141 })
4142 .unzip()
4143 };
4144
4145 let mut auto_indent_edits = Vec::new();
4146 let mut edits = Vec::new();
4147 for (edit, prevent_auto_indent) in edits_with_flags {
4148 if prevent_auto_indent {
4149 edits.push(edit);
4150 } else {
4151 auto_indent_edits.push(edit);
4152 }
4153 }
4154 if !edits.is_empty() {
4155 this.edit(edits, cx);
4156 }
4157 if !auto_indent_edits.is_empty() {
4158 this.edit_with_autoindent(auto_indent_edits, cx);
4159 }
4160
4161 let buffer = this.buffer.read(cx).snapshot(cx);
4162 let new_selections = selection_info
4163 .into_iter()
4164 .map(|(extra_newline_inserted, new_selection)| {
4165 let mut cursor = new_selection.end.to_point(&buffer);
4166 if extra_newline_inserted {
4167 cursor.row -= 1;
4168 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4169 }
4170 new_selection.map(|_| cursor)
4171 })
4172 .collect();
4173
4174 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4175 s.select(new_selections)
4176 });
4177 this.refresh_inline_completion(true, false, window, cx);
4178 });
4179 }
4180
4181 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4182 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4183
4184 let buffer = self.buffer.read(cx);
4185 let snapshot = buffer.snapshot(cx);
4186
4187 let mut edits = Vec::new();
4188 let mut rows = Vec::new();
4189
4190 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4191 let cursor = selection.head();
4192 let row = cursor.row;
4193
4194 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4195
4196 let newline = "\n".to_string();
4197 edits.push((start_of_line..start_of_line, newline));
4198
4199 rows.push(row + rows_inserted as u32);
4200 }
4201
4202 self.transact(window, cx, |editor, window, cx| {
4203 editor.edit(edits, cx);
4204
4205 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4206 let mut index = 0;
4207 s.move_cursors_with(|map, _, _| {
4208 let row = rows[index];
4209 index += 1;
4210
4211 let point = Point::new(row, 0);
4212 let boundary = map.next_line_boundary(point).1;
4213 let clipped = map.clip_point(boundary, Bias::Left);
4214
4215 (clipped, SelectionGoal::None)
4216 });
4217 });
4218
4219 let mut indent_edits = Vec::new();
4220 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4221 for row in rows {
4222 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4223 for (row, indent) in indents {
4224 if indent.len == 0 {
4225 continue;
4226 }
4227
4228 let text = match indent.kind {
4229 IndentKind::Space => " ".repeat(indent.len as usize),
4230 IndentKind::Tab => "\t".repeat(indent.len as usize),
4231 };
4232 let point = Point::new(row.0, 0);
4233 indent_edits.push((point..point, text));
4234 }
4235 }
4236 editor.edit(indent_edits, cx);
4237 });
4238 }
4239
4240 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4241 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4242
4243 let buffer = self.buffer.read(cx);
4244 let snapshot = buffer.snapshot(cx);
4245
4246 let mut edits = Vec::new();
4247 let mut rows = Vec::new();
4248 let mut rows_inserted = 0;
4249
4250 for selection in self.selections.all_adjusted(cx) {
4251 let cursor = selection.head();
4252 let row = cursor.row;
4253
4254 let point = Point::new(row + 1, 0);
4255 let start_of_line = snapshot.clip_point(point, Bias::Left);
4256
4257 let newline = "\n".to_string();
4258 edits.push((start_of_line..start_of_line, newline));
4259
4260 rows_inserted += 1;
4261 rows.push(row + rows_inserted);
4262 }
4263
4264 self.transact(window, cx, |editor, window, cx| {
4265 editor.edit(edits, cx);
4266
4267 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4268 let mut index = 0;
4269 s.move_cursors_with(|map, _, _| {
4270 let row = rows[index];
4271 index += 1;
4272
4273 let point = Point::new(row, 0);
4274 let boundary = map.next_line_boundary(point).1;
4275 let clipped = map.clip_point(boundary, Bias::Left);
4276
4277 (clipped, SelectionGoal::None)
4278 });
4279 });
4280
4281 let mut indent_edits = Vec::new();
4282 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4283 for row in rows {
4284 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4285 for (row, indent) in indents {
4286 if indent.len == 0 {
4287 continue;
4288 }
4289
4290 let text = match indent.kind {
4291 IndentKind::Space => " ".repeat(indent.len as usize),
4292 IndentKind::Tab => "\t".repeat(indent.len as usize),
4293 };
4294 let point = Point::new(row.0, 0);
4295 indent_edits.push((point..point, text));
4296 }
4297 }
4298 editor.edit(indent_edits, cx);
4299 });
4300 }
4301
4302 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4303 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4304 original_indent_columns: Vec::new(),
4305 });
4306 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4307 }
4308
4309 fn insert_with_autoindent_mode(
4310 &mut self,
4311 text: &str,
4312 autoindent_mode: Option<AutoindentMode>,
4313 window: &mut Window,
4314 cx: &mut Context<Self>,
4315 ) {
4316 if self.read_only(cx) {
4317 return;
4318 }
4319
4320 let text: Arc<str> = text.into();
4321 self.transact(window, cx, |this, window, cx| {
4322 let old_selections = this.selections.all_adjusted(cx);
4323 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4324 let anchors = {
4325 let snapshot = buffer.read(cx);
4326 old_selections
4327 .iter()
4328 .map(|s| {
4329 let anchor = snapshot.anchor_after(s.head());
4330 s.map(|_| anchor)
4331 })
4332 .collect::<Vec<_>>()
4333 };
4334 buffer.edit(
4335 old_selections
4336 .iter()
4337 .map(|s| (s.start..s.end, text.clone())),
4338 autoindent_mode,
4339 cx,
4340 );
4341 anchors
4342 });
4343
4344 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4345 s.select_anchors(selection_anchors);
4346 });
4347
4348 cx.notify();
4349 });
4350 }
4351
4352 fn trigger_completion_on_input(
4353 &mut self,
4354 text: &str,
4355 trigger_in_words: bool,
4356 window: &mut Window,
4357 cx: &mut Context<Self>,
4358 ) {
4359 let ignore_completion_provider = self
4360 .context_menu
4361 .borrow()
4362 .as_ref()
4363 .map(|menu| match menu {
4364 CodeContextMenu::Completions(completions_menu) => {
4365 completions_menu.ignore_completion_provider
4366 }
4367 CodeContextMenu::CodeActions(_) => false,
4368 })
4369 .unwrap_or(false);
4370
4371 if ignore_completion_provider {
4372 self.show_word_completions(&ShowWordCompletions, window, cx);
4373 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4374 self.show_completions(
4375 &ShowCompletions {
4376 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4377 },
4378 window,
4379 cx,
4380 );
4381 } else {
4382 self.hide_context_menu(window, cx);
4383 }
4384 }
4385
4386 fn is_completion_trigger(
4387 &self,
4388 text: &str,
4389 trigger_in_words: bool,
4390 cx: &mut Context<Self>,
4391 ) -> bool {
4392 let position = self.selections.newest_anchor().head();
4393 let multibuffer = self.buffer.read(cx);
4394 let Some(buffer) = position
4395 .buffer_id
4396 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4397 else {
4398 return false;
4399 };
4400
4401 if let Some(completion_provider) = &self.completion_provider {
4402 completion_provider.is_completion_trigger(
4403 &buffer,
4404 position.text_anchor,
4405 text,
4406 trigger_in_words,
4407 cx,
4408 )
4409 } else {
4410 false
4411 }
4412 }
4413
4414 /// If any empty selections is touching the start of its innermost containing autoclose
4415 /// region, expand it to select the brackets.
4416 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4417 let selections = self.selections.all::<usize>(cx);
4418 let buffer = self.buffer.read(cx).read(cx);
4419 let new_selections = self
4420 .selections_with_autoclose_regions(selections, &buffer)
4421 .map(|(mut selection, region)| {
4422 if !selection.is_empty() {
4423 return selection;
4424 }
4425
4426 if let Some(region) = region {
4427 let mut range = region.range.to_offset(&buffer);
4428 if selection.start == range.start && range.start >= region.pair.start.len() {
4429 range.start -= region.pair.start.len();
4430 if buffer.contains_str_at(range.start, ®ion.pair.start)
4431 && buffer.contains_str_at(range.end, ®ion.pair.end)
4432 {
4433 range.end += region.pair.end.len();
4434 selection.start = range.start;
4435 selection.end = range.end;
4436
4437 return selection;
4438 }
4439 }
4440 }
4441
4442 let always_treat_brackets_as_autoclosed = buffer
4443 .language_settings_at(selection.start, cx)
4444 .always_treat_brackets_as_autoclosed;
4445
4446 if !always_treat_brackets_as_autoclosed {
4447 return selection;
4448 }
4449
4450 if let Some(scope) = buffer.language_scope_at(selection.start) {
4451 for (pair, enabled) in scope.brackets() {
4452 if !enabled || !pair.close {
4453 continue;
4454 }
4455
4456 if buffer.contains_str_at(selection.start, &pair.end) {
4457 let pair_start_len = pair.start.len();
4458 if buffer.contains_str_at(
4459 selection.start.saturating_sub(pair_start_len),
4460 &pair.start,
4461 ) {
4462 selection.start -= pair_start_len;
4463 selection.end += pair.end.len();
4464
4465 return selection;
4466 }
4467 }
4468 }
4469 }
4470
4471 selection
4472 })
4473 .collect();
4474
4475 drop(buffer);
4476 self.change_selections(None, window, cx, |selections| {
4477 selections.select(new_selections)
4478 });
4479 }
4480
4481 /// Iterate the given selections, and for each one, find the smallest surrounding
4482 /// autoclose region. This uses the ordering of the selections and the autoclose
4483 /// regions to avoid repeated comparisons.
4484 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4485 &'a self,
4486 selections: impl IntoIterator<Item = Selection<D>>,
4487 buffer: &'a MultiBufferSnapshot,
4488 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4489 let mut i = 0;
4490 let mut regions = self.autoclose_regions.as_slice();
4491 selections.into_iter().map(move |selection| {
4492 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4493
4494 let mut enclosing = None;
4495 while let Some(pair_state) = regions.get(i) {
4496 if pair_state.range.end.to_offset(buffer) < range.start {
4497 regions = ®ions[i + 1..];
4498 i = 0;
4499 } else if pair_state.range.start.to_offset(buffer) > range.end {
4500 break;
4501 } else {
4502 if pair_state.selection_id == selection.id {
4503 enclosing = Some(pair_state);
4504 }
4505 i += 1;
4506 }
4507 }
4508
4509 (selection, enclosing)
4510 })
4511 }
4512
4513 /// Remove any autoclose regions that no longer contain their selection.
4514 fn invalidate_autoclose_regions(
4515 &mut self,
4516 mut selections: &[Selection<Anchor>],
4517 buffer: &MultiBufferSnapshot,
4518 ) {
4519 self.autoclose_regions.retain(|state| {
4520 let mut i = 0;
4521 while let Some(selection) = selections.get(i) {
4522 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4523 selections = &selections[1..];
4524 continue;
4525 }
4526 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4527 break;
4528 }
4529 if selection.id == state.selection_id {
4530 return true;
4531 } else {
4532 i += 1;
4533 }
4534 }
4535 false
4536 });
4537 }
4538
4539 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4540 let offset = position.to_offset(buffer);
4541 let (word_range, kind) = buffer.surrounding_word(offset, true);
4542 if offset > word_range.start && kind == Some(CharKind::Word) {
4543 Some(
4544 buffer
4545 .text_for_range(word_range.start..offset)
4546 .collect::<String>(),
4547 )
4548 } else {
4549 None
4550 }
4551 }
4552
4553 pub fn toggle_inline_values(
4554 &mut self,
4555 _: &ToggleInlineValues,
4556 _: &mut Window,
4557 cx: &mut Context<Self>,
4558 ) {
4559 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4560
4561 self.refresh_inline_values(cx);
4562 }
4563
4564 pub fn toggle_inlay_hints(
4565 &mut self,
4566 _: &ToggleInlayHints,
4567 _: &mut Window,
4568 cx: &mut Context<Self>,
4569 ) {
4570 self.refresh_inlay_hints(
4571 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4572 cx,
4573 );
4574 }
4575
4576 pub fn inlay_hints_enabled(&self) -> bool {
4577 self.inlay_hint_cache.enabled
4578 }
4579
4580 pub fn inline_values_enabled(&self) -> bool {
4581 self.inline_value_cache.enabled
4582 }
4583
4584 #[cfg(any(test, feature = "test-support"))]
4585 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4586 self.display_map
4587 .read(cx)
4588 .current_inlays()
4589 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4590 .cloned()
4591 .collect()
4592 }
4593
4594 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4595 if self.semantics_provider.is_none() || !self.mode.is_full() {
4596 return;
4597 }
4598
4599 let reason_description = reason.description();
4600 let ignore_debounce = matches!(
4601 reason,
4602 InlayHintRefreshReason::SettingsChange(_)
4603 | InlayHintRefreshReason::Toggle(_)
4604 | InlayHintRefreshReason::ExcerptsRemoved(_)
4605 | InlayHintRefreshReason::ModifiersChanged(_)
4606 );
4607 let (invalidate_cache, required_languages) = match reason {
4608 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4609 match self.inlay_hint_cache.modifiers_override(enabled) {
4610 Some(enabled) => {
4611 if enabled {
4612 (InvalidationStrategy::RefreshRequested, None)
4613 } else {
4614 self.splice_inlays(
4615 &self
4616 .visible_inlay_hints(cx)
4617 .iter()
4618 .map(|inlay| inlay.id)
4619 .collect::<Vec<InlayId>>(),
4620 Vec::new(),
4621 cx,
4622 );
4623 return;
4624 }
4625 }
4626 None => return,
4627 }
4628 }
4629 InlayHintRefreshReason::Toggle(enabled) => {
4630 if self.inlay_hint_cache.toggle(enabled) {
4631 if enabled {
4632 (InvalidationStrategy::RefreshRequested, None)
4633 } else {
4634 self.splice_inlays(
4635 &self
4636 .visible_inlay_hints(cx)
4637 .iter()
4638 .map(|inlay| inlay.id)
4639 .collect::<Vec<InlayId>>(),
4640 Vec::new(),
4641 cx,
4642 );
4643 return;
4644 }
4645 } else {
4646 return;
4647 }
4648 }
4649 InlayHintRefreshReason::SettingsChange(new_settings) => {
4650 match self.inlay_hint_cache.update_settings(
4651 &self.buffer,
4652 new_settings,
4653 self.visible_inlay_hints(cx),
4654 cx,
4655 ) {
4656 ControlFlow::Break(Some(InlaySplice {
4657 to_remove,
4658 to_insert,
4659 })) => {
4660 self.splice_inlays(&to_remove, to_insert, cx);
4661 return;
4662 }
4663 ControlFlow::Break(None) => return,
4664 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4665 }
4666 }
4667 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4668 if let Some(InlaySplice {
4669 to_remove,
4670 to_insert,
4671 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4672 {
4673 self.splice_inlays(&to_remove, to_insert, cx);
4674 }
4675 self.display_map.update(cx, |display_map, _| {
4676 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4677 });
4678 return;
4679 }
4680 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4681 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4682 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4683 }
4684 InlayHintRefreshReason::RefreshRequested => {
4685 (InvalidationStrategy::RefreshRequested, None)
4686 }
4687 };
4688
4689 if let Some(InlaySplice {
4690 to_remove,
4691 to_insert,
4692 }) = self.inlay_hint_cache.spawn_hint_refresh(
4693 reason_description,
4694 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4695 invalidate_cache,
4696 ignore_debounce,
4697 cx,
4698 ) {
4699 self.splice_inlays(&to_remove, to_insert, cx);
4700 }
4701 }
4702
4703 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4704 self.display_map
4705 .read(cx)
4706 .current_inlays()
4707 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4708 .cloned()
4709 .collect()
4710 }
4711
4712 pub fn excerpts_for_inlay_hints_query(
4713 &self,
4714 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4715 cx: &mut Context<Editor>,
4716 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4717 let Some(project) = self.project.as_ref() else {
4718 return HashMap::default();
4719 };
4720 let project = project.read(cx);
4721 let multi_buffer = self.buffer().read(cx);
4722 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4723 let multi_buffer_visible_start = self
4724 .scroll_manager
4725 .anchor()
4726 .anchor
4727 .to_point(&multi_buffer_snapshot);
4728 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4729 multi_buffer_visible_start
4730 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4731 Bias::Left,
4732 );
4733 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4734 multi_buffer_snapshot
4735 .range_to_buffer_ranges(multi_buffer_visible_range)
4736 .into_iter()
4737 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4738 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4739 let buffer_file = project::File::from_dyn(buffer.file())?;
4740 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4741 let worktree_entry = buffer_worktree
4742 .read(cx)
4743 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4744 if worktree_entry.is_ignored {
4745 return None;
4746 }
4747
4748 let language = buffer.language()?;
4749 if let Some(restrict_to_languages) = restrict_to_languages {
4750 if !restrict_to_languages.contains(language) {
4751 return None;
4752 }
4753 }
4754 Some((
4755 excerpt_id,
4756 (
4757 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4758 buffer.version().clone(),
4759 excerpt_visible_range,
4760 ),
4761 ))
4762 })
4763 .collect()
4764 }
4765
4766 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4767 TextLayoutDetails {
4768 text_system: window.text_system().clone(),
4769 editor_style: self.style.clone().unwrap(),
4770 rem_size: window.rem_size(),
4771 scroll_anchor: self.scroll_manager.anchor(),
4772 visible_rows: self.visible_line_count(),
4773 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4774 }
4775 }
4776
4777 pub fn splice_inlays(
4778 &self,
4779 to_remove: &[InlayId],
4780 to_insert: Vec<Inlay>,
4781 cx: &mut Context<Self>,
4782 ) {
4783 self.display_map.update(cx, |display_map, cx| {
4784 display_map.splice_inlays(to_remove, to_insert, cx)
4785 });
4786 cx.notify();
4787 }
4788
4789 fn trigger_on_type_formatting(
4790 &self,
4791 input: String,
4792 window: &mut Window,
4793 cx: &mut Context<Self>,
4794 ) -> Option<Task<Result<()>>> {
4795 if input.len() != 1 {
4796 return None;
4797 }
4798
4799 let project = self.project.as_ref()?;
4800 let position = self.selections.newest_anchor().head();
4801 let (buffer, buffer_position) = self
4802 .buffer
4803 .read(cx)
4804 .text_anchor_for_position(position, cx)?;
4805
4806 let settings = language_settings::language_settings(
4807 buffer
4808 .read(cx)
4809 .language_at(buffer_position)
4810 .map(|l| l.name()),
4811 buffer.read(cx).file(),
4812 cx,
4813 );
4814 if !settings.use_on_type_format {
4815 return None;
4816 }
4817
4818 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4819 // hence we do LSP request & edit on host side only — add formats to host's history.
4820 let push_to_lsp_host_history = true;
4821 // If this is not the host, append its history with new edits.
4822 let push_to_client_history = project.read(cx).is_via_collab();
4823
4824 let on_type_formatting = project.update(cx, |project, cx| {
4825 project.on_type_format(
4826 buffer.clone(),
4827 buffer_position,
4828 input,
4829 push_to_lsp_host_history,
4830 cx,
4831 )
4832 });
4833 Some(cx.spawn_in(window, async move |editor, cx| {
4834 if let Some(transaction) = on_type_formatting.await? {
4835 if push_to_client_history {
4836 buffer
4837 .update(cx, |buffer, _| {
4838 buffer.push_transaction(transaction, Instant::now());
4839 buffer.finalize_last_transaction();
4840 })
4841 .ok();
4842 }
4843 editor.update(cx, |editor, cx| {
4844 editor.refresh_document_highlights(cx);
4845 })?;
4846 }
4847 Ok(())
4848 }))
4849 }
4850
4851 pub fn show_word_completions(
4852 &mut self,
4853 _: &ShowWordCompletions,
4854 window: &mut Window,
4855 cx: &mut Context<Self>,
4856 ) {
4857 self.open_completions_menu(true, None, window, cx);
4858 }
4859
4860 pub fn show_completions(
4861 &mut self,
4862 options: &ShowCompletions,
4863 window: &mut Window,
4864 cx: &mut Context<Self>,
4865 ) {
4866 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4867 }
4868
4869 fn open_completions_menu(
4870 &mut self,
4871 ignore_completion_provider: bool,
4872 trigger: Option<&str>,
4873 window: &mut Window,
4874 cx: &mut Context<Self>,
4875 ) {
4876 if self.pending_rename.is_some() {
4877 return;
4878 }
4879 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4880 return;
4881 }
4882
4883 let position = self.selections.newest_anchor().head();
4884 if position.diff_base_anchor.is_some() {
4885 return;
4886 }
4887 let (buffer, buffer_position) =
4888 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4889 output
4890 } else {
4891 return;
4892 };
4893 let buffer_snapshot = buffer.read(cx).snapshot();
4894 let show_completion_documentation = buffer_snapshot
4895 .settings_at(buffer_position, cx)
4896 .show_completion_documentation;
4897
4898 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4899
4900 let trigger_kind = match trigger {
4901 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4902 CompletionTriggerKind::TRIGGER_CHARACTER
4903 }
4904 _ => CompletionTriggerKind::INVOKED,
4905 };
4906 let completion_context = CompletionContext {
4907 trigger_character: trigger.and_then(|trigger| {
4908 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4909 Some(String::from(trigger))
4910 } else {
4911 None
4912 }
4913 }),
4914 trigger_kind,
4915 };
4916
4917 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4918 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4919 let word_to_exclude = buffer_snapshot
4920 .text_for_range(old_range.clone())
4921 .collect::<String>();
4922 (
4923 buffer_snapshot.anchor_before(old_range.start)
4924 ..buffer_snapshot.anchor_after(old_range.end),
4925 Some(word_to_exclude),
4926 )
4927 } else {
4928 (buffer_position..buffer_position, None)
4929 };
4930
4931 let completion_settings = language_settings(
4932 buffer_snapshot
4933 .language_at(buffer_position)
4934 .map(|language| language.name()),
4935 buffer_snapshot.file(),
4936 cx,
4937 )
4938 .completions;
4939
4940 // The document can be large, so stay in reasonable bounds when searching for words,
4941 // otherwise completion pop-up might be slow to appear.
4942 const WORD_LOOKUP_ROWS: u32 = 5_000;
4943 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4944 let min_word_search = buffer_snapshot.clip_point(
4945 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4946 Bias::Left,
4947 );
4948 let max_word_search = buffer_snapshot.clip_point(
4949 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4950 Bias::Right,
4951 );
4952 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4953 ..buffer_snapshot.point_to_offset(max_word_search);
4954
4955 let provider = self
4956 .completion_provider
4957 .as_ref()
4958 .filter(|_| !ignore_completion_provider);
4959 let skip_digits = query
4960 .as_ref()
4961 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4962
4963 let (mut words, provided_completions) = match provider {
4964 Some(provider) => {
4965 let completions = provider.completions(
4966 position.excerpt_id,
4967 &buffer,
4968 buffer_position,
4969 completion_context,
4970 window,
4971 cx,
4972 );
4973
4974 let words = match completion_settings.words {
4975 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4976 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4977 .background_spawn(async move {
4978 buffer_snapshot.words_in_range(WordsQuery {
4979 fuzzy_contents: None,
4980 range: word_search_range,
4981 skip_digits,
4982 })
4983 }),
4984 };
4985
4986 (words, completions)
4987 }
4988 None => (
4989 cx.background_spawn(async move {
4990 buffer_snapshot.words_in_range(WordsQuery {
4991 fuzzy_contents: None,
4992 range: word_search_range,
4993 skip_digits,
4994 })
4995 }),
4996 Task::ready(Ok(None)),
4997 ),
4998 };
4999
5000 let sort_completions = provider
5001 .as_ref()
5002 .map_or(false, |provider| provider.sort_completions());
5003
5004 let filter_completions = provider
5005 .as_ref()
5006 .map_or(true, |provider| provider.filter_completions());
5007
5008 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5009
5010 let id = post_inc(&mut self.next_completion_id);
5011 let task = cx.spawn_in(window, async move |editor, cx| {
5012 async move {
5013 editor.update(cx, |this, _| {
5014 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5015 })?;
5016
5017 let mut completions = Vec::new();
5018 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5019 completions.extend(provided_completions);
5020 if completion_settings.words == WordsCompletionMode::Fallback {
5021 words = Task::ready(BTreeMap::default());
5022 }
5023 }
5024
5025 let mut words = words.await;
5026 if let Some(word_to_exclude) = &word_to_exclude {
5027 words.remove(word_to_exclude);
5028 }
5029 for lsp_completion in &completions {
5030 words.remove(&lsp_completion.new_text);
5031 }
5032 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5033 replace_range: old_range.clone(),
5034 new_text: word.clone(),
5035 label: CodeLabel::plain(word, None),
5036 icon_path: None,
5037 documentation: None,
5038 source: CompletionSource::BufferWord {
5039 word_range,
5040 resolved: false,
5041 },
5042 insert_text_mode: Some(InsertTextMode::AS_IS),
5043 confirm: None,
5044 }));
5045
5046 let menu = if completions.is_empty() {
5047 None
5048 } else {
5049 let mut menu = CompletionsMenu::new(
5050 id,
5051 sort_completions,
5052 show_completion_documentation,
5053 ignore_completion_provider,
5054 position,
5055 buffer.clone(),
5056 completions.into(),
5057 snippet_sort_order,
5058 );
5059
5060 menu.filter(
5061 if filter_completions {
5062 query.as_deref()
5063 } else {
5064 None
5065 },
5066 cx.background_executor().clone(),
5067 )
5068 .await;
5069
5070 menu.visible().then_some(menu)
5071 };
5072
5073 editor.update_in(cx, |editor, window, cx| {
5074 match editor.context_menu.borrow().as_ref() {
5075 None => {}
5076 Some(CodeContextMenu::Completions(prev_menu)) => {
5077 if prev_menu.id > id {
5078 return;
5079 }
5080 }
5081 _ => return,
5082 }
5083
5084 if editor.focus_handle.is_focused(window) && menu.is_some() {
5085 let mut menu = menu.unwrap();
5086 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5087 crate::hover_popover::hide_hover(editor, cx);
5088 *editor.context_menu.borrow_mut() =
5089 Some(CodeContextMenu::Completions(menu));
5090
5091 if editor.show_edit_predictions_in_menu() {
5092 editor.update_visible_inline_completion(window, cx);
5093 } else {
5094 editor.discard_inline_completion(false, cx);
5095 }
5096
5097 cx.notify();
5098 } else if editor.completion_tasks.len() <= 1 {
5099 // If there are no more completion tasks and the last menu was
5100 // empty, we should hide it.
5101 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5102 // If it was already hidden and we don't show inline
5103 // completions in the menu, we should also show the
5104 // inline-completion when available.
5105 if was_hidden && editor.show_edit_predictions_in_menu() {
5106 editor.update_visible_inline_completion(window, cx);
5107 }
5108 }
5109 })?;
5110
5111 anyhow::Ok(())
5112 }
5113 .log_err()
5114 .await
5115 });
5116
5117 self.completion_tasks.push((id, task));
5118 }
5119
5120 #[cfg(feature = "test-support")]
5121 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5122 let menu = self.context_menu.borrow();
5123 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5124 let completions = menu.completions.borrow();
5125 Some(completions.to_vec())
5126 } else {
5127 None
5128 }
5129 }
5130
5131 pub fn confirm_completion(
5132 &mut self,
5133 action: &ConfirmCompletion,
5134 window: &mut Window,
5135 cx: &mut Context<Self>,
5136 ) -> Option<Task<Result<()>>> {
5137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5138 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5139 }
5140
5141 pub fn confirm_completion_insert(
5142 &mut self,
5143 _: &ConfirmCompletionInsert,
5144 window: &mut Window,
5145 cx: &mut Context<Self>,
5146 ) -> Option<Task<Result<()>>> {
5147 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5148 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5149 }
5150
5151 pub fn confirm_completion_replace(
5152 &mut self,
5153 _: &ConfirmCompletionReplace,
5154 window: &mut Window,
5155 cx: &mut Context<Self>,
5156 ) -> Option<Task<Result<()>>> {
5157 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5158 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5159 }
5160
5161 pub fn compose_completion(
5162 &mut self,
5163 action: &ComposeCompletion,
5164 window: &mut Window,
5165 cx: &mut Context<Self>,
5166 ) -> Option<Task<Result<()>>> {
5167 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5168 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5169 }
5170
5171 fn do_completion(
5172 &mut self,
5173 item_ix: Option<usize>,
5174 intent: CompletionIntent,
5175 window: &mut Window,
5176 cx: &mut Context<Editor>,
5177 ) -> Option<Task<Result<()>>> {
5178 use language::ToOffset as _;
5179
5180 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5181 else {
5182 return None;
5183 };
5184
5185 let candidate_id = {
5186 let entries = completions_menu.entries.borrow();
5187 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5188 if self.show_edit_predictions_in_menu() {
5189 self.discard_inline_completion(true, cx);
5190 }
5191 mat.candidate_id
5192 };
5193
5194 let buffer_handle = completions_menu.buffer;
5195 let completion = completions_menu
5196 .completions
5197 .borrow()
5198 .get(candidate_id)?
5199 .clone();
5200 cx.stop_propagation();
5201
5202 let snapshot = self.buffer.read(cx).snapshot(cx);
5203 let newest_anchor = self.selections.newest_anchor();
5204
5205 let snippet;
5206 let new_text;
5207 if completion.is_snippet() {
5208 let mut snippet_source = completion.new_text.clone();
5209 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5210 if scope.prefers_label_for_snippet_in_completion() {
5211 if let Some(label) = completion.label() {
5212 if matches!(
5213 completion.kind(),
5214 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5215 ) {
5216 snippet_source = label;
5217 }
5218 }
5219 }
5220 }
5221 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5222 new_text = snippet.as_ref().unwrap().text.clone();
5223 } else {
5224 snippet = None;
5225 new_text = completion.new_text.clone();
5226 };
5227
5228 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5229 let buffer = buffer_handle.read(cx);
5230 let replace_range_multibuffer = {
5231 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5232 let multibuffer_anchor = snapshot
5233 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5234 .unwrap()
5235 ..snapshot
5236 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5237 .unwrap();
5238 multibuffer_anchor.start.to_offset(&snapshot)
5239 ..multibuffer_anchor.end.to_offset(&snapshot)
5240 };
5241 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5242 return None;
5243 }
5244
5245 let old_text = buffer
5246 .text_for_range(replace_range.clone())
5247 .collect::<String>();
5248 let lookbehind = newest_anchor
5249 .start
5250 .text_anchor
5251 .to_offset(buffer)
5252 .saturating_sub(replace_range.start);
5253 let lookahead = replace_range
5254 .end
5255 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5256 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5257 let suffix = &old_text[lookbehind.min(old_text.len())..];
5258
5259 let selections = self.selections.all::<usize>(cx);
5260 let mut ranges = Vec::new();
5261 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5262
5263 for selection in &selections {
5264 let range = if selection.id == newest_anchor.id {
5265 replace_range_multibuffer.clone()
5266 } else {
5267 let mut range = selection.range();
5268
5269 // if prefix is present, don't duplicate it
5270 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5271 range.start = range.start.saturating_sub(lookbehind);
5272
5273 // if suffix is also present, mimic the newest cursor and replace it
5274 if selection.id != newest_anchor.id
5275 && snapshot.contains_str_at(range.end, suffix)
5276 {
5277 range.end += lookahead;
5278 }
5279 }
5280 range
5281 };
5282
5283 ranges.push(range.clone());
5284
5285 if !self.linked_edit_ranges.is_empty() {
5286 let start_anchor = snapshot.anchor_before(range.start);
5287 let end_anchor = snapshot.anchor_after(range.end);
5288 if let Some(ranges) = self
5289 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5290 {
5291 for (buffer, edits) in ranges {
5292 linked_edits
5293 .entry(buffer.clone())
5294 .or_default()
5295 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5296 }
5297 }
5298 }
5299 }
5300
5301 cx.emit(EditorEvent::InputHandled {
5302 utf16_range_to_replace: None,
5303 text: new_text.clone().into(),
5304 });
5305
5306 self.transact(window, cx, |this, window, cx| {
5307 if let Some(mut snippet) = snippet {
5308 snippet.text = new_text.to_string();
5309 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5310 } else {
5311 this.buffer.update(cx, |buffer, cx| {
5312 let auto_indent = match completion.insert_text_mode {
5313 Some(InsertTextMode::AS_IS) => None,
5314 _ => this.autoindent_mode.clone(),
5315 };
5316 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5317 buffer.edit(edits, auto_indent, cx);
5318 });
5319 }
5320 for (buffer, edits) in linked_edits {
5321 buffer.update(cx, |buffer, cx| {
5322 let snapshot = buffer.snapshot();
5323 let edits = edits
5324 .into_iter()
5325 .map(|(range, text)| {
5326 use text::ToPoint as TP;
5327 let end_point = TP::to_point(&range.end, &snapshot);
5328 let start_point = TP::to_point(&range.start, &snapshot);
5329 (start_point..end_point, text)
5330 })
5331 .sorted_by_key(|(range, _)| range.start);
5332 buffer.edit(edits, None, cx);
5333 })
5334 }
5335
5336 this.refresh_inline_completion(true, false, window, cx);
5337 });
5338
5339 let show_new_completions_on_confirm = completion
5340 .confirm
5341 .as_ref()
5342 .map_or(false, |confirm| confirm(intent, window, cx));
5343 if show_new_completions_on_confirm {
5344 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5345 }
5346
5347 let provider = self.completion_provider.as_ref()?;
5348 drop(completion);
5349 let apply_edits = provider.apply_additional_edits_for_completion(
5350 buffer_handle,
5351 completions_menu.completions.clone(),
5352 candidate_id,
5353 true,
5354 cx,
5355 );
5356
5357 let editor_settings = EditorSettings::get_global(cx);
5358 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5359 // After the code completion is finished, users often want to know what signatures are needed.
5360 // so we should automatically call signature_help
5361 self.show_signature_help(&ShowSignatureHelp, window, cx);
5362 }
5363
5364 Some(cx.foreground_executor().spawn(async move {
5365 apply_edits.await?;
5366 Ok(())
5367 }))
5368 }
5369
5370 pub fn toggle_code_actions(
5371 &mut self,
5372 action: &ToggleCodeActions,
5373 window: &mut Window,
5374 cx: &mut Context<Self>,
5375 ) {
5376 let quick_launch = action.quick_launch;
5377 let mut context_menu = self.context_menu.borrow_mut();
5378 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5379 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5380 // Toggle if we're selecting the same one
5381 *context_menu = None;
5382 cx.notify();
5383 return;
5384 } else {
5385 // Otherwise, clear it and start a new one
5386 *context_menu = None;
5387 cx.notify();
5388 }
5389 }
5390 drop(context_menu);
5391 let snapshot = self.snapshot(window, cx);
5392 let deployed_from_indicator = action.deployed_from_indicator;
5393 let mut task = self.code_actions_task.take();
5394 let action = action.clone();
5395 cx.spawn_in(window, async move |editor, cx| {
5396 while let Some(prev_task) = task {
5397 prev_task.await.log_err();
5398 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5399 }
5400
5401 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5402 if editor.focus_handle.is_focused(window) {
5403 let multibuffer_point = action
5404 .deployed_from_indicator
5405 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5406 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5407 let (buffer, buffer_row) = snapshot
5408 .buffer_snapshot
5409 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5410 .and_then(|(buffer_snapshot, range)| {
5411 editor
5412 .buffer
5413 .read(cx)
5414 .buffer(buffer_snapshot.remote_id())
5415 .map(|buffer| (buffer, range.start.row))
5416 })?;
5417 let (_, code_actions) = editor
5418 .available_code_actions
5419 .clone()
5420 .and_then(|(location, code_actions)| {
5421 let snapshot = location.buffer.read(cx).snapshot();
5422 let point_range = location.range.to_point(&snapshot);
5423 let point_range = point_range.start.row..=point_range.end.row;
5424 if point_range.contains(&buffer_row) {
5425 Some((location, code_actions))
5426 } else {
5427 None
5428 }
5429 })
5430 .unzip();
5431 let buffer_id = buffer.read(cx).remote_id();
5432 let tasks = editor
5433 .tasks
5434 .get(&(buffer_id, buffer_row))
5435 .map(|t| Arc::new(t.to_owned()));
5436 if tasks.is_none() && code_actions.is_none() {
5437 return None;
5438 }
5439
5440 editor.completion_tasks.clear();
5441 editor.discard_inline_completion(false, cx);
5442 let task_context =
5443 tasks
5444 .as_ref()
5445 .zip(editor.project.clone())
5446 .map(|(tasks, project)| {
5447 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5448 });
5449
5450 Some(cx.spawn_in(window, async move |editor, cx| {
5451 let task_context = match task_context {
5452 Some(task_context) => task_context.await,
5453 None => None,
5454 };
5455 let resolved_tasks =
5456 tasks
5457 .zip(task_context.clone())
5458 .map(|(tasks, task_context)| ResolvedTasks {
5459 templates: tasks.resolve(&task_context).collect(),
5460 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5461 multibuffer_point.row,
5462 tasks.column,
5463 )),
5464 });
5465 let debug_scenarios = editor.update(cx, |editor, cx| {
5466 if cx.has_flag::<DebuggerFeatureFlag>() {
5467 maybe!({
5468 let project = editor.project.as_ref()?;
5469 let dap_store = project.read(cx).dap_store();
5470 let mut scenarios = vec![];
5471 let resolved_tasks = resolved_tasks.as_ref()?;
5472 let buffer = buffer.read(cx);
5473 let language = buffer.language()?;
5474 let file = buffer.file();
5475 let debug_adapter =
5476 language_settings(language.name().into(), file, cx)
5477 .debuggers
5478 .first()
5479 .map(SharedString::from)
5480 .or_else(|| {
5481 language
5482 .config()
5483 .debuggers
5484 .first()
5485 .map(SharedString::from)
5486 })?;
5487
5488 dap_store.update(cx, |dap_store, cx| {
5489 for (_, task) in &resolved_tasks.templates {
5490 if let Some(scenario) = dap_store
5491 .debug_scenario_for_build_task(
5492 task.original_task().clone(),
5493 debug_adapter.clone().into(),
5494 task.display_label().to_owned().into(),
5495 cx,
5496 )
5497 {
5498 scenarios.push(scenario);
5499 }
5500 }
5501 });
5502 Some(scenarios)
5503 })
5504 .unwrap_or_default()
5505 } else {
5506 vec![]
5507 }
5508 })?;
5509 let spawn_straight_away = quick_launch
5510 && resolved_tasks
5511 .as_ref()
5512 .map_or(false, |tasks| tasks.templates.len() == 1)
5513 && code_actions
5514 .as_ref()
5515 .map_or(true, |actions| actions.is_empty())
5516 && debug_scenarios.is_empty();
5517 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5518 crate::hover_popover::hide_hover(editor, cx);
5519 *editor.context_menu.borrow_mut() =
5520 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5521 buffer,
5522 actions: CodeActionContents::new(
5523 resolved_tasks,
5524 code_actions,
5525 debug_scenarios,
5526 task_context.unwrap_or_default(),
5527 ),
5528 selected_item: Default::default(),
5529 scroll_handle: UniformListScrollHandle::default(),
5530 deployed_from_indicator,
5531 }));
5532 if spawn_straight_away {
5533 if let Some(task) = editor.confirm_code_action(
5534 &ConfirmCodeAction { item_ix: Some(0) },
5535 window,
5536 cx,
5537 ) {
5538 cx.notify();
5539 return task;
5540 }
5541 }
5542 cx.notify();
5543 Task::ready(Ok(()))
5544 }) {
5545 task.await
5546 } else {
5547 Ok(())
5548 }
5549 }))
5550 } else {
5551 Some(Task::ready(Ok(())))
5552 }
5553 })?;
5554 if let Some(task) = spawned_test_task {
5555 task.await?;
5556 }
5557
5558 anyhow::Ok(())
5559 })
5560 .detach_and_log_err(cx);
5561 }
5562
5563 pub fn confirm_code_action(
5564 &mut self,
5565 action: &ConfirmCodeAction,
5566 window: &mut Window,
5567 cx: &mut Context<Self>,
5568 ) -> Option<Task<Result<()>>> {
5569 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5570
5571 let actions_menu =
5572 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5573 menu
5574 } else {
5575 return None;
5576 };
5577
5578 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5579 let action = actions_menu.actions.get(action_ix)?;
5580 let title = action.label();
5581 let buffer = actions_menu.buffer;
5582 let workspace = self.workspace()?;
5583
5584 match action {
5585 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5586 workspace.update(cx, |workspace, cx| {
5587 workspace.schedule_resolved_task(
5588 task_source_kind,
5589 resolved_task,
5590 false,
5591 window,
5592 cx,
5593 );
5594
5595 Some(Task::ready(Ok(())))
5596 })
5597 }
5598 CodeActionsItem::CodeAction {
5599 excerpt_id,
5600 action,
5601 provider,
5602 } => {
5603 let apply_code_action =
5604 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5605 let workspace = workspace.downgrade();
5606 Some(cx.spawn_in(window, async move |editor, cx| {
5607 let project_transaction = apply_code_action.await?;
5608 Self::open_project_transaction(
5609 &editor,
5610 workspace,
5611 project_transaction,
5612 title,
5613 cx,
5614 )
5615 .await
5616 }))
5617 }
5618 CodeActionsItem::DebugScenario(scenario) => {
5619 let context = actions_menu.actions.context.clone();
5620
5621 workspace.update(cx, |workspace, cx| {
5622 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5623 });
5624 Some(Task::ready(Ok(())))
5625 }
5626 }
5627 }
5628
5629 pub async fn open_project_transaction(
5630 this: &WeakEntity<Editor>,
5631 workspace: WeakEntity<Workspace>,
5632 transaction: ProjectTransaction,
5633 title: String,
5634 cx: &mut AsyncWindowContext,
5635 ) -> Result<()> {
5636 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5637 cx.update(|_, cx| {
5638 entries.sort_unstable_by_key(|(buffer, _)| {
5639 buffer.read(cx).file().map(|f| f.path().clone())
5640 });
5641 })?;
5642
5643 // If the project transaction's edits are all contained within this editor, then
5644 // avoid opening a new editor to display them.
5645
5646 if let Some((buffer, transaction)) = entries.first() {
5647 if entries.len() == 1 {
5648 let excerpt = this.update(cx, |editor, cx| {
5649 editor
5650 .buffer()
5651 .read(cx)
5652 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5653 })?;
5654 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5655 if excerpted_buffer == *buffer {
5656 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5657 let excerpt_range = excerpt_range.to_offset(buffer);
5658 buffer
5659 .edited_ranges_for_transaction::<usize>(transaction)
5660 .all(|range| {
5661 excerpt_range.start <= range.start
5662 && excerpt_range.end >= range.end
5663 })
5664 })?;
5665
5666 if all_edits_within_excerpt {
5667 return Ok(());
5668 }
5669 }
5670 }
5671 }
5672 } else {
5673 return Ok(());
5674 }
5675
5676 let mut ranges_to_highlight = Vec::new();
5677 let excerpt_buffer = cx.new(|cx| {
5678 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5679 for (buffer_handle, transaction) in &entries {
5680 let edited_ranges = buffer_handle
5681 .read(cx)
5682 .edited_ranges_for_transaction::<Point>(transaction)
5683 .collect::<Vec<_>>();
5684 let (ranges, _) = multibuffer.set_excerpts_for_path(
5685 PathKey::for_buffer(buffer_handle, cx),
5686 buffer_handle.clone(),
5687 edited_ranges,
5688 DEFAULT_MULTIBUFFER_CONTEXT,
5689 cx,
5690 );
5691
5692 ranges_to_highlight.extend(ranges);
5693 }
5694 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5695 multibuffer
5696 })?;
5697
5698 workspace.update_in(cx, |workspace, window, cx| {
5699 let project = workspace.project().clone();
5700 let editor =
5701 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5702 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5703 editor.update(cx, |editor, cx| {
5704 editor.highlight_background::<Self>(
5705 &ranges_to_highlight,
5706 |theme| theme.editor_highlighted_line_background,
5707 cx,
5708 );
5709 });
5710 })?;
5711
5712 Ok(())
5713 }
5714
5715 pub fn clear_code_action_providers(&mut self) {
5716 self.code_action_providers.clear();
5717 self.available_code_actions.take();
5718 }
5719
5720 pub fn add_code_action_provider(
5721 &mut self,
5722 provider: Rc<dyn CodeActionProvider>,
5723 window: &mut Window,
5724 cx: &mut Context<Self>,
5725 ) {
5726 if self
5727 .code_action_providers
5728 .iter()
5729 .any(|existing_provider| existing_provider.id() == provider.id())
5730 {
5731 return;
5732 }
5733
5734 self.code_action_providers.push(provider);
5735 self.refresh_code_actions(window, cx);
5736 }
5737
5738 pub fn remove_code_action_provider(
5739 &mut self,
5740 id: Arc<str>,
5741 window: &mut Window,
5742 cx: &mut Context<Self>,
5743 ) {
5744 self.code_action_providers
5745 .retain(|provider| provider.id() != id);
5746 self.refresh_code_actions(window, cx);
5747 }
5748
5749 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5750 let newest_selection = self.selections.newest_anchor().clone();
5751 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5752 let buffer = self.buffer.read(cx);
5753 if newest_selection.head().diff_base_anchor.is_some() {
5754 return None;
5755 }
5756 let (start_buffer, start) =
5757 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5758 let (end_buffer, end) =
5759 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5760 if start_buffer != end_buffer {
5761 return None;
5762 }
5763
5764 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5765 cx.background_executor()
5766 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5767 .await;
5768
5769 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5770 let providers = this.code_action_providers.clone();
5771 let tasks = this
5772 .code_action_providers
5773 .iter()
5774 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5775 .collect::<Vec<_>>();
5776 (providers, tasks)
5777 })?;
5778
5779 let mut actions = Vec::new();
5780 for (provider, provider_actions) in
5781 providers.into_iter().zip(future::join_all(tasks).await)
5782 {
5783 if let Some(provider_actions) = provider_actions.log_err() {
5784 actions.extend(provider_actions.into_iter().map(|action| {
5785 AvailableCodeAction {
5786 excerpt_id: newest_selection.start.excerpt_id,
5787 action,
5788 provider: provider.clone(),
5789 }
5790 }));
5791 }
5792 }
5793
5794 this.update(cx, |this, cx| {
5795 this.available_code_actions = if actions.is_empty() {
5796 None
5797 } else {
5798 Some((
5799 Location {
5800 buffer: start_buffer,
5801 range: start..end,
5802 },
5803 actions.into(),
5804 ))
5805 };
5806 cx.notify();
5807 })
5808 }));
5809 None
5810 }
5811
5812 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5813 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5814 self.show_git_blame_inline = false;
5815
5816 self.show_git_blame_inline_delay_task =
5817 Some(cx.spawn_in(window, async move |this, cx| {
5818 cx.background_executor().timer(delay).await;
5819
5820 this.update(cx, |this, cx| {
5821 this.show_git_blame_inline = true;
5822 cx.notify();
5823 })
5824 .log_err();
5825 }));
5826 }
5827 }
5828
5829 fn show_blame_popover(
5830 &mut self,
5831 blame_entry: &BlameEntry,
5832 position: gpui::Point<Pixels>,
5833 cx: &mut Context<Self>,
5834 ) {
5835 if let Some(state) = &mut self.inline_blame_popover {
5836 state.hide_task.take();
5837 cx.notify();
5838 } else {
5839 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5840 let show_task = cx.spawn(async move |editor, cx| {
5841 cx.background_executor()
5842 .timer(std::time::Duration::from_millis(delay))
5843 .await;
5844 editor
5845 .update(cx, |editor, cx| {
5846 if let Some(state) = &mut editor.inline_blame_popover {
5847 state.show_task = None;
5848 cx.notify();
5849 }
5850 })
5851 .ok();
5852 });
5853 let Some(blame) = self.blame.as_ref() else {
5854 return;
5855 };
5856 let blame = blame.read(cx);
5857 let details = blame.details_for_entry(&blame_entry);
5858 let markdown = cx.new(|cx| {
5859 Markdown::new(
5860 details
5861 .as_ref()
5862 .map(|message| message.message.clone())
5863 .unwrap_or_default(),
5864 None,
5865 None,
5866 cx,
5867 )
5868 });
5869 self.inline_blame_popover = Some(InlineBlamePopover {
5870 position,
5871 show_task: Some(show_task),
5872 hide_task: None,
5873 popover_bounds: None,
5874 popover_state: InlineBlamePopoverState {
5875 scroll_handle: ScrollHandle::new(),
5876 commit_message: details,
5877 markdown,
5878 },
5879 });
5880 }
5881 }
5882
5883 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5884 if let Some(state) = &mut self.inline_blame_popover {
5885 if state.show_task.is_some() {
5886 self.inline_blame_popover.take();
5887 cx.notify();
5888 } else {
5889 let hide_task = cx.spawn(async move |editor, cx| {
5890 cx.background_executor()
5891 .timer(std::time::Duration::from_millis(100))
5892 .await;
5893 editor
5894 .update(cx, |editor, cx| {
5895 editor.inline_blame_popover.take();
5896 cx.notify();
5897 })
5898 .ok();
5899 });
5900 state.hide_task = Some(hide_task);
5901 }
5902 }
5903 }
5904
5905 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5906 if self.pending_rename.is_some() {
5907 return None;
5908 }
5909
5910 let provider = self.semantics_provider.clone()?;
5911 let buffer = self.buffer.read(cx);
5912 let newest_selection = self.selections.newest_anchor().clone();
5913 let cursor_position = newest_selection.head();
5914 let (cursor_buffer, cursor_buffer_position) =
5915 buffer.text_anchor_for_position(cursor_position, cx)?;
5916 let (tail_buffer, tail_buffer_position) =
5917 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5918 if cursor_buffer != tail_buffer {
5919 return None;
5920 }
5921
5922 let snapshot = cursor_buffer.read(cx).snapshot();
5923 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5924 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5925 if start_word_range != end_word_range {
5926 self.document_highlights_task.take();
5927 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5928 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5929 return None;
5930 }
5931
5932 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5933 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5934 cx.background_executor()
5935 .timer(Duration::from_millis(debounce))
5936 .await;
5937
5938 let highlights = if let Some(highlights) = cx
5939 .update(|cx| {
5940 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5941 })
5942 .ok()
5943 .flatten()
5944 {
5945 highlights.await.log_err()
5946 } else {
5947 None
5948 };
5949
5950 if let Some(highlights) = highlights {
5951 this.update(cx, |this, cx| {
5952 if this.pending_rename.is_some() {
5953 return;
5954 }
5955
5956 let buffer_id = cursor_position.buffer_id;
5957 let buffer = this.buffer.read(cx);
5958 if !buffer
5959 .text_anchor_for_position(cursor_position, cx)
5960 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5961 {
5962 return;
5963 }
5964
5965 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5966 let mut write_ranges = Vec::new();
5967 let mut read_ranges = Vec::new();
5968 for highlight in highlights {
5969 for (excerpt_id, excerpt_range) in
5970 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5971 {
5972 let start = highlight
5973 .range
5974 .start
5975 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5976 let end = highlight
5977 .range
5978 .end
5979 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5980 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5981 continue;
5982 }
5983
5984 let range = Anchor {
5985 buffer_id,
5986 excerpt_id,
5987 text_anchor: start,
5988 diff_base_anchor: None,
5989 }..Anchor {
5990 buffer_id,
5991 excerpt_id,
5992 text_anchor: end,
5993 diff_base_anchor: None,
5994 };
5995 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5996 write_ranges.push(range);
5997 } else {
5998 read_ranges.push(range);
5999 }
6000 }
6001 }
6002
6003 this.highlight_background::<DocumentHighlightRead>(
6004 &read_ranges,
6005 |theme| theme.editor_document_highlight_read_background,
6006 cx,
6007 );
6008 this.highlight_background::<DocumentHighlightWrite>(
6009 &write_ranges,
6010 |theme| theme.editor_document_highlight_write_background,
6011 cx,
6012 );
6013 cx.notify();
6014 })
6015 .log_err();
6016 }
6017 }));
6018 None
6019 }
6020
6021 fn prepare_highlight_query_from_selection(
6022 &mut self,
6023 cx: &mut Context<Editor>,
6024 ) -> Option<(String, Range<Anchor>)> {
6025 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6026 return None;
6027 }
6028 if !EditorSettings::get_global(cx).selection_highlight {
6029 return None;
6030 }
6031 if self.selections.count() != 1 || self.selections.line_mode {
6032 return None;
6033 }
6034 let selection = self.selections.newest::<Point>(cx);
6035 if selection.is_empty() || selection.start.row != selection.end.row {
6036 return None;
6037 }
6038 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6039 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6040 let query = multi_buffer_snapshot
6041 .text_for_range(selection_anchor_range.clone())
6042 .collect::<String>();
6043 if query.trim().is_empty() {
6044 return None;
6045 }
6046 Some((query, selection_anchor_range))
6047 }
6048
6049 fn update_selection_occurrence_highlights(
6050 &mut self,
6051 query_text: String,
6052 query_range: Range<Anchor>,
6053 multi_buffer_range_to_query: Range<Point>,
6054 use_debounce: bool,
6055 window: &mut Window,
6056 cx: &mut Context<Editor>,
6057 ) -> Task<()> {
6058 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6059 cx.spawn_in(window, async move |editor, cx| {
6060 if use_debounce {
6061 cx.background_executor()
6062 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6063 .await;
6064 }
6065 let match_task = cx.background_spawn(async move {
6066 let buffer_ranges = multi_buffer_snapshot
6067 .range_to_buffer_ranges(multi_buffer_range_to_query)
6068 .into_iter()
6069 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6070 let mut match_ranges = Vec::new();
6071 let Ok(regex) = project::search::SearchQuery::text(
6072 query_text.clone(),
6073 false,
6074 false,
6075 false,
6076 Default::default(),
6077 Default::default(),
6078 false,
6079 None,
6080 ) else {
6081 return Vec::default();
6082 };
6083 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6084 match_ranges.extend(
6085 regex
6086 .search(&buffer_snapshot, Some(search_range.clone()))
6087 .await
6088 .into_iter()
6089 .filter_map(|match_range| {
6090 let match_start = buffer_snapshot
6091 .anchor_after(search_range.start + match_range.start);
6092 let match_end = buffer_snapshot
6093 .anchor_before(search_range.start + match_range.end);
6094 let match_anchor_range = Anchor::range_in_buffer(
6095 excerpt_id,
6096 buffer_snapshot.remote_id(),
6097 match_start..match_end,
6098 );
6099 (match_anchor_range != query_range).then_some(match_anchor_range)
6100 }),
6101 );
6102 }
6103 match_ranges
6104 });
6105 let match_ranges = match_task.await;
6106 editor
6107 .update_in(cx, |editor, _, cx| {
6108 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6109 if !match_ranges.is_empty() {
6110 editor.highlight_background::<SelectedTextHighlight>(
6111 &match_ranges,
6112 |theme| theme.editor_document_highlight_bracket_background,
6113 cx,
6114 )
6115 }
6116 })
6117 .log_err();
6118 })
6119 }
6120
6121 fn refresh_selected_text_highlights(
6122 &mut self,
6123 on_buffer_edit: bool,
6124 window: &mut Window,
6125 cx: &mut Context<Editor>,
6126 ) {
6127 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6128 else {
6129 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6130 self.quick_selection_highlight_task.take();
6131 self.debounced_selection_highlight_task.take();
6132 return;
6133 };
6134 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6135 if on_buffer_edit
6136 || self
6137 .quick_selection_highlight_task
6138 .as_ref()
6139 .map_or(true, |(prev_anchor_range, _)| {
6140 prev_anchor_range != &query_range
6141 })
6142 {
6143 let multi_buffer_visible_start = self
6144 .scroll_manager
6145 .anchor()
6146 .anchor
6147 .to_point(&multi_buffer_snapshot);
6148 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6149 multi_buffer_visible_start
6150 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6151 Bias::Left,
6152 );
6153 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6154 self.quick_selection_highlight_task = Some((
6155 query_range.clone(),
6156 self.update_selection_occurrence_highlights(
6157 query_text.clone(),
6158 query_range.clone(),
6159 multi_buffer_visible_range,
6160 false,
6161 window,
6162 cx,
6163 ),
6164 ));
6165 }
6166 if on_buffer_edit
6167 || self
6168 .debounced_selection_highlight_task
6169 .as_ref()
6170 .map_or(true, |(prev_anchor_range, _)| {
6171 prev_anchor_range != &query_range
6172 })
6173 {
6174 let multi_buffer_start = multi_buffer_snapshot
6175 .anchor_before(0)
6176 .to_point(&multi_buffer_snapshot);
6177 let multi_buffer_end = multi_buffer_snapshot
6178 .anchor_after(multi_buffer_snapshot.len())
6179 .to_point(&multi_buffer_snapshot);
6180 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6181 self.debounced_selection_highlight_task = Some((
6182 query_range.clone(),
6183 self.update_selection_occurrence_highlights(
6184 query_text,
6185 query_range,
6186 multi_buffer_full_range,
6187 true,
6188 window,
6189 cx,
6190 ),
6191 ));
6192 }
6193 }
6194
6195 pub fn refresh_inline_completion(
6196 &mut self,
6197 debounce: bool,
6198 user_requested: bool,
6199 window: &mut Window,
6200 cx: &mut Context<Self>,
6201 ) -> Option<()> {
6202 let provider = self.edit_prediction_provider()?;
6203 let cursor = self.selections.newest_anchor().head();
6204 let (buffer, cursor_buffer_position) =
6205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6206
6207 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6208 self.discard_inline_completion(false, cx);
6209 return None;
6210 }
6211
6212 if !user_requested
6213 && (!self.should_show_edit_predictions()
6214 || !self.is_focused(window)
6215 || buffer.read(cx).is_empty())
6216 {
6217 self.discard_inline_completion(false, cx);
6218 return None;
6219 }
6220
6221 self.update_visible_inline_completion(window, cx);
6222 provider.refresh(
6223 self.project.clone(),
6224 buffer,
6225 cursor_buffer_position,
6226 debounce,
6227 cx,
6228 );
6229 Some(())
6230 }
6231
6232 fn show_edit_predictions_in_menu(&self) -> bool {
6233 match self.edit_prediction_settings {
6234 EditPredictionSettings::Disabled => false,
6235 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6236 }
6237 }
6238
6239 pub fn edit_predictions_enabled(&self) -> bool {
6240 match self.edit_prediction_settings {
6241 EditPredictionSettings::Disabled => false,
6242 EditPredictionSettings::Enabled { .. } => true,
6243 }
6244 }
6245
6246 fn edit_prediction_requires_modifier(&self) -> bool {
6247 match self.edit_prediction_settings {
6248 EditPredictionSettings::Disabled => false,
6249 EditPredictionSettings::Enabled {
6250 preview_requires_modifier,
6251 ..
6252 } => preview_requires_modifier,
6253 }
6254 }
6255
6256 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6257 if self.edit_prediction_provider.is_none() {
6258 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6259 } else {
6260 let selection = self.selections.newest_anchor();
6261 let cursor = selection.head();
6262
6263 if let Some((buffer, cursor_buffer_position)) =
6264 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6265 {
6266 self.edit_prediction_settings =
6267 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6268 }
6269 }
6270 }
6271
6272 fn edit_prediction_settings_at_position(
6273 &self,
6274 buffer: &Entity<Buffer>,
6275 buffer_position: language::Anchor,
6276 cx: &App,
6277 ) -> EditPredictionSettings {
6278 if !self.mode.is_full()
6279 || !self.show_inline_completions_override.unwrap_or(true)
6280 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6281 {
6282 return EditPredictionSettings::Disabled;
6283 }
6284
6285 let buffer = buffer.read(cx);
6286
6287 let file = buffer.file();
6288
6289 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6290 return EditPredictionSettings::Disabled;
6291 };
6292
6293 let by_provider = matches!(
6294 self.menu_inline_completions_policy,
6295 MenuInlineCompletionsPolicy::ByProvider
6296 );
6297
6298 let show_in_menu = by_provider
6299 && self
6300 .edit_prediction_provider
6301 .as_ref()
6302 .map_or(false, |provider| {
6303 provider.provider.show_completions_in_menu()
6304 });
6305
6306 let preview_requires_modifier =
6307 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6308
6309 EditPredictionSettings::Enabled {
6310 show_in_menu,
6311 preview_requires_modifier,
6312 }
6313 }
6314
6315 fn should_show_edit_predictions(&self) -> bool {
6316 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6317 }
6318
6319 pub fn edit_prediction_preview_is_active(&self) -> bool {
6320 matches!(
6321 self.edit_prediction_preview,
6322 EditPredictionPreview::Active { .. }
6323 )
6324 }
6325
6326 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6327 let cursor = self.selections.newest_anchor().head();
6328 if let Some((buffer, cursor_position)) =
6329 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6330 {
6331 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6332 } else {
6333 false
6334 }
6335 }
6336
6337 pub fn supports_minimap(&self, cx: &App) -> bool {
6338 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6339 }
6340
6341 fn edit_predictions_enabled_in_buffer(
6342 &self,
6343 buffer: &Entity<Buffer>,
6344 buffer_position: language::Anchor,
6345 cx: &App,
6346 ) -> bool {
6347 maybe!({
6348 if self.read_only(cx) {
6349 return Some(false);
6350 }
6351 let provider = self.edit_prediction_provider()?;
6352 if !provider.is_enabled(&buffer, buffer_position, cx) {
6353 return Some(false);
6354 }
6355 let buffer = buffer.read(cx);
6356 let Some(file) = buffer.file() else {
6357 return Some(true);
6358 };
6359 let settings = all_language_settings(Some(file), cx);
6360 Some(settings.edit_predictions_enabled_for_file(file, cx))
6361 })
6362 .unwrap_or(false)
6363 }
6364
6365 fn cycle_inline_completion(
6366 &mut self,
6367 direction: Direction,
6368 window: &mut Window,
6369 cx: &mut Context<Self>,
6370 ) -> Option<()> {
6371 let provider = self.edit_prediction_provider()?;
6372 let cursor = self.selections.newest_anchor().head();
6373 let (buffer, cursor_buffer_position) =
6374 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6375 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6376 return None;
6377 }
6378
6379 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6380 self.update_visible_inline_completion(window, cx);
6381
6382 Some(())
6383 }
6384
6385 pub fn show_inline_completion(
6386 &mut self,
6387 _: &ShowEditPrediction,
6388 window: &mut Window,
6389 cx: &mut Context<Self>,
6390 ) {
6391 if !self.has_active_inline_completion() {
6392 self.refresh_inline_completion(false, true, window, cx);
6393 return;
6394 }
6395
6396 self.update_visible_inline_completion(window, cx);
6397 }
6398
6399 pub fn display_cursor_names(
6400 &mut self,
6401 _: &DisplayCursorNames,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) {
6405 self.show_cursor_names(window, cx);
6406 }
6407
6408 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6409 self.show_cursor_names = true;
6410 cx.notify();
6411 cx.spawn_in(window, async move |this, cx| {
6412 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6413 this.update(cx, |this, cx| {
6414 this.show_cursor_names = false;
6415 cx.notify()
6416 })
6417 .ok()
6418 })
6419 .detach();
6420 }
6421
6422 pub fn next_edit_prediction(
6423 &mut self,
6424 _: &NextEditPrediction,
6425 window: &mut Window,
6426 cx: &mut Context<Self>,
6427 ) {
6428 if self.has_active_inline_completion() {
6429 self.cycle_inline_completion(Direction::Next, window, cx);
6430 } else {
6431 let is_copilot_disabled = self
6432 .refresh_inline_completion(false, true, window, cx)
6433 .is_none();
6434 if is_copilot_disabled {
6435 cx.propagate();
6436 }
6437 }
6438 }
6439
6440 pub fn previous_edit_prediction(
6441 &mut self,
6442 _: &PreviousEditPrediction,
6443 window: &mut Window,
6444 cx: &mut Context<Self>,
6445 ) {
6446 if self.has_active_inline_completion() {
6447 self.cycle_inline_completion(Direction::Prev, window, cx);
6448 } else {
6449 let is_copilot_disabled = self
6450 .refresh_inline_completion(false, true, window, cx)
6451 .is_none();
6452 if is_copilot_disabled {
6453 cx.propagate();
6454 }
6455 }
6456 }
6457
6458 pub fn accept_edit_prediction(
6459 &mut self,
6460 _: &AcceptEditPrediction,
6461 window: &mut Window,
6462 cx: &mut Context<Self>,
6463 ) {
6464 if self.show_edit_predictions_in_menu() {
6465 self.hide_context_menu(window, cx);
6466 }
6467
6468 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6469 return;
6470 };
6471
6472 self.report_inline_completion_event(
6473 active_inline_completion.completion_id.clone(),
6474 true,
6475 cx,
6476 );
6477
6478 match &active_inline_completion.completion {
6479 InlineCompletion::Move { target, .. } => {
6480 let target = *target;
6481
6482 if let Some(position_map) = &self.last_position_map {
6483 if position_map
6484 .visible_row_range
6485 .contains(&target.to_display_point(&position_map.snapshot).row())
6486 || !self.edit_prediction_requires_modifier()
6487 {
6488 self.unfold_ranges(&[target..target], true, false, cx);
6489 // Note that this is also done in vim's handler of the Tab action.
6490 self.change_selections(
6491 Some(Autoscroll::newest()),
6492 window,
6493 cx,
6494 |selections| {
6495 selections.select_anchor_ranges([target..target]);
6496 },
6497 );
6498 self.clear_row_highlights::<EditPredictionPreview>();
6499
6500 self.edit_prediction_preview
6501 .set_previous_scroll_position(None);
6502 } else {
6503 self.edit_prediction_preview
6504 .set_previous_scroll_position(Some(
6505 position_map.snapshot.scroll_anchor,
6506 ));
6507
6508 self.highlight_rows::<EditPredictionPreview>(
6509 target..target,
6510 cx.theme().colors().editor_highlighted_line_background,
6511 RowHighlightOptions {
6512 autoscroll: true,
6513 ..Default::default()
6514 },
6515 cx,
6516 );
6517 self.request_autoscroll(Autoscroll::fit(), cx);
6518 }
6519 }
6520 }
6521 InlineCompletion::Edit { edits, .. } => {
6522 if let Some(provider) = self.edit_prediction_provider() {
6523 provider.accept(cx);
6524 }
6525
6526 // Store the transaction ID and selections before applying the edit
6527 let transaction_id_prev =
6528 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
6529
6530 let snapshot = self.buffer.read(cx).snapshot(cx);
6531 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6532
6533 self.buffer.update(cx, |buffer, cx| {
6534 buffer.edit(edits.iter().cloned(), None, cx)
6535 });
6536
6537 self.change_selections(None, window, cx, |s| {
6538 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6539 });
6540
6541 let selections = self.selections.disjoint_anchors();
6542 if let Some(transaction_id_now) =
6543 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
6544 {
6545 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6546 if has_new_transaction {
6547 self.selection_history
6548 .insert_transaction(transaction_id_now, selections);
6549 }
6550 }
6551
6552 self.update_visible_inline_completion(window, cx);
6553 if self.active_inline_completion.is_none() {
6554 self.refresh_inline_completion(true, true, window, cx);
6555 }
6556
6557 cx.notify();
6558 }
6559 }
6560
6561 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6562 }
6563
6564 pub fn accept_partial_inline_completion(
6565 &mut self,
6566 _: &AcceptPartialEditPrediction,
6567 window: &mut Window,
6568 cx: &mut Context<Self>,
6569 ) {
6570 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6571 return;
6572 };
6573 if self.selections.count() != 1 {
6574 return;
6575 }
6576
6577 self.report_inline_completion_event(
6578 active_inline_completion.completion_id.clone(),
6579 true,
6580 cx,
6581 );
6582
6583 match &active_inline_completion.completion {
6584 InlineCompletion::Move { target, .. } => {
6585 let target = *target;
6586 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6587 selections.select_anchor_ranges([target..target]);
6588 });
6589 }
6590 InlineCompletion::Edit { edits, .. } => {
6591 // Find an insertion that starts at the cursor position.
6592 let snapshot = self.buffer.read(cx).snapshot(cx);
6593 let cursor_offset = self.selections.newest::<usize>(cx).head();
6594 let insertion = edits.iter().find_map(|(range, text)| {
6595 let range = range.to_offset(&snapshot);
6596 if range.is_empty() && range.start == cursor_offset {
6597 Some(text)
6598 } else {
6599 None
6600 }
6601 });
6602
6603 if let Some(text) = insertion {
6604 let mut partial_completion = text
6605 .chars()
6606 .by_ref()
6607 .take_while(|c| c.is_alphabetic())
6608 .collect::<String>();
6609 if partial_completion.is_empty() {
6610 partial_completion = text
6611 .chars()
6612 .by_ref()
6613 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6614 .collect::<String>();
6615 }
6616
6617 cx.emit(EditorEvent::InputHandled {
6618 utf16_range_to_replace: None,
6619 text: partial_completion.clone().into(),
6620 });
6621
6622 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6623
6624 self.refresh_inline_completion(true, true, window, cx);
6625 cx.notify();
6626 } else {
6627 self.accept_edit_prediction(&Default::default(), window, cx);
6628 }
6629 }
6630 }
6631 }
6632
6633 fn discard_inline_completion(
6634 &mut self,
6635 should_report_inline_completion_event: bool,
6636 cx: &mut Context<Self>,
6637 ) -> bool {
6638 if should_report_inline_completion_event {
6639 let completion_id = self
6640 .active_inline_completion
6641 .as_ref()
6642 .and_then(|active_completion| active_completion.completion_id.clone());
6643
6644 self.report_inline_completion_event(completion_id, false, cx);
6645 }
6646
6647 if let Some(provider) = self.edit_prediction_provider() {
6648 provider.discard(cx);
6649 }
6650
6651 self.take_active_inline_completion(cx)
6652 }
6653
6654 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6655 let Some(provider) = self.edit_prediction_provider() else {
6656 return;
6657 };
6658
6659 let Some((_, buffer, _)) = self
6660 .buffer
6661 .read(cx)
6662 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6663 else {
6664 return;
6665 };
6666
6667 let extension = buffer
6668 .read(cx)
6669 .file()
6670 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6671
6672 let event_type = match accepted {
6673 true => "Edit Prediction Accepted",
6674 false => "Edit Prediction Discarded",
6675 };
6676 telemetry::event!(
6677 event_type,
6678 provider = provider.name(),
6679 prediction_id = id,
6680 suggestion_accepted = accepted,
6681 file_extension = extension,
6682 );
6683 }
6684
6685 pub fn has_active_inline_completion(&self) -> bool {
6686 self.active_inline_completion.is_some()
6687 }
6688
6689 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6690 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6691 return false;
6692 };
6693
6694 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6695 self.clear_highlights::<InlineCompletionHighlight>(cx);
6696 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6697 true
6698 }
6699
6700 /// Returns true when we're displaying the edit prediction popover below the cursor
6701 /// like we are not previewing and the LSP autocomplete menu is visible
6702 /// or we are in `when_holding_modifier` mode.
6703 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6704 if self.edit_prediction_preview_is_active()
6705 || !self.show_edit_predictions_in_menu()
6706 || !self.edit_predictions_enabled()
6707 {
6708 return false;
6709 }
6710
6711 if self.has_visible_completions_menu() {
6712 return true;
6713 }
6714
6715 has_completion && self.edit_prediction_requires_modifier()
6716 }
6717
6718 fn handle_modifiers_changed(
6719 &mut self,
6720 modifiers: Modifiers,
6721 position_map: &PositionMap,
6722 window: &mut Window,
6723 cx: &mut Context<Self>,
6724 ) {
6725 if self.show_edit_predictions_in_menu() {
6726 self.update_edit_prediction_preview(&modifiers, window, cx);
6727 }
6728
6729 self.update_selection_mode(&modifiers, position_map, window, cx);
6730
6731 let mouse_position = window.mouse_position();
6732 if !position_map.text_hitbox.is_hovered(window) {
6733 return;
6734 }
6735
6736 self.update_hovered_link(
6737 position_map.point_for_position(mouse_position),
6738 &position_map.snapshot,
6739 modifiers,
6740 window,
6741 cx,
6742 )
6743 }
6744
6745 fn update_selection_mode(
6746 &mut self,
6747 modifiers: &Modifiers,
6748 position_map: &PositionMap,
6749 window: &mut Window,
6750 cx: &mut Context<Self>,
6751 ) {
6752 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6753 return;
6754 }
6755
6756 let mouse_position = window.mouse_position();
6757 let point_for_position = position_map.point_for_position(mouse_position);
6758 let position = point_for_position.previous_valid;
6759
6760 self.select(
6761 SelectPhase::BeginColumnar {
6762 position,
6763 reset: false,
6764 goal_column: point_for_position.exact_unclipped.column(),
6765 },
6766 window,
6767 cx,
6768 );
6769 }
6770
6771 fn update_edit_prediction_preview(
6772 &mut self,
6773 modifiers: &Modifiers,
6774 window: &mut Window,
6775 cx: &mut Context<Self>,
6776 ) {
6777 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6778 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6779 return;
6780 };
6781
6782 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6783 if matches!(
6784 self.edit_prediction_preview,
6785 EditPredictionPreview::Inactive { .. }
6786 ) {
6787 self.edit_prediction_preview = EditPredictionPreview::Active {
6788 previous_scroll_position: None,
6789 since: Instant::now(),
6790 };
6791
6792 self.update_visible_inline_completion(window, cx);
6793 cx.notify();
6794 }
6795 } else if let EditPredictionPreview::Active {
6796 previous_scroll_position,
6797 since,
6798 } = self.edit_prediction_preview
6799 {
6800 if let (Some(previous_scroll_position), Some(position_map)) =
6801 (previous_scroll_position, self.last_position_map.as_ref())
6802 {
6803 self.set_scroll_position(
6804 previous_scroll_position
6805 .scroll_position(&position_map.snapshot.display_snapshot),
6806 window,
6807 cx,
6808 );
6809 }
6810
6811 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6812 released_too_fast: since.elapsed() < Duration::from_millis(200),
6813 };
6814 self.clear_row_highlights::<EditPredictionPreview>();
6815 self.update_visible_inline_completion(window, cx);
6816 cx.notify();
6817 }
6818 }
6819
6820 fn update_visible_inline_completion(
6821 &mut self,
6822 _window: &mut Window,
6823 cx: &mut Context<Self>,
6824 ) -> Option<()> {
6825 let selection = self.selections.newest_anchor();
6826 let cursor = selection.head();
6827 let multibuffer = self.buffer.read(cx).snapshot(cx);
6828 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6829 let excerpt_id = cursor.excerpt_id;
6830
6831 let show_in_menu = self.show_edit_predictions_in_menu();
6832 let completions_menu_has_precedence = !show_in_menu
6833 && (self.context_menu.borrow().is_some()
6834 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6835
6836 if completions_menu_has_precedence
6837 || !offset_selection.is_empty()
6838 || self
6839 .active_inline_completion
6840 .as_ref()
6841 .map_or(false, |completion| {
6842 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6843 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6844 !invalidation_range.contains(&offset_selection.head())
6845 })
6846 {
6847 self.discard_inline_completion(false, cx);
6848 return None;
6849 }
6850
6851 self.take_active_inline_completion(cx);
6852 let Some(provider) = self.edit_prediction_provider() else {
6853 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6854 return None;
6855 };
6856
6857 let (buffer, cursor_buffer_position) =
6858 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6859
6860 self.edit_prediction_settings =
6861 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6862
6863 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6864
6865 if self.edit_prediction_indent_conflict {
6866 let cursor_point = cursor.to_point(&multibuffer);
6867
6868 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6869
6870 if let Some((_, indent)) = indents.iter().next() {
6871 if indent.len == cursor_point.column {
6872 self.edit_prediction_indent_conflict = false;
6873 }
6874 }
6875 }
6876
6877 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6878 let edits = inline_completion
6879 .edits
6880 .into_iter()
6881 .flat_map(|(range, new_text)| {
6882 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6883 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6884 Some((start..end, new_text))
6885 })
6886 .collect::<Vec<_>>();
6887 if edits.is_empty() {
6888 return None;
6889 }
6890
6891 let first_edit_start = edits.first().unwrap().0.start;
6892 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6893 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6894
6895 let last_edit_end = edits.last().unwrap().0.end;
6896 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6897 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6898
6899 let cursor_row = cursor.to_point(&multibuffer).row;
6900
6901 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6902
6903 let mut inlay_ids = Vec::new();
6904 let invalidation_row_range;
6905 let move_invalidation_row_range = if cursor_row < edit_start_row {
6906 Some(cursor_row..edit_end_row)
6907 } else if cursor_row > edit_end_row {
6908 Some(edit_start_row..cursor_row)
6909 } else {
6910 None
6911 };
6912 let is_move =
6913 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6914 let completion = if is_move {
6915 invalidation_row_range =
6916 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6917 let target = first_edit_start;
6918 InlineCompletion::Move { target, snapshot }
6919 } else {
6920 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6921 && !self.inline_completions_hidden_for_vim_mode;
6922
6923 if show_completions_in_buffer {
6924 if edits
6925 .iter()
6926 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6927 {
6928 let mut inlays = Vec::new();
6929 for (range, new_text) in &edits {
6930 let inlay = Inlay::inline_completion(
6931 post_inc(&mut self.next_inlay_id),
6932 range.start,
6933 new_text.as_str(),
6934 );
6935 inlay_ids.push(inlay.id);
6936 inlays.push(inlay);
6937 }
6938
6939 self.splice_inlays(&[], inlays, cx);
6940 } else {
6941 let background_color = cx.theme().status().deleted_background;
6942 self.highlight_text::<InlineCompletionHighlight>(
6943 edits.iter().map(|(range, _)| range.clone()).collect(),
6944 HighlightStyle {
6945 background_color: Some(background_color),
6946 ..Default::default()
6947 },
6948 cx,
6949 );
6950 }
6951 }
6952
6953 invalidation_row_range = edit_start_row..edit_end_row;
6954
6955 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6956 if provider.show_tab_accept_marker() {
6957 EditDisplayMode::TabAccept
6958 } else {
6959 EditDisplayMode::Inline
6960 }
6961 } else {
6962 EditDisplayMode::DiffPopover
6963 };
6964
6965 InlineCompletion::Edit {
6966 edits,
6967 edit_preview: inline_completion.edit_preview,
6968 display_mode,
6969 snapshot,
6970 }
6971 };
6972
6973 let invalidation_range = multibuffer
6974 .anchor_before(Point::new(invalidation_row_range.start, 0))
6975 ..multibuffer.anchor_after(Point::new(
6976 invalidation_row_range.end,
6977 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6978 ));
6979
6980 self.stale_inline_completion_in_menu = None;
6981 self.active_inline_completion = Some(InlineCompletionState {
6982 inlay_ids,
6983 completion,
6984 completion_id: inline_completion.id,
6985 invalidation_range,
6986 });
6987
6988 cx.notify();
6989
6990 Some(())
6991 }
6992
6993 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6994 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6995 }
6996
6997 fn clear_tasks(&mut self) {
6998 self.tasks.clear()
6999 }
7000
7001 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7002 if self.tasks.insert(key, value).is_some() {
7003 // This case should hopefully be rare, but just in case...
7004 log::error!(
7005 "multiple different run targets found on a single line, only the last target will be rendered"
7006 )
7007 }
7008 }
7009
7010 /// Get all display points of breakpoints that will be rendered within editor
7011 ///
7012 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7013 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7014 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7015 fn active_breakpoints(
7016 &self,
7017 range: Range<DisplayRow>,
7018 window: &mut Window,
7019 cx: &mut Context<Self>,
7020 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7021 let mut breakpoint_display_points = HashMap::default();
7022
7023 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7024 return breakpoint_display_points;
7025 };
7026
7027 let snapshot = self.snapshot(window, cx);
7028
7029 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7030 let Some(project) = self.project.as_ref() else {
7031 return breakpoint_display_points;
7032 };
7033
7034 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7035 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7036
7037 for (buffer_snapshot, range, excerpt_id) in
7038 multi_buffer_snapshot.range_to_buffer_ranges(range)
7039 {
7040 let Some(buffer) = project.read_with(cx, |this, cx| {
7041 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7042 }) else {
7043 continue;
7044 };
7045 let breakpoints = breakpoint_store.read(cx).breakpoints(
7046 &buffer,
7047 Some(
7048 buffer_snapshot.anchor_before(range.start)
7049 ..buffer_snapshot.anchor_after(range.end),
7050 ),
7051 buffer_snapshot,
7052 cx,
7053 );
7054 for (breakpoint, state) in breakpoints {
7055 let multi_buffer_anchor =
7056 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7057 let position = multi_buffer_anchor
7058 .to_point(&multi_buffer_snapshot)
7059 .to_display_point(&snapshot);
7060
7061 breakpoint_display_points.insert(
7062 position.row(),
7063 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7064 );
7065 }
7066 }
7067
7068 breakpoint_display_points
7069 }
7070
7071 fn breakpoint_context_menu(
7072 &self,
7073 anchor: Anchor,
7074 window: &mut Window,
7075 cx: &mut Context<Self>,
7076 ) -> Entity<ui::ContextMenu> {
7077 let weak_editor = cx.weak_entity();
7078 let focus_handle = self.focus_handle(cx);
7079
7080 let row = self
7081 .buffer
7082 .read(cx)
7083 .snapshot(cx)
7084 .summary_for_anchor::<Point>(&anchor)
7085 .row;
7086
7087 let breakpoint = self
7088 .breakpoint_at_row(row, window, cx)
7089 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7090
7091 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7092 "Edit Log Breakpoint"
7093 } else {
7094 "Set Log Breakpoint"
7095 };
7096
7097 let condition_breakpoint_msg = if breakpoint
7098 .as_ref()
7099 .is_some_and(|bp| bp.1.condition.is_some())
7100 {
7101 "Edit Condition Breakpoint"
7102 } else {
7103 "Set Condition Breakpoint"
7104 };
7105
7106 let hit_condition_breakpoint_msg = if breakpoint
7107 .as_ref()
7108 .is_some_and(|bp| bp.1.hit_condition.is_some())
7109 {
7110 "Edit Hit Condition Breakpoint"
7111 } else {
7112 "Set Hit Condition Breakpoint"
7113 };
7114
7115 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7116 "Unset Breakpoint"
7117 } else {
7118 "Set Breakpoint"
7119 };
7120
7121 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7122 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7123
7124 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7125 BreakpointState::Enabled => Some("Disable"),
7126 BreakpointState::Disabled => Some("Enable"),
7127 });
7128
7129 let (anchor, breakpoint) =
7130 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7131
7132 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7133 menu.on_blur_subscription(Subscription::new(|| {}))
7134 .context(focus_handle)
7135 .when(run_to_cursor, |this| {
7136 let weak_editor = weak_editor.clone();
7137 this.entry("Run to cursor", None, move |window, cx| {
7138 weak_editor
7139 .update(cx, |editor, cx| {
7140 editor.change_selections(None, window, cx, |s| {
7141 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7142 });
7143 })
7144 .ok();
7145
7146 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7147 })
7148 .separator()
7149 })
7150 .when_some(toggle_state_msg, |this, msg| {
7151 this.entry(msg, None, {
7152 let weak_editor = weak_editor.clone();
7153 let breakpoint = breakpoint.clone();
7154 move |_window, cx| {
7155 weak_editor
7156 .update(cx, |this, cx| {
7157 this.edit_breakpoint_at_anchor(
7158 anchor,
7159 breakpoint.as_ref().clone(),
7160 BreakpointEditAction::InvertState,
7161 cx,
7162 );
7163 })
7164 .log_err();
7165 }
7166 })
7167 })
7168 .entry(set_breakpoint_msg, None, {
7169 let weak_editor = weak_editor.clone();
7170 let breakpoint = breakpoint.clone();
7171 move |_window, cx| {
7172 weak_editor
7173 .update(cx, |this, cx| {
7174 this.edit_breakpoint_at_anchor(
7175 anchor,
7176 breakpoint.as_ref().clone(),
7177 BreakpointEditAction::Toggle,
7178 cx,
7179 );
7180 })
7181 .log_err();
7182 }
7183 })
7184 .entry(log_breakpoint_msg, None, {
7185 let breakpoint = breakpoint.clone();
7186 let weak_editor = weak_editor.clone();
7187 move |window, cx| {
7188 weak_editor
7189 .update(cx, |this, cx| {
7190 this.add_edit_breakpoint_block(
7191 anchor,
7192 breakpoint.as_ref(),
7193 BreakpointPromptEditAction::Log,
7194 window,
7195 cx,
7196 );
7197 })
7198 .log_err();
7199 }
7200 })
7201 .entry(condition_breakpoint_msg, None, {
7202 let breakpoint = breakpoint.clone();
7203 let weak_editor = weak_editor.clone();
7204 move |window, cx| {
7205 weak_editor
7206 .update(cx, |this, cx| {
7207 this.add_edit_breakpoint_block(
7208 anchor,
7209 breakpoint.as_ref(),
7210 BreakpointPromptEditAction::Condition,
7211 window,
7212 cx,
7213 );
7214 })
7215 .log_err();
7216 }
7217 })
7218 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7219 weak_editor
7220 .update(cx, |this, cx| {
7221 this.add_edit_breakpoint_block(
7222 anchor,
7223 breakpoint.as_ref(),
7224 BreakpointPromptEditAction::HitCondition,
7225 window,
7226 cx,
7227 );
7228 })
7229 .log_err();
7230 })
7231 })
7232 }
7233
7234 fn render_breakpoint(
7235 &self,
7236 position: Anchor,
7237 row: DisplayRow,
7238 breakpoint: &Breakpoint,
7239 state: Option<BreakpointSessionState>,
7240 cx: &mut Context<Self>,
7241 ) -> IconButton {
7242 let is_rejected = state.is_some_and(|s| !s.verified);
7243 // Is it a breakpoint that shows up when hovering over gutter?
7244 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7245 (false, false),
7246 |PhantomBreakpointIndicator {
7247 is_active,
7248 display_row,
7249 collides_with_existing_breakpoint,
7250 }| {
7251 (
7252 is_active && display_row == row,
7253 collides_with_existing_breakpoint,
7254 )
7255 },
7256 );
7257
7258 let (color, icon) = {
7259 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7260 (false, false) => ui::IconName::DebugBreakpoint,
7261 (true, false) => ui::IconName::DebugLogBreakpoint,
7262 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7263 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7264 };
7265
7266 let color = if is_phantom {
7267 Color::Hint
7268 } else if is_rejected {
7269 Color::Disabled
7270 } else {
7271 Color::Debugger
7272 };
7273
7274 (color, icon)
7275 };
7276
7277 let breakpoint = Arc::from(breakpoint.clone());
7278
7279 let alt_as_text = gpui::Keystroke {
7280 modifiers: Modifiers::secondary_key(),
7281 ..Default::default()
7282 };
7283 let primary_action_text = if breakpoint.is_disabled() {
7284 "Enable breakpoint"
7285 } else if is_phantom && !collides_with_existing {
7286 "Set breakpoint"
7287 } else {
7288 "Unset breakpoint"
7289 };
7290 let focus_handle = self.focus_handle.clone();
7291
7292 let meta = if is_rejected {
7293 SharedString::from("No executable code is associated with this line.")
7294 } else if collides_with_existing && !breakpoint.is_disabled() {
7295 SharedString::from(format!(
7296 "{alt_as_text}-click to disable,\nright-click for more options."
7297 ))
7298 } else {
7299 SharedString::from("Right-click for more options.")
7300 };
7301 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7302 .icon_size(IconSize::XSmall)
7303 .size(ui::ButtonSize::None)
7304 .when(is_rejected, |this| {
7305 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7306 })
7307 .icon_color(color)
7308 .style(ButtonStyle::Transparent)
7309 .on_click(cx.listener({
7310 let breakpoint = breakpoint.clone();
7311
7312 move |editor, event: &ClickEvent, window, cx| {
7313 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7314 BreakpointEditAction::InvertState
7315 } else {
7316 BreakpointEditAction::Toggle
7317 };
7318
7319 window.focus(&editor.focus_handle(cx));
7320 editor.edit_breakpoint_at_anchor(
7321 position,
7322 breakpoint.as_ref().clone(),
7323 edit_action,
7324 cx,
7325 );
7326 }
7327 }))
7328 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7329 editor.set_breakpoint_context_menu(
7330 row,
7331 Some(position),
7332 event.down.position,
7333 window,
7334 cx,
7335 );
7336 }))
7337 .tooltip(move |window, cx| {
7338 Tooltip::with_meta_in(
7339 primary_action_text,
7340 Some(&ToggleBreakpoint),
7341 meta.clone(),
7342 &focus_handle,
7343 window,
7344 cx,
7345 )
7346 })
7347 }
7348
7349 fn build_tasks_context(
7350 project: &Entity<Project>,
7351 buffer: &Entity<Buffer>,
7352 buffer_row: u32,
7353 tasks: &Arc<RunnableTasks>,
7354 cx: &mut Context<Self>,
7355 ) -> Task<Option<task::TaskContext>> {
7356 let position = Point::new(buffer_row, tasks.column);
7357 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7358 let location = Location {
7359 buffer: buffer.clone(),
7360 range: range_start..range_start,
7361 };
7362 // Fill in the environmental variables from the tree-sitter captures
7363 let mut captured_task_variables = TaskVariables::default();
7364 for (capture_name, value) in tasks.extra_variables.clone() {
7365 captured_task_variables.insert(
7366 task::VariableName::Custom(capture_name.into()),
7367 value.clone(),
7368 );
7369 }
7370 project.update(cx, |project, cx| {
7371 project.task_store().update(cx, |task_store, cx| {
7372 task_store.task_context_for_location(captured_task_variables, location, cx)
7373 })
7374 })
7375 }
7376
7377 pub fn spawn_nearest_task(
7378 &mut self,
7379 action: &SpawnNearestTask,
7380 window: &mut Window,
7381 cx: &mut Context<Self>,
7382 ) {
7383 let Some((workspace, _)) = self.workspace.clone() else {
7384 return;
7385 };
7386 let Some(project) = self.project.clone() else {
7387 return;
7388 };
7389
7390 // Try to find a closest, enclosing node using tree-sitter that has a
7391 // task
7392 let Some((buffer, buffer_row, tasks)) = self
7393 .find_enclosing_node_task(cx)
7394 // Or find the task that's closest in row-distance.
7395 .or_else(|| self.find_closest_task(cx))
7396 else {
7397 return;
7398 };
7399
7400 let reveal_strategy = action.reveal;
7401 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7402 cx.spawn_in(window, async move |_, cx| {
7403 let context = task_context.await?;
7404 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7405
7406 let resolved = &mut resolved_task.resolved;
7407 resolved.reveal = reveal_strategy;
7408
7409 workspace
7410 .update_in(cx, |workspace, window, cx| {
7411 workspace.schedule_resolved_task(
7412 task_source_kind,
7413 resolved_task,
7414 false,
7415 window,
7416 cx,
7417 );
7418 })
7419 .ok()
7420 })
7421 .detach();
7422 }
7423
7424 fn find_closest_task(
7425 &mut self,
7426 cx: &mut Context<Self>,
7427 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7428 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7429
7430 let ((buffer_id, row), tasks) = self
7431 .tasks
7432 .iter()
7433 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7434
7435 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7436 let tasks = Arc::new(tasks.to_owned());
7437 Some((buffer, *row, tasks))
7438 }
7439
7440 fn find_enclosing_node_task(
7441 &mut self,
7442 cx: &mut Context<Self>,
7443 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7444 let snapshot = self.buffer.read(cx).snapshot(cx);
7445 let offset = self.selections.newest::<usize>(cx).head();
7446 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7447 let buffer_id = excerpt.buffer().remote_id();
7448
7449 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7450 let mut cursor = layer.node().walk();
7451
7452 while cursor.goto_first_child_for_byte(offset).is_some() {
7453 if cursor.node().end_byte() == offset {
7454 cursor.goto_next_sibling();
7455 }
7456 }
7457
7458 // Ascend to the smallest ancestor that contains the range and has a task.
7459 loop {
7460 let node = cursor.node();
7461 let node_range = node.byte_range();
7462 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7463
7464 // Check if this node contains our offset
7465 if node_range.start <= offset && node_range.end >= offset {
7466 // If it contains offset, check for task
7467 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7468 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7469 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7470 }
7471 }
7472
7473 if !cursor.goto_parent() {
7474 break;
7475 }
7476 }
7477 None
7478 }
7479
7480 fn render_run_indicator(
7481 &self,
7482 _style: &EditorStyle,
7483 is_active: bool,
7484 row: DisplayRow,
7485 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7486 cx: &mut Context<Self>,
7487 ) -> IconButton {
7488 let color = Color::Muted;
7489 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7490
7491 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7492 .shape(ui::IconButtonShape::Square)
7493 .icon_size(IconSize::XSmall)
7494 .icon_color(color)
7495 .toggle_state(is_active)
7496 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7497 let quick_launch = e.down.button == MouseButton::Left;
7498 window.focus(&editor.focus_handle(cx));
7499 editor.toggle_code_actions(
7500 &ToggleCodeActions {
7501 deployed_from_indicator: Some(row),
7502 quick_launch,
7503 },
7504 window,
7505 cx,
7506 );
7507 }))
7508 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7509 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7510 }))
7511 }
7512
7513 pub fn context_menu_visible(&self) -> bool {
7514 !self.edit_prediction_preview_is_active()
7515 && self
7516 .context_menu
7517 .borrow()
7518 .as_ref()
7519 .map_or(false, |menu| menu.visible())
7520 }
7521
7522 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7523 self.context_menu
7524 .borrow()
7525 .as_ref()
7526 .map(|menu| menu.origin())
7527 }
7528
7529 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7530 self.context_menu_options = Some(options);
7531 }
7532
7533 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7534 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7535
7536 fn render_edit_prediction_popover(
7537 &mut self,
7538 text_bounds: &Bounds<Pixels>,
7539 content_origin: gpui::Point<Pixels>,
7540 right_margin: Pixels,
7541 editor_snapshot: &EditorSnapshot,
7542 visible_row_range: Range<DisplayRow>,
7543 scroll_top: f32,
7544 scroll_bottom: f32,
7545 line_layouts: &[LineWithInvisibles],
7546 line_height: Pixels,
7547 scroll_pixel_position: gpui::Point<Pixels>,
7548 newest_selection_head: Option<DisplayPoint>,
7549 editor_width: Pixels,
7550 style: &EditorStyle,
7551 window: &mut Window,
7552 cx: &mut App,
7553 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7554 if self.mode().is_minimap() {
7555 return None;
7556 }
7557 let active_inline_completion = self.active_inline_completion.as_ref()?;
7558
7559 if self.edit_prediction_visible_in_cursor_popover(true) {
7560 return None;
7561 }
7562
7563 match &active_inline_completion.completion {
7564 InlineCompletion::Move { target, .. } => {
7565 let target_display_point = target.to_display_point(editor_snapshot);
7566
7567 if self.edit_prediction_requires_modifier() {
7568 if !self.edit_prediction_preview_is_active() {
7569 return None;
7570 }
7571
7572 self.render_edit_prediction_modifier_jump_popover(
7573 text_bounds,
7574 content_origin,
7575 visible_row_range,
7576 line_layouts,
7577 line_height,
7578 scroll_pixel_position,
7579 newest_selection_head,
7580 target_display_point,
7581 window,
7582 cx,
7583 )
7584 } else {
7585 self.render_edit_prediction_eager_jump_popover(
7586 text_bounds,
7587 content_origin,
7588 editor_snapshot,
7589 visible_row_range,
7590 scroll_top,
7591 scroll_bottom,
7592 line_height,
7593 scroll_pixel_position,
7594 target_display_point,
7595 editor_width,
7596 window,
7597 cx,
7598 )
7599 }
7600 }
7601 InlineCompletion::Edit {
7602 display_mode: EditDisplayMode::Inline,
7603 ..
7604 } => None,
7605 InlineCompletion::Edit {
7606 display_mode: EditDisplayMode::TabAccept,
7607 edits,
7608 ..
7609 } => {
7610 let range = &edits.first()?.0;
7611 let target_display_point = range.end.to_display_point(editor_snapshot);
7612
7613 self.render_edit_prediction_end_of_line_popover(
7614 "Accept",
7615 editor_snapshot,
7616 visible_row_range,
7617 target_display_point,
7618 line_height,
7619 scroll_pixel_position,
7620 content_origin,
7621 editor_width,
7622 window,
7623 cx,
7624 )
7625 }
7626 InlineCompletion::Edit {
7627 edits,
7628 edit_preview,
7629 display_mode: EditDisplayMode::DiffPopover,
7630 snapshot,
7631 } => self.render_edit_prediction_diff_popover(
7632 text_bounds,
7633 content_origin,
7634 right_margin,
7635 editor_snapshot,
7636 visible_row_range,
7637 line_layouts,
7638 line_height,
7639 scroll_pixel_position,
7640 newest_selection_head,
7641 editor_width,
7642 style,
7643 edits,
7644 edit_preview,
7645 snapshot,
7646 window,
7647 cx,
7648 ),
7649 }
7650 }
7651
7652 fn render_edit_prediction_modifier_jump_popover(
7653 &mut self,
7654 text_bounds: &Bounds<Pixels>,
7655 content_origin: gpui::Point<Pixels>,
7656 visible_row_range: Range<DisplayRow>,
7657 line_layouts: &[LineWithInvisibles],
7658 line_height: Pixels,
7659 scroll_pixel_position: gpui::Point<Pixels>,
7660 newest_selection_head: Option<DisplayPoint>,
7661 target_display_point: DisplayPoint,
7662 window: &mut Window,
7663 cx: &mut App,
7664 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7665 let scrolled_content_origin =
7666 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7667
7668 const SCROLL_PADDING_Y: Pixels = px(12.);
7669
7670 if target_display_point.row() < visible_row_range.start {
7671 return self.render_edit_prediction_scroll_popover(
7672 |_| SCROLL_PADDING_Y,
7673 IconName::ArrowUp,
7674 visible_row_range,
7675 line_layouts,
7676 newest_selection_head,
7677 scrolled_content_origin,
7678 window,
7679 cx,
7680 );
7681 } else if target_display_point.row() >= visible_row_range.end {
7682 return self.render_edit_prediction_scroll_popover(
7683 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7684 IconName::ArrowDown,
7685 visible_row_range,
7686 line_layouts,
7687 newest_selection_head,
7688 scrolled_content_origin,
7689 window,
7690 cx,
7691 );
7692 }
7693
7694 const POLE_WIDTH: Pixels = px(2.);
7695
7696 let line_layout =
7697 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7698 let target_column = target_display_point.column() as usize;
7699
7700 let target_x = line_layout.x_for_index(target_column);
7701 let target_y =
7702 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7703
7704 let flag_on_right = target_x < text_bounds.size.width / 2.;
7705
7706 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7707 border_color.l += 0.001;
7708
7709 let mut element = v_flex()
7710 .items_end()
7711 .when(flag_on_right, |el| el.items_start())
7712 .child(if flag_on_right {
7713 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7714 .rounded_bl(px(0.))
7715 .rounded_tl(px(0.))
7716 .border_l_2()
7717 .border_color(border_color)
7718 } else {
7719 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7720 .rounded_br(px(0.))
7721 .rounded_tr(px(0.))
7722 .border_r_2()
7723 .border_color(border_color)
7724 })
7725 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7726 .into_any();
7727
7728 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7729
7730 let mut origin = scrolled_content_origin + point(target_x, target_y)
7731 - point(
7732 if flag_on_right {
7733 POLE_WIDTH
7734 } else {
7735 size.width - POLE_WIDTH
7736 },
7737 size.height - line_height,
7738 );
7739
7740 origin.x = origin.x.max(content_origin.x);
7741
7742 element.prepaint_at(origin, window, cx);
7743
7744 Some((element, origin))
7745 }
7746
7747 fn render_edit_prediction_scroll_popover(
7748 &mut self,
7749 to_y: impl Fn(Size<Pixels>) -> Pixels,
7750 scroll_icon: IconName,
7751 visible_row_range: Range<DisplayRow>,
7752 line_layouts: &[LineWithInvisibles],
7753 newest_selection_head: Option<DisplayPoint>,
7754 scrolled_content_origin: gpui::Point<Pixels>,
7755 window: &mut Window,
7756 cx: &mut App,
7757 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7758 let mut element = self
7759 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7760 .into_any();
7761
7762 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7763
7764 let cursor = newest_selection_head?;
7765 let cursor_row_layout =
7766 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7767 let cursor_column = cursor.column() as usize;
7768
7769 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7770
7771 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7772
7773 element.prepaint_at(origin, window, cx);
7774 Some((element, origin))
7775 }
7776
7777 fn render_edit_prediction_eager_jump_popover(
7778 &mut self,
7779 text_bounds: &Bounds<Pixels>,
7780 content_origin: gpui::Point<Pixels>,
7781 editor_snapshot: &EditorSnapshot,
7782 visible_row_range: Range<DisplayRow>,
7783 scroll_top: f32,
7784 scroll_bottom: f32,
7785 line_height: Pixels,
7786 scroll_pixel_position: gpui::Point<Pixels>,
7787 target_display_point: DisplayPoint,
7788 editor_width: Pixels,
7789 window: &mut Window,
7790 cx: &mut App,
7791 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7792 if target_display_point.row().as_f32() < scroll_top {
7793 let mut element = self
7794 .render_edit_prediction_line_popover(
7795 "Jump to Edit",
7796 Some(IconName::ArrowUp),
7797 window,
7798 cx,
7799 )?
7800 .into_any();
7801
7802 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7803 let offset = point(
7804 (text_bounds.size.width - size.width) / 2.,
7805 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7806 );
7807
7808 let origin = text_bounds.origin + offset;
7809 element.prepaint_at(origin, window, cx);
7810 Some((element, origin))
7811 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7812 let mut element = self
7813 .render_edit_prediction_line_popover(
7814 "Jump to Edit",
7815 Some(IconName::ArrowDown),
7816 window,
7817 cx,
7818 )?
7819 .into_any();
7820
7821 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7822 let offset = point(
7823 (text_bounds.size.width - size.width) / 2.,
7824 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7825 );
7826
7827 let origin = text_bounds.origin + offset;
7828 element.prepaint_at(origin, window, cx);
7829 Some((element, origin))
7830 } else {
7831 self.render_edit_prediction_end_of_line_popover(
7832 "Jump to Edit",
7833 editor_snapshot,
7834 visible_row_range,
7835 target_display_point,
7836 line_height,
7837 scroll_pixel_position,
7838 content_origin,
7839 editor_width,
7840 window,
7841 cx,
7842 )
7843 }
7844 }
7845
7846 fn render_edit_prediction_end_of_line_popover(
7847 self: &mut Editor,
7848 label: &'static str,
7849 editor_snapshot: &EditorSnapshot,
7850 visible_row_range: Range<DisplayRow>,
7851 target_display_point: DisplayPoint,
7852 line_height: Pixels,
7853 scroll_pixel_position: gpui::Point<Pixels>,
7854 content_origin: gpui::Point<Pixels>,
7855 editor_width: Pixels,
7856 window: &mut Window,
7857 cx: &mut App,
7858 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7859 let target_line_end = DisplayPoint::new(
7860 target_display_point.row(),
7861 editor_snapshot.line_len(target_display_point.row()),
7862 );
7863
7864 let mut element = self
7865 .render_edit_prediction_line_popover(label, None, window, cx)?
7866 .into_any();
7867
7868 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7869
7870 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7871
7872 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7873 let mut origin = start_point
7874 + line_origin
7875 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7876 origin.x = origin.x.max(content_origin.x);
7877
7878 let max_x = content_origin.x + editor_width - size.width;
7879
7880 if origin.x > max_x {
7881 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7882
7883 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7884 origin.y += offset;
7885 IconName::ArrowUp
7886 } else {
7887 origin.y -= offset;
7888 IconName::ArrowDown
7889 };
7890
7891 element = self
7892 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7893 .into_any();
7894
7895 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7896
7897 origin.x = content_origin.x + editor_width - size.width - px(2.);
7898 }
7899
7900 element.prepaint_at(origin, window, cx);
7901 Some((element, origin))
7902 }
7903
7904 fn render_edit_prediction_diff_popover(
7905 self: &Editor,
7906 text_bounds: &Bounds<Pixels>,
7907 content_origin: gpui::Point<Pixels>,
7908 right_margin: Pixels,
7909 editor_snapshot: &EditorSnapshot,
7910 visible_row_range: Range<DisplayRow>,
7911 line_layouts: &[LineWithInvisibles],
7912 line_height: Pixels,
7913 scroll_pixel_position: gpui::Point<Pixels>,
7914 newest_selection_head: Option<DisplayPoint>,
7915 editor_width: Pixels,
7916 style: &EditorStyle,
7917 edits: &Vec<(Range<Anchor>, String)>,
7918 edit_preview: &Option<language::EditPreview>,
7919 snapshot: &language::BufferSnapshot,
7920 window: &mut Window,
7921 cx: &mut App,
7922 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7923 let edit_start = edits
7924 .first()
7925 .unwrap()
7926 .0
7927 .start
7928 .to_display_point(editor_snapshot);
7929 let edit_end = edits
7930 .last()
7931 .unwrap()
7932 .0
7933 .end
7934 .to_display_point(editor_snapshot);
7935
7936 let is_visible = visible_row_range.contains(&edit_start.row())
7937 || visible_row_range.contains(&edit_end.row());
7938 if !is_visible {
7939 return None;
7940 }
7941
7942 let highlighted_edits =
7943 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7944
7945 let styled_text = highlighted_edits.to_styled_text(&style.text);
7946 let line_count = highlighted_edits.text.lines().count();
7947
7948 const BORDER_WIDTH: Pixels = px(1.);
7949
7950 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7951 let has_keybind = keybind.is_some();
7952
7953 let mut element = h_flex()
7954 .items_start()
7955 .child(
7956 h_flex()
7957 .bg(cx.theme().colors().editor_background)
7958 .border(BORDER_WIDTH)
7959 .shadow_sm()
7960 .border_color(cx.theme().colors().border)
7961 .rounded_l_lg()
7962 .when(line_count > 1, |el| el.rounded_br_lg())
7963 .pr_1()
7964 .child(styled_text),
7965 )
7966 .child(
7967 h_flex()
7968 .h(line_height + BORDER_WIDTH * 2.)
7969 .px_1p5()
7970 .gap_1()
7971 // Workaround: For some reason, there's a gap if we don't do this
7972 .ml(-BORDER_WIDTH)
7973 .shadow(smallvec![gpui::BoxShadow {
7974 color: gpui::black().opacity(0.05),
7975 offset: point(px(1.), px(1.)),
7976 blur_radius: px(2.),
7977 spread_radius: px(0.),
7978 }])
7979 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7980 .border(BORDER_WIDTH)
7981 .border_color(cx.theme().colors().border)
7982 .rounded_r_lg()
7983 .id("edit_prediction_diff_popover_keybind")
7984 .when(!has_keybind, |el| {
7985 let status_colors = cx.theme().status();
7986
7987 el.bg(status_colors.error_background)
7988 .border_color(status_colors.error.opacity(0.6))
7989 .child(Icon::new(IconName::Info).color(Color::Error))
7990 .cursor_default()
7991 .hoverable_tooltip(move |_window, cx| {
7992 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7993 })
7994 })
7995 .children(keybind),
7996 )
7997 .into_any();
7998
7999 let longest_row =
8000 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8001 let longest_line_width = if visible_row_range.contains(&longest_row) {
8002 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8003 } else {
8004 layout_line(
8005 longest_row,
8006 editor_snapshot,
8007 style,
8008 editor_width,
8009 |_| false,
8010 window,
8011 cx,
8012 )
8013 .width
8014 };
8015
8016 let viewport_bounds =
8017 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8018 right: -right_margin,
8019 ..Default::default()
8020 });
8021
8022 let x_after_longest =
8023 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8024 - scroll_pixel_position.x;
8025
8026 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8027
8028 // Fully visible if it can be displayed within the window (allow overlapping other
8029 // panes). However, this is only allowed if the popover starts within text_bounds.
8030 let can_position_to_the_right = x_after_longest < text_bounds.right()
8031 && x_after_longest + element_bounds.width < viewport_bounds.right();
8032
8033 let mut origin = if can_position_to_the_right {
8034 point(
8035 x_after_longest,
8036 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8037 - scroll_pixel_position.y,
8038 )
8039 } else {
8040 let cursor_row = newest_selection_head.map(|head| head.row());
8041 let above_edit = edit_start
8042 .row()
8043 .0
8044 .checked_sub(line_count as u32)
8045 .map(DisplayRow);
8046 let below_edit = Some(edit_end.row() + 1);
8047 let above_cursor =
8048 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8049 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8050
8051 // Place the edit popover adjacent to the edit if there is a location
8052 // available that is onscreen and does not obscure the cursor. Otherwise,
8053 // place it adjacent to the cursor.
8054 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8055 .into_iter()
8056 .flatten()
8057 .find(|&start_row| {
8058 let end_row = start_row + line_count as u32;
8059 visible_row_range.contains(&start_row)
8060 && visible_row_range.contains(&end_row)
8061 && cursor_row.map_or(true, |cursor_row| {
8062 !((start_row..end_row).contains(&cursor_row))
8063 })
8064 })?;
8065
8066 content_origin
8067 + point(
8068 -scroll_pixel_position.x,
8069 row_target.as_f32() * line_height - scroll_pixel_position.y,
8070 )
8071 };
8072
8073 origin.x -= BORDER_WIDTH;
8074
8075 window.defer_draw(element, origin, 1);
8076
8077 // Do not return an element, since it will already be drawn due to defer_draw.
8078 None
8079 }
8080
8081 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8082 px(30.)
8083 }
8084
8085 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8086 if self.read_only(cx) {
8087 cx.theme().players().read_only()
8088 } else {
8089 self.style.as_ref().unwrap().local_player
8090 }
8091 }
8092
8093 fn render_edit_prediction_accept_keybind(
8094 &self,
8095 window: &mut Window,
8096 cx: &App,
8097 ) -> Option<AnyElement> {
8098 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8099 let accept_keystroke = accept_binding.keystroke()?;
8100
8101 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8102
8103 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8104 Color::Accent
8105 } else {
8106 Color::Muted
8107 };
8108
8109 h_flex()
8110 .px_0p5()
8111 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8112 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8113 .text_size(TextSize::XSmall.rems(cx))
8114 .child(h_flex().children(ui::render_modifiers(
8115 &accept_keystroke.modifiers,
8116 PlatformStyle::platform(),
8117 Some(modifiers_color),
8118 Some(IconSize::XSmall.rems().into()),
8119 true,
8120 )))
8121 .when(is_platform_style_mac, |parent| {
8122 parent.child(accept_keystroke.key.clone())
8123 })
8124 .when(!is_platform_style_mac, |parent| {
8125 parent.child(
8126 Key::new(
8127 util::capitalize(&accept_keystroke.key),
8128 Some(Color::Default),
8129 )
8130 .size(Some(IconSize::XSmall.rems().into())),
8131 )
8132 })
8133 .into_any()
8134 .into()
8135 }
8136
8137 fn render_edit_prediction_line_popover(
8138 &self,
8139 label: impl Into<SharedString>,
8140 icon: Option<IconName>,
8141 window: &mut Window,
8142 cx: &App,
8143 ) -> Option<Stateful<Div>> {
8144 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8145
8146 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8147 let has_keybind = keybind.is_some();
8148
8149 let result = h_flex()
8150 .id("ep-line-popover")
8151 .py_0p5()
8152 .pl_1()
8153 .pr(padding_right)
8154 .gap_1()
8155 .rounded_md()
8156 .border_1()
8157 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8158 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8159 .shadow_sm()
8160 .when(!has_keybind, |el| {
8161 let status_colors = cx.theme().status();
8162
8163 el.bg(status_colors.error_background)
8164 .border_color(status_colors.error.opacity(0.6))
8165 .pl_2()
8166 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8167 .cursor_default()
8168 .hoverable_tooltip(move |_window, cx| {
8169 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8170 })
8171 })
8172 .children(keybind)
8173 .child(
8174 Label::new(label)
8175 .size(LabelSize::Small)
8176 .when(!has_keybind, |el| {
8177 el.color(cx.theme().status().error.into()).strikethrough()
8178 }),
8179 )
8180 .when(!has_keybind, |el| {
8181 el.child(
8182 h_flex().ml_1().child(
8183 Icon::new(IconName::Info)
8184 .size(IconSize::Small)
8185 .color(cx.theme().status().error.into()),
8186 ),
8187 )
8188 })
8189 .when_some(icon, |element, icon| {
8190 element.child(
8191 div()
8192 .mt(px(1.5))
8193 .child(Icon::new(icon).size(IconSize::Small)),
8194 )
8195 });
8196
8197 Some(result)
8198 }
8199
8200 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8201 let accent_color = cx.theme().colors().text_accent;
8202 let editor_bg_color = cx.theme().colors().editor_background;
8203 editor_bg_color.blend(accent_color.opacity(0.1))
8204 }
8205
8206 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8207 let accent_color = cx.theme().colors().text_accent;
8208 let editor_bg_color = cx.theme().colors().editor_background;
8209 editor_bg_color.blend(accent_color.opacity(0.6))
8210 }
8211
8212 fn render_edit_prediction_cursor_popover(
8213 &self,
8214 min_width: Pixels,
8215 max_width: Pixels,
8216 cursor_point: Point,
8217 style: &EditorStyle,
8218 accept_keystroke: Option<&gpui::Keystroke>,
8219 _window: &Window,
8220 cx: &mut Context<Editor>,
8221 ) -> Option<AnyElement> {
8222 let provider = self.edit_prediction_provider.as_ref()?;
8223
8224 if provider.provider.needs_terms_acceptance(cx) {
8225 return Some(
8226 h_flex()
8227 .min_w(min_width)
8228 .flex_1()
8229 .px_2()
8230 .py_1()
8231 .gap_3()
8232 .elevation_2(cx)
8233 .hover(|style| style.bg(cx.theme().colors().element_hover))
8234 .id("accept-terms")
8235 .cursor_pointer()
8236 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8237 .on_click(cx.listener(|this, _event, window, cx| {
8238 cx.stop_propagation();
8239 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8240 window.dispatch_action(
8241 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8242 cx,
8243 );
8244 }))
8245 .child(
8246 h_flex()
8247 .flex_1()
8248 .gap_2()
8249 .child(Icon::new(IconName::ZedPredict))
8250 .child(Label::new("Accept Terms of Service"))
8251 .child(div().w_full())
8252 .child(
8253 Icon::new(IconName::ArrowUpRight)
8254 .color(Color::Muted)
8255 .size(IconSize::Small),
8256 )
8257 .into_any_element(),
8258 )
8259 .into_any(),
8260 );
8261 }
8262
8263 let is_refreshing = provider.provider.is_refreshing(cx);
8264
8265 fn pending_completion_container() -> Div {
8266 h_flex()
8267 .h_full()
8268 .flex_1()
8269 .gap_2()
8270 .child(Icon::new(IconName::ZedPredict))
8271 }
8272
8273 let completion = match &self.active_inline_completion {
8274 Some(prediction) => {
8275 if !self.has_visible_completions_menu() {
8276 const RADIUS: Pixels = px(6.);
8277 const BORDER_WIDTH: Pixels = px(1.);
8278
8279 return Some(
8280 h_flex()
8281 .elevation_2(cx)
8282 .border(BORDER_WIDTH)
8283 .border_color(cx.theme().colors().border)
8284 .when(accept_keystroke.is_none(), |el| {
8285 el.border_color(cx.theme().status().error)
8286 })
8287 .rounded(RADIUS)
8288 .rounded_tl(px(0.))
8289 .overflow_hidden()
8290 .child(div().px_1p5().child(match &prediction.completion {
8291 InlineCompletion::Move { target, snapshot } => {
8292 use text::ToPoint as _;
8293 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8294 {
8295 Icon::new(IconName::ZedPredictDown)
8296 } else {
8297 Icon::new(IconName::ZedPredictUp)
8298 }
8299 }
8300 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8301 }))
8302 .child(
8303 h_flex()
8304 .gap_1()
8305 .py_1()
8306 .px_2()
8307 .rounded_r(RADIUS - BORDER_WIDTH)
8308 .border_l_1()
8309 .border_color(cx.theme().colors().border)
8310 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8311 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8312 el.child(
8313 Label::new("Hold")
8314 .size(LabelSize::Small)
8315 .when(accept_keystroke.is_none(), |el| {
8316 el.strikethrough()
8317 })
8318 .line_height_style(LineHeightStyle::UiLabel),
8319 )
8320 })
8321 .id("edit_prediction_cursor_popover_keybind")
8322 .when(accept_keystroke.is_none(), |el| {
8323 let status_colors = cx.theme().status();
8324
8325 el.bg(status_colors.error_background)
8326 .border_color(status_colors.error.opacity(0.6))
8327 .child(Icon::new(IconName::Info).color(Color::Error))
8328 .cursor_default()
8329 .hoverable_tooltip(move |_window, cx| {
8330 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8331 .into()
8332 })
8333 })
8334 .when_some(
8335 accept_keystroke.as_ref(),
8336 |el, accept_keystroke| {
8337 el.child(h_flex().children(ui::render_modifiers(
8338 &accept_keystroke.modifiers,
8339 PlatformStyle::platform(),
8340 Some(Color::Default),
8341 Some(IconSize::XSmall.rems().into()),
8342 false,
8343 )))
8344 },
8345 ),
8346 )
8347 .into_any(),
8348 );
8349 }
8350
8351 self.render_edit_prediction_cursor_popover_preview(
8352 prediction,
8353 cursor_point,
8354 style,
8355 cx,
8356 )?
8357 }
8358
8359 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8360 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8361 stale_completion,
8362 cursor_point,
8363 style,
8364 cx,
8365 )?,
8366
8367 None => {
8368 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8369 }
8370 },
8371
8372 None => pending_completion_container().child(Label::new("No Prediction")),
8373 };
8374
8375 let completion = if is_refreshing {
8376 completion
8377 .with_animation(
8378 "loading-completion",
8379 Animation::new(Duration::from_secs(2))
8380 .repeat()
8381 .with_easing(pulsating_between(0.4, 0.8)),
8382 |label, delta| label.opacity(delta),
8383 )
8384 .into_any_element()
8385 } else {
8386 completion.into_any_element()
8387 };
8388
8389 let has_completion = self.active_inline_completion.is_some();
8390
8391 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8392 Some(
8393 h_flex()
8394 .min_w(min_width)
8395 .max_w(max_width)
8396 .flex_1()
8397 .elevation_2(cx)
8398 .border_color(cx.theme().colors().border)
8399 .child(
8400 div()
8401 .flex_1()
8402 .py_1()
8403 .px_2()
8404 .overflow_hidden()
8405 .child(completion),
8406 )
8407 .when_some(accept_keystroke, |el, accept_keystroke| {
8408 if !accept_keystroke.modifiers.modified() {
8409 return el;
8410 }
8411
8412 el.child(
8413 h_flex()
8414 .h_full()
8415 .border_l_1()
8416 .rounded_r_lg()
8417 .border_color(cx.theme().colors().border)
8418 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8419 .gap_1()
8420 .py_1()
8421 .px_2()
8422 .child(
8423 h_flex()
8424 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8425 .when(is_platform_style_mac, |parent| parent.gap_1())
8426 .child(h_flex().children(ui::render_modifiers(
8427 &accept_keystroke.modifiers,
8428 PlatformStyle::platform(),
8429 Some(if !has_completion {
8430 Color::Muted
8431 } else {
8432 Color::Default
8433 }),
8434 None,
8435 false,
8436 ))),
8437 )
8438 .child(Label::new("Preview").into_any_element())
8439 .opacity(if has_completion { 1.0 } else { 0.4 }),
8440 )
8441 })
8442 .into_any(),
8443 )
8444 }
8445
8446 fn render_edit_prediction_cursor_popover_preview(
8447 &self,
8448 completion: &InlineCompletionState,
8449 cursor_point: Point,
8450 style: &EditorStyle,
8451 cx: &mut Context<Editor>,
8452 ) -> Option<Div> {
8453 use text::ToPoint as _;
8454
8455 fn render_relative_row_jump(
8456 prefix: impl Into<String>,
8457 current_row: u32,
8458 target_row: u32,
8459 ) -> Div {
8460 let (row_diff, arrow) = if target_row < current_row {
8461 (current_row - target_row, IconName::ArrowUp)
8462 } else {
8463 (target_row - current_row, IconName::ArrowDown)
8464 };
8465
8466 h_flex()
8467 .child(
8468 Label::new(format!("{}{}", prefix.into(), row_diff))
8469 .color(Color::Muted)
8470 .size(LabelSize::Small),
8471 )
8472 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8473 }
8474
8475 match &completion.completion {
8476 InlineCompletion::Move {
8477 target, snapshot, ..
8478 } => Some(
8479 h_flex()
8480 .px_2()
8481 .gap_2()
8482 .flex_1()
8483 .child(
8484 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8485 Icon::new(IconName::ZedPredictDown)
8486 } else {
8487 Icon::new(IconName::ZedPredictUp)
8488 },
8489 )
8490 .child(Label::new("Jump to Edit")),
8491 ),
8492
8493 InlineCompletion::Edit {
8494 edits,
8495 edit_preview,
8496 snapshot,
8497 display_mode: _,
8498 } => {
8499 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8500
8501 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8502 &snapshot,
8503 &edits,
8504 edit_preview.as_ref()?,
8505 true,
8506 cx,
8507 )
8508 .first_line_preview();
8509
8510 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8511 .with_default_highlights(&style.text, highlighted_edits.highlights);
8512
8513 let preview = h_flex()
8514 .gap_1()
8515 .min_w_16()
8516 .child(styled_text)
8517 .when(has_more_lines, |parent| parent.child("…"));
8518
8519 let left = if first_edit_row != cursor_point.row {
8520 render_relative_row_jump("", cursor_point.row, first_edit_row)
8521 .into_any_element()
8522 } else {
8523 Icon::new(IconName::ZedPredict).into_any_element()
8524 };
8525
8526 Some(
8527 h_flex()
8528 .h_full()
8529 .flex_1()
8530 .gap_2()
8531 .pr_1()
8532 .overflow_x_hidden()
8533 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8534 .child(left)
8535 .child(preview),
8536 )
8537 }
8538 }
8539 }
8540
8541 fn render_context_menu(
8542 &self,
8543 style: &EditorStyle,
8544 max_height_in_lines: u32,
8545 window: &mut Window,
8546 cx: &mut Context<Editor>,
8547 ) -> Option<AnyElement> {
8548 let menu = self.context_menu.borrow();
8549 let menu = menu.as_ref()?;
8550 if !menu.visible() {
8551 return None;
8552 };
8553 Some(menu.render(style, max_height_in_lines, window, cx))
8554 }
8555
8556 fn render_context_menu_aside(
8557 &mut self,
8558 max_size: Size<Pixels>,
8559 window: &mut Window,
8560 cx: &mut Context<Editor>,
8561 ) -> Option<AnyElement> {
8562 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8563 if menu.visible() {
8564 menu.render_aside(self, max_size, window, cx)
8565 } else {
8566 None
8567 }
8568 })
8569 }
8570
8571 fn hide_context_menu(
8572 &mut self,
8573 window: &mut Window,
8574 cx: &mut Context<Self>,
8575 ) -> Option<CodeContextMenu> {
8576 cx.notify();
8577 self.completion_tasks.clear();
8578 let context_menu = self.context_menu.borrow_mut().take();
8579 self.stale_inline_completion_in_menu.take();
8580 self.update_visible_inline_completion(window, cx);
8581 context_menu
8582 }
8583
8584 fn show_snippet_choices(
8585 &mut self,
8586 choices: &Vec<String>,
8587 selection: Range<Anchor>,
8588 cx: &mut Context<Self>,
8589 ) {
8590 if selection.start.buffer_id.is_none() {
8591 return;
8592 }
8593 let buffer_id = selection.start.buffer_id.unwrap();
8594 let buffer = self.buffer().read(cx).buffer(buffer_id);
8595 let id = post_inc(&mut self.next_completion_id);
8596 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8597
8598 if let Some(buffer) = buffer {
8599 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8600 CompletionsMenu::new_snippet_choices(
8601 id,
8602 true,
8603 choices,
8604 selection,
8605 buffer,
8606 snippet_sort_order,
8607 ),
8608 ));
8609 }
8610 }
8611
8612 pub fn insert_snippet(
8613 &mut self,
8614 insertion_ranges: &[Range<usize>],
8615 snippet: Snippet,
8616 window: &mut Window,
8617 cx: &mut Context<Self>,
8618 ) -> Result<()> {
8619 struct Tabstop<T> {
8620 is_end_tabstop: bool,
8621 ranges: Vec<Range<T>>,
8622 choices: Option<Vec<String>>,
8623 }
8624
8625 let tabstops = self.buffer.update(cx, |buffer, cx| {
8626 let snippet_text: Arc<str> = snippet.text.clone().into();
8627 let edits = insertion_ranges
8628 .iter()
8629 .cloned()
8630 .map(|range| (range, snippet_text.clone()));
8631 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8632
8633 let snapshot = &*buffer.read(cx);
8634 let snippet = &snippet;
8635 snippet
8636 .tabstops
8637 .iter()
8638 .map(|tabstop| {
8639 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8640 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8641 });
8642 let mut tabstop_ranges = tabstop
8643 .ranges
8644 .iter()
8645 .flat_map(|tabstop_range| {
8646 let mut delta = 0_isize;
8647 insertion_ranges.iter().map(move |insertion_range| {
8648 let insertion_start = insertion_range.start as isize + delta;
8649 delta +=
8650 snippet.text.len() as isize - insertion_range.len() as isize;
8651
8652 let start = ((insertion_start + tabstop_range.start) as usize)
8653 .min(snapshot.len());
8654 let end = ((insertion_start + tabstop_range.end) as usize)
8655 .min(snapshot.len());
8656 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8657 })
8658 })
8659 .collect::<Vec<_>>();
8660 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8661
8662 Tabstop {
8663 is_end_tabstop,
8664 ranges: tabstop_ranges,
8665 choices: tabstop.choices.clone(),
8666 }
8667 })
8668 .collect::<Vec<_>>()
8669 });
8670 if let Some(tabstop) = tabstops.first() {
8671 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8672 s.select_ranges(tabstop.ranges.iter().cloned());
8673 });
8674
8675 if let Some(choices) = &tabstop.choices {
8676 if let Some(selection) = tabstop.ranges.first() {
8677 self.show_snippet_choices(choices, selection.clone(), cx)
8678 }
8679 }
8680
8681 // If we're already at the last tabstop and it's at the end of the snippet,
8682 // we're done, we don't need to keep the state around.
8683 if !tabstop.is_end_tabstop {
8684 let choices = tabstops
8685 .iter()
8686 .map(|tabstop| tabstop.choices.clone())
8687 .collect();
8688
8689 let ranges = tabstops
8690 .into_iter()
8691 .map(|tabstop| tabstop.ranges)
8692 .collect::<Vec<_>>();
8693
8694 self.snippet_stack.push(SnippetState {
8695 active_index: 0,
8696 ranges,
8697 choices,
8698 });
8699 }
8700
8701 // Check whether the just-entered snippet ends with an auto-closable bracket.
8702 if self.autoclose_regions.is_empty() {
8703 let snapshot = self.buffer.read(cx).snapshot(cx);
8704 for selection in &mut self.selections.all::<Point>(cx) {
8705 let selection_head = selection.head();
8706 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8707 continue;
8708 };
8709
8710 let mut bracket_pair = None;
8711 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8712 let prev_chars = snapshot
8713 .reversed_chars_at(selection_head)
8714 .collect::<String>();
8715 for (pair, enabled) in scope.brackets() {
8716 if enabled
8717 && pair.close
8718 && prev_chars.starts_with(pair.start.as_str())
8719 && next_chars.starts_with(pair.end.as_str())
8720 {
8721 bracket_pair = Some(pair.clone());
8722 break;
8723 }
8724 }
8725 if let Some(pair) = bracket_pair {
8726 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8727 let autoclose_enabled =
8728 self.use_autoclose && snapshot_settings.use_autoclose;
8729 if autoclose_enabled {
8730 let start = snapshot.anchor_after(selection_head);
8731 let end = snapshot.anchor_after(selection_head);
8732 self.autoclose_regions.push(AutocloseRegion {
8733 selection_id: selection.id,
8734 range: start..end,
8735 pair,
8736 });
8737 }
8738 }
8739 }
8740 }
8741 }
8742 Ok(())
8743 }
8744
8745 pub fn move_to_next_snippet_tabstop(
8746 &mut self,
8747 window: &mut Window,
8748 cx: &mut Context<Self>,
8749 ) -> bool {
8750 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8751 }
8752
8753 pub fn move_to_prev_snippet_tabstop(
8754 &mut self,
8755 window: &mut Window,
8756 cx: &mut Context<Self>,
8757 ) -> bool {
8758 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8759 }
8760
8761 pub fn move_to_snippet_tabstop(
8762 &mut self,
8763 bias: Bias,
8764 window: &mut Window,
8765 cx: &mut Context<Self>,
8766 ) -> bool {
8767 if let Some(mut snippet) = self.snippet_stack.pop() {
8768 match bias {
8769 Bias::Left => {
8770 if snippet.active_index > 0 {
8771 snippet.active_index -= 1;
8772 } else {
8773 self.snippet_stack.push(snippet);
8774 return false;
8775 }
8776 }
8777 Bias::Right => {
8778 if snippet.active_index + 1 < snippet.ranges.len() {
8779 snippet.active_index += 1;
8780 } else {
8781 self.snippet_stack.push(snippet);
8782 return false;
8783 }
8784 }
8785 }
8786 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8788 s.select_anchor_ranges(current_ranges.iter().cloned())
8789 });
8790
8791 if let Some(choices) = &snippet.choices[snippet.active_index] {
8792 if let Some(selection) = current_ranges.first() {
8793 self.show_snippet_choices(&choices, selection.clone(), cx);
8794 }
8795 }
8796
8797 // If snippet state is not at the last tabstop, push it back on the stack
8798 if snippet.active_index + 1 < snippet.ranges.len() {
8799 self.snippet_stack.push(snippet);
8800 }
8801 return true;
8802 }
8803 }
8804
8805 false
8806 }
8807
8808 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8809 self.transact(window, cx, |this, window, cx| {
8810 this.select_all(&SelectAll, window, cx);
8811 this.insert("", window, cx);
8812 });
8813 }
8814
8815 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8816 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8817 self.transact(window, cx, |this, window, cx| {
8818 this.select_autoclose_pair(window, cx);
8819 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8820 if !this.linked_edit_ranges.is_empty() {
8821 let selections = this.selections.all::<MultiBufferPoint>(cx);
8822 let snapshot = this.buffer.read(cx).snapshot(cx);
8823
8824 for selection in selections.iter() {
8825 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8826 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8827 if selection_start.buffer_id != selection_end.buffer_id {
8828 continue;
8829 }
8830 if let Some(ranges) =
8831 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8832 {
8833 for (buffer, entries) in ranges {
8834 linked_ranges.entry(buffer).or_default().extend(entries);
8835 }
8836 }
8837 }
8838 }
8839
8840 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8841 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8842 for selection in &mut selections {
8843 if selection.is_empty() {
8844 let old_head = selection.head();
8845 let mut new_head =
8846 movement::left(&display_map, old_head.to_display_point(&display_map))
8847 .to_point(&display_map);
8848 if let Some((buffer, line_buffer_range)) = display_map
8849 .buffer_snapshot
8850 .buffer_line_for_row(MultiBufferRow(old_head.row))
8851 {
8852 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8853 let indent_len = match indent_size.kind {
8854 IndentKind::Space => {
8855 buffer.settings_at(line_buffer_range.start, cx).tab_size
8856 }
8857 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8858 };
8859 if old_head.column <= indent_size.len && old_head.column > 0 {
8860 let indent_len = indent_len.get();
8861 new_head = cmp::min(
8862 new_head,
8863 MultiBufferPoint::new(
8864 old_head.row,
8865 ((old_head.column - 1) / indent_len) * indent_len,
8866 ),
8867 );
8868 }
8869 }
8870
8871 selection.set_head(new_head, SelectionGoal::None);
8872 }
8873 }
8874
8875 this.signature_help_state.set_backspace_pressed(true);
8876 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8877 s.select(selections)
8878 });
8879 this.insert("", window, cx);
8880 let empty_str: Arc<str> = Arc::from("");
8881 for (buffer, edits) in linked_ranges {
8882 let snapshot = buffer.read(cx).snapshot();
8883 use text::ToPoint as TP;
8884
8885 let edits = edits
8886 .into_iter()
8887 .map(|range| {
8888 let end_point = TP::to_point(&range.end, &snapshot);
8889 let mut start_point = TP::to_point(&range.start, &snapshot);
8890
8891 if end_point == start_point {
8892 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8893 .saturating_sub(1);
8894 start_point =
8895 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8896 };
8897
8898 (start_point..end_point, empty_str.clone())
8899 })
8900 .sorted_by_key(|(range, _)| range.start)
8901 .collect::<Vec<_>>();
8902 buffer.update(cx, |this, cx| {
8903 this.edit(edits, None, cx);
8904 })
8905 }
8906 this.refresh_inline_completion(true, false, window, cx);
8907 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8908 });
8909 }
8910
8911 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8912 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8913 self.transact(window, cx, |this, window, cx| {
8914 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8915 s.move_with(|map, selection| {
8916 if selection.is_empty() {
8917 let cursor = movement::right(map, selection.head());
8918 selection.end = cursor;
8919 selection.reversed = true;
8920 selection.goal = SelectionGoal::None;
8921 }
8922 })
8923 });
8924 this.insert("", window, cx);
8925 this.refresh_inline_completion(true, false, window, cx);
8926 });
8927 }
8928
8929 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8930 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8931 if self.move_to_prev_snippet_tabstop(window, cx) {
8932 return;
8933 }
8934 self.outdent(&Outdent, window, cx);
8935 }
8936
8937 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8938 if self.move_to_next_snippet_tabstop(window, cx) {
8939 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8940 return;
8941 }
8942 if self.read_only(cx) {
8943 return;
8944 }
8945 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8946 let mut selections = self.selections.all_adjusted(cx);
8947 let buffer = self.buffer.read(cx);
8948 let snapshot = buffer.snapshot(cx);
8949 let rows_iter = selections.iter().map(|s| s.head().row);
8950 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8951
8952 let has_some_cursor_in_whitespace = selections
8953 .iter()
8954 .filter(|selection| selection.is_empty())
8955 .any(|selection| {
8956 let cursor = selection.head();
8957 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8958 cursor.column < current_indent.len
8959 });
8960
8961 let mut edits = Vec::new();
8962 let mut prev_edited_row = 0;
8963 let mut row_delta = 0;
8964 for selection in &mut selections {
8965 if selection.start.row != prev_edited_row {
8966 row_delta = 0;
8967 }
8968 prev_edited_row = selection.end.row;
8969
8970 // If the selection is non-empty, then increase the indentation of the selected lines.
8971 if !selection.is_empty() {
8972 row_delta =
8973 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8974 continue;
8975 }
8976
8977 let cursor = selection.head();
8978 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8979 if let Some(suggested_indent) =
8980 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8981 {
8982 // Don't do anything if already at suggested indent
8983 // and there is any other cursor which is not
8984 if has_some_cursor_in_whitespace
8985 && cursor.column == current_indent.len
8986 && current_indent.len == suggested_indent.len
8987 {
8988 continue;
8989 }
8990
8991 // Adjust line and move cursor to suggested indent
8992 // if cursor is not at suggested indent
8993 if cursor.column < suggested_indent.len
8994 && cursor.column <= current_indent.len
8995 && current_indent.len <= suggested_indent.len
8996 {
8997 selection.start = Point::new(cursor.row, suggested_indent.len);
8998 selection.end = selection.start;
8999 if row_delta == 0 {
9000 edits.extend(Buffer::edit_for_indent_size_adjustment(
9001 cursor.row,
9002 current_indent,
9003 suggested_indent,
9004 ));
9005 row_delta = suggested_indent.len - current_indent.len;
9006 }
9007 continue;
9008 }
9009
9010 // If current indent is more than suggested indent
9011 // only move cursor to current indent and skip indent
9012 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9013 selection.start = Point::new(cursor.row, current_indent.len);
9014 selection.end = selection.start;
9015 continue;
9016 }
9017 }
9018
9019 // Otherwise, insert a hard or soft tab.
9020 let settings = buffer.language_settings_at(cursor, cx);
9021 let tab_size = if settings.hard_tabs {
9022 IndentSize::tab()
9023 } else {
9024 let tab_size = settings.tab_size.get();
9025 let indent_remainder = snapshot
9026 .text_for_range(Point::new(cursor.row, 0)..cursor)
9027 .flat_map(str::chars)
9028 .fold(row_delta % tab_size, |counter: u32, c| {
9029 if c == '\t' {
9030 0
9031 } else {
9032 (counter + 1) % tab_size
9033 }
9034 });
9035
9036 let chars_to_next_tab_stop = tab_size - indent_remainder;
9037 IndentSize::spaces(chars_to_next_tab_stop)
9038 };
9039 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9040 selection.end = selection.start;
9041 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9042 row_delta += tab_size.len;
9043 }
9044
9045 self.transact(window, cx, |this, window, cx| {
9046 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9047 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9048 s.select(selections)
9049 });
9050 this.refresh_inline_completion(true, false, window, cx);
9051 });
9052 }
9053
9054 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9055 if self.read_only(cx) {
9056 return;
9057 }
9058 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9059 let mut selections = self.selections.all::<Point>(cx);
9060 let mut prev_edited_row = 0;
9061 let mut row_delta = 0;
9062 let mut edits = Vec::new();
9063 let buffer = self.buffer.read(cx);
9064 let snapshot = buffer.snapshot(cx);
9065 for selection in &mut selections {
9066 if selection.start.row != prev_edited_row {
9067 row_delta = 0;
9068 }
9069 prev_edited_row = selection.end.row;
9070
9071 row_delta =
9072 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9073 }
9074
9075 self.transact(window, cx, |this, window, cx| {
9076 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9077 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9078 s.select(selections)
9079 });
9080 });
9081 }
9082
9083 fn indent_selection(
9084 buffer: &MultiBuffer,
9085 snapshot: &MultiBufferSnapshot,
9086 selection: &mut Selection<Point>,
9087 edits: &mut Vec<(Range<Point>, String)>,
9088 delta_for_start_row: u32,
9089 cx: &App,
9090 ) -> u32 {
9091 let settings = buffer.language_settings_at(selection.start, cx);
9092 let tab_size = settings.tab_size.get();
9093 let indent_kind = if settings.hard_tabs {
9094 IndentKind::Tab
9095 } else {
9096 IndentKind::Space
9097 };
9098 let mut start_row = selection.start.row;
9099 let mut end_row = selection.end.row + 1;
9100
9101 // If a selection ends at the beginning of a line, don't indent
9102 // that last line.
9103 if selection.end.column == 0 && selection.end.row > selection.start.row {
9104 end_row -= 1;
9105 }
9106
9107 // Avoid re-indenting a row that has already been indented by a
9108 // previous selection, but still update this selection's column
9109 // to reflect that indentation.
9110 if delta_for_start_row > 0 {
9111 start_row += 1;
9112 selection.start.column += delta_for_start_row;
9113 if selection.end.row == selection.start.row {
9114 selection.end.column += delta_for_start_row;
9115 }
9116 }
9117
9118 let mut delta_for_end_row = 0;
9119 let has_multiple_rows = start_row + 1 != end_row;
9120 for row in start_row..end_row {
9121 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9122 let indent_delta = match (current_indent.kind, indent_kind) {
9123 (IndentKind::Space, IndentKind::Space) => {
9124 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9125 IndentSize::spaces(columns_to_next_tab_stop)
9126 }
9127 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9128 (_, IndentKind::Tab) => IndentSize::tab(),
9129 };
9130
9131 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9132 0
9133 } else {
9134 selection.start.column
9135 };
9136 let row_start = Point::new(row, start);
9137 edits.push((
9138 row_start..row_start,
9139 indent_delta.chars().collect::<String>(),
9140 ));
9141
9142 // Update this selection's endpoints to reflect the indentation.
9143 if row == selection.start.row {
9144 selection.start.column += indent_delta.len;
9145 }
9146 if row == selection.end.row {
9147 selection.end.column += indent_delta.len;
9148 delta_for_end_row = indent_delta.len;
9149 }
9150 }
9151
9152 if selection.start.row == selection.end.row {
9153 delta_for_start_row + delta_for_end_row
9154 } else {
9155 delta_for_end_row
9156 }
9157 }
9158
9159 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9160 if self.read_only(cx) {
9161 return;
9162 }
9163 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9165 let selections = self.selections.all::<Point>(cx);
9166 let mut deletion_ranges = Vec::new();
9167 let mut last_outdent = None;
9168 {
9169 let buffer = self.buffer.read(cx);
9170 let snapshot = buffer.snapshot(cx);
9171 for selection in &selections {
9172 let settings = buffer.language_settings_at(selection.start, cx);
9173 let tab_size = settings.tab_size.get();
9174 let mut rows = selection.spanned_rows(false, &display_map);
9175
9176 // Avoid re-outdenting a row that has already been outdented by a
9177 // previous selection.
9178 if let Some(last_row) = last_outdent {
9179 if last_row == rows.start {
9180 rows.start = rows.start.next_row();
9181 }
9182 }
9183 let has_multiple_rows = rows.len() > 1;
9184 for row in rows.iter_rows() {
9185 let indent_size = snapshot.indent_size_for_line(row);
9186 if indent_size.len > 0 {
9187 let deletion_len = match indent_size.kind {
9188 IndentKind::Space => {
9189 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9190 if columns_to_prev_tab_stop == 0 {
9191 tab_size
9192 } else {
9193 columns_to_prev_tab_stop
9194 }
9195 }
9196 IndentKind::Tab => 1,
9197 };
9198 let start = if has_multiple_rows
9199 || deletion_len > selection.start.column
9200 || indent_size.len < selection.start.column
9201 {
9202 0
9203 } else {
9204 selection.start.column - deletion_len
9205 };
9206 deletion_ranges.push(
9207 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9208 );
9209 last_outdent = Some(row);
9210 }
9211 }
9212 }
9213 }
9214
9215 self.transact(window, cx, |this, window, cx| {
9216 this.buffer.update(cx, |buffer, cx| {
9217 let empty_str: Arc<str> = Arc::default();
9218 buffer.edit(
9219 deletion_ranges
9220 .into_iter()
9221 .map(|range| (range, empty_str.clone())),
9222 None,
9223 cx,
9224 );
9225 });
9226 let selections = this.selections.all::<usize>(cx);
9227 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9228 s.select(selections)
9229 });
9230 });
9231 }
9232
9233 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9234 if self.read_only(cx) {
9235 return;
9236 }
9237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9238 let selections = self
9239 .selections
9240 .all::<usize>(cx)
9241 .into_iter()
9242 .map(|s| s.range());
9243
9244 self.transact(window, cx, |this, window, cx| {
9245 this.buffer.update(cx, |buffer, cx| {
9246 buffer.autoindent_ranges(selections, cx);
9247 });
9248 let selections = this.selections.all::<usize>(cx);
9249 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9250 s.select(selections)
9251 });
9252 });
9253 }
9254
9255 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9256 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9258 let selections = self.selections.all::<Point>(cx);
9259
9260 let mut new_cursors = Vec::new();
9261 let mut edit_ranges = Vec::new();
9262 let mut selections = selections.iter().peekable();
9263 while let Some(selection) = selections.next() {
9264 let mut rows = selection.spanned_rows(false, &display_map);
9265 let goal_display_column = selection.head().to_display_point(&display_map).column();
9266
9267 // Accumulate contiguous regions of rows that we want to delete.
9268 while let Some(next_selection) = selections.peek() {
9269 let next_rows = next_selection.spanned_rows(false, &display_map);
9270 if next_rows.start <= rows.end {
9271 rows.end = next_rows.end;
9272 selections.next().unwrap();
9273 } else {
9274 break;
9275 }
9276 }
9277
9278 let buffer = &display_map.buffer_snapshot;
9279 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9280 let edit_end;
9281 let cursor_buffer_row;
9282 if buffer.max_point().row >= rows.end.0 {
9283 // If there's a line after the range, delete the \n from the end of the row range
9284 // and position the cursor on the next line.
9285 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9286 cursor_buffer_row = rows.end;
9287 } else {
9288 // If there isn't a line after the range, delete the \n from the line before the
9289 // start of the row range and position the cursor there.
9290 edit_start = edit_start.saturating_sub(1);
9291 edit_end = buffer.len();
9292 cursor_buffer_row = rows.start.previous_row();
9293 }
9294
9295 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9296 *cursor.column_mut() =
9297 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9298
9299 new_cursors.push((
9300 selection.id,
9301 buffer.anchor_after(cursor.to_point(&display_map)),
9302 ));
9303 edit_ranges.push(edit_start..edit_end);
9304 }
9305
9306 self.transact(window, cx, |this, window, cx| {
9307 let buffer = this.buffer.update(cx, |buffer, cx| {
9308 let empty_str: Arc<str> = Arc::default();
9309 buffer.edit(
9310 edit_ranges
9311 .into_iter()
9312 .map(|range| (range, empty_str.clone())),
9313 None,
9314 cx,
9315 );
9316 buffer.snapshot(cx)
9317 });
9318 let new_selections = new_cursors
9319 .into_iter()
9320 .map(|(id, cursor)| {
9321 let cursor = cursor.to_point(&buffer);
9322 Selection {
9323 id,
9324 start: cursor,
9325 end: cursor,
9326 reversed: false,
9327 goal: SelectionGoal::None,
9328 }
9329 })
9330 .collect();
9331
9332 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9333 s.select(new_selections);
9334 });
9335 });
9336 }
9337
9338 pub fn join_lines_impl(
9339 &mut self,
9340 insert_whitespace: bool,
9341 window: &mut Window,
9342 cx: &mut Context<Self>,
9343 ) {
9344 if self.read_only(cx) {
9345 return;
9346 }
9347 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9348 for selection in self.selections.all::<Point>(cx) {
9349 let start = MultiBufferRow(selection.start.row);
9350 // Treat single line selections as if they include the next line. Otherwise this action
9351 // would do nothing for single line selections individual cursors.
9352 let end = if selection.start.row == selection.end.row {
9353 MultiBufferRow(selection.start.row + 1)
9354 } else {
9355 MultiBufferRow(selection.end.row)
9356 };
9357
9358 if let Some(last_row_range) = row_ranges.last_mut() {
9359 if start <= last_row_range.end {
9360 last_row_range.end = end;
9361 continue;
9362 }
9363 }
9364 row_ranges.push(start..end);
9365 }
9366
9367 let snapshot = self.buffer.read(cx).snapshot(cx);
9368 let mut cursor_positions = Vec::new();
9369 for row_range in &row_ranges {
9370 let anchor = snapshot.anchor_before(Point::new(
9371 row_range.end.previous_row().0,
9372 snapshot.line_len(row_range.end.previous_row()),
9373 ));
9374 cursor_positions.push(anchor..anchor);
9375 }
9376
9377 self.transact(window, cx, |this, window, cx| {
9378 for row_range in row_ranges.into_iter().rev() {
9379 for row in row_range.iter_rows().rev() {
9380 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9381 let next_line_row = row.next_row();
9382 let indent = snapshot.indent_size_for_line(next_line_row);
9383 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9384
9385 let replace =
9386 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9387 " "
9388 } else {
9389 ""
9390 };
9391
9392 this.buffer.update(cx, |buffer, cx| {
9393 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9394 });
9395 }
9396 }
9397
9398 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9399 s.select_anchor_ranges(cursor_positions)
9400 });
9401 });
9402 }
9403
9404 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9405 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9406 self.join_lines_impl(true, window, cx);
9407 }
9408
9409 pub fn sort_lines_case_sensitive(
9410 &mut self,
9411 _: &SortLinesCaseSensitive,
9412 window: &mut Window,
9413 cx: &mut Context<Self>,
9414 ) {
9415 self.manipulate_lines(window, cx, |lines| lines.sort())
9416 }
9417
9418 pub fn sort_lines_case_insensitive(
9419 &mut self,
9420 _: &SortLinesCaseInsensitive,
9421 window: &mut Window,
9422 cx: &mut Context<Self>,
9423 ) {
9424 self.manipulate_lines(window, cx, |lines| {
9425 lines.sort_by_key(|line| line.to_lowercase())
9426 })
9427 }
9428
9429 pub fn unique_lines_case_insensitive(
9430 &mut self,
9431 _: &UniqueLinesCaseInsensitive,
9432 window: &mut Window,
9433 cx: &mut Context<Self>,
9434 ) {
9435 self.manipulate_lines(window, cx, |lines| {
9436 let mut seen = HashSet::default();
9437 lines.retain(|line| seen.insert(line.to_lowercase()));
9438 })
9439 }
9440
9441 pub fn unique_lines_case_sensitive(
9442 &mut self,
9443 _: &UniqueLinesCaseSensitive,
9444 window: &mut Window,
9445 cx: &mut Context<Self>,
9446 ) {
9447 self.manipulate_lines(window, cx, |lines| {
9448 let mut seen = HashSet::default();
9449 lines.retain(|line| seen.insert(*line));
9450 })
9451 }
9452
9453 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9454 let Some(project) = self.project.clone() else {
9455 return;
9456 };
9457 self.reload(project, window, cx)
9458 .detach_and_notify_err(window, cx);
9459 }
9460
9461 pub fn restore_file(
9462 &mut self,
9463 _: &::git::RestoreFile,
9464 window: &mut Window,
9465 cx: &mut Context<Self>,
9466 ) {
9467 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9468 let mut buffer_ids = HashSet::default();
9469 let snapshot = self.buffer().read(cx).snapshot(cx);
9470 for selection in self.selections.all::<usize>(cx) {
9471 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9472 }
9473
9474 let buffer = self.buffer().read(cx);
9475 let ranges = buffer_ids
9476 .into_iter()
9477 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9478 .collect::<Vec<_>>();
9479
9480 self.restore_hunks_in_ranges(ranges, window, cx);
9481 }
9482
9483 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9484 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9485 let selections = self
9486 .selections
9487 .all(cx)
9488 .into_iter()
9489 .map(|s| s.range())
9490 .collect();
9491 self.restore_hunks_in_ranges(selections, window, cx);
9492 }
9493
9494 pub fn restore_hunks_in_ranges(
9495 &mut self,
9496 ranges: Vec<Range<Point>>,
9497 window: &mut Window,
9498 cx: &mut Context<Editor>,
9499 ) {
9500 let mut revert_changes = HashMap::default();
9501 let chunk_by = self
9502 .snapshot(window, cx)
9503 .hunks_for_ranges(ranges)
9504 .into_iter()
9505 .chunk_by(|hunk| hunk.buffer_id);
9506 for (buffer_id, hunks) in &chunk_by {
9507 let hunks = hunks.collect::<Vec<_>>();
9508 for hunk in &hunks {
9509 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9510 }
9511 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9512 }
9513 drop(chunk_by);
9514 if !revert_changes.is_empty() {
9515 self.transact(window, cx, |editor, window, cx| {
9516 editor.restore(revert_changes, window, cx);
9517 });
9518 }
9519 }
9520
9521 pub fn open_active_item_in_terminal(
9522 &mut self,
9523 _: &OpenInTerminal,
9524 window: &mut Window,
9525 cx: &mut Context<Self>,
9526 ) {
9527 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9528 let project_path = buffer.read(cx).project_path(cx)?;
9529 let project = self.project.as_ref()?.read(cx);
9530 let entry = project.entry_for_path(&project_path, cx)?;
9531 let parent = match &entry.canonical_path {
9532 Some(canonical_path) => canonical_path.to_path_buf(),
9533 None => project.absolute_path(&project_path, cx)?,
9534 }
9535 .parent()?
9536 .to_path_buf();
9537 Some(parent)
9538 }) {
9539 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9540 }
9541 }
9542
9543 fn set_breakpoint_context_menu(
9544 &mut self,
9545 display_row: DisplayRow,
9546 position: Option<Anchor>,
9547 clicked_point: gpui::Point<Pixels>,
9548 window: &mut Window,
9549 cx: &mut Context<Self>,
9550 ) {
9551 if !cx.has_flag::<DebuggerFeatureFlag>() {
9552 return;
9553 }
9554 let source = self
9555 .buffer
9556 .read(cx)
9557 .snapshot(cx)
9558 .anchor_before(Point::new(display_row.0, 0u32));
9559
9560 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9561
9562 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9563 self,
9564 source,
9565 clicked_point,
9566 context_menu,
9567 window,
9568 cx,
9569 );
9570 }
9571
9572 fn add_edit_breakpoint_block(
9573 &mut self,
9574 anchor: Anchor,
9575 breakpoint: &Breakpoint,
9576 edit_action: BreakpointPromptEditAction,
9577 window: &mut Window,
9578 cx: &mut Context<Self>,
9579 ) {
9580 let weak_editor = cx.weak_entity();
9581 let bp_prompt = cx.new(|cx| {
9582 BreakpointPromptEditor::new(
9583 weak_editor,
9584 anchor,
9585 breakpoint.clone(),
9586 edit_action,
9587 window,
9588 cx,
9589 )
9590 });
9591
9592 let height = bp_prompt.update(cx, |this, cx| {
9593 this.prompt
9594 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9595 });
9596 let cloned_prompt = bp_prompt.clone();
9597 let blocks = vec![BlockProperties {
9598 style: BlockStyle::Sticky,
9599 placement: BlockPlacement::Above(anchor),
9600 height: Some(height),
9601 render: Arc::new(move |cx| {
9602 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9603 cloned_prompt.clone().into_any_element()
9604 }),
9605 priority: 0,
9606 render_in_minimap: true,
9607 }];
9608
9609 let focus_handle = bp_prompt.focus_handle(cx);
9610 window.focus(&focus_handle);
9611
9612 let block_ids = self.insert_blocks(blocks, None, cx);
9613 bp_prompt.update(cx, |prompt, _| {
9614 prompt.add_block_ids(block_ids);
9615 });
9616 }
9617
9618 pub(crate) fn breakpoint_at_row(
9619 &self,
9620 row: u32,
9621 window: &mut Window,
9622 cx: &mut Context<Self>,
9623 ) -> Option<(Anchor, Breakpoint)> {
9624 let snapshot = self.snapshot(window, cx);
9625 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9626
9627 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9628 }
9629
9630 pub(crate) fn breakpoint_at_anchor(
9631 &self,
9632 breakpoint_position: Anchor,
9633 snapshot: &EditorSnapshot,
9634 cx: &mut Context<Self>,
9635 ) -> Option<(Anchor, Breakpoint)> {
9636 let project = self.project.clone()?;
9637
9638 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9639 snapshot
9640 .buffer_snapshot
9641 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9642 })?;
9643
9644 let enclosing_excerpt = breakpoint_position.excerpt_id;
9645 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9646 let buffer_snapshot = buffer.read(cx).snapshot();
9647
9648 let row = buffer_snapshot
9649 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9650 .row;
9651
9652 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9653 let anchor_end = snapshot
9654 .buffer_snapshot
9655 .anchor_after(Point::new(row, line_len));
9656
9657 let bp = self
9658 .breakpoint_store
9659 .as_ref()?
9660 .read_with(cx, |breakpoint_store, cx| {
9661 breakpoint_store
9662 .breakpoints(
9663 &buffer,
9664 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9665 &buffer_snapshot,
9666 cx,
9667 )
9668 .next()
9669 .and_then(|(bp, _)| {
9670 let breakpoint_row = buffer_snapshot
9671 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9672 .row;
9673
9674 if breakpoint_row == row {
9675 snapshot
9676 .buffer_snapshot
9677 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9678 .map(|position| (position, bp.bp.clone()))
9679 } else {
9680 None
9681 }
9682 })
9683 });
9684 bp
9685 }
9686
9687 pub fn edit_log_breakpoint(
9688 &mut self,
9689 _: &EditLogBreakpoint,
9690 window: &mut Window,
9691 cx: &mut Context<Self>,
9692 ) {
9693 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9694 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9695 message: None,
9696 state: BreakpointState::Enabled,
9697 condition: None,
9698 hit_condition: None,
9699 });
9700
9701 self.add_edit_breakpoint_block(
9702 anchor,
9703 &breakpoint,
9704 BreakpointPromptEditAction::Log,
9705 window,
9706 cx,
9707 );
9708 }
9709 }
9710
9711 fn breakpoints_at_cursors(
9712 &self,
9713 window: &mut Window,
9714 cx: &mut Context<Self>,
9715 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9716 let snapshot = self.snapshot(window, cx);
9717 let cursors = self
9718 .selections
9719 .disjoint_anchors()
9720 .into_iter()
9721 .map(|selection| {
9722 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9723
9724 let breakpoint_position = self
9725 .breakpoint_at_row(cursor_position.row, window, cx)
9726 .map(|bp| bp.0)
9727 .unwrap_or_else(|| {
9728 snapshot
9729 .display_snapshot
9730 .buffer_snapshot
9731 .anchor_after(Point::new(cursor_position.row, 0))
9732 });
9733
9734 let breakpoint = self
9735 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9736 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9737
9738 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9739 })
9740 // 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.
9741 .collect::<HashMap<Anchor, _>>();
9742
9743 cursors.into_iter().collect()
9744 }
9745
9746 pub fn enable_breakpoint(
9747 &mut self,
9748 _: &crate::actions::EnableBreakpoint,
9749 window: &mut Window,
9750 cx: &mut Context<Self>,
9751 ) {
9752 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9753 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9754 continue;
9755 };
9756 self.edit_breakpoint_at_anchor(
9757 anchor,
9758 breakpoint,
9759 BreakpointEditAction::InvertState,
9760 cx,
9761 );
9762 }
9763 }
9764
9765 pub fn disable_breakpoint(
9766 &mut self,
9767 _: &crate::actions::DisableBreakpoint,
9768 window: &mut Window,
9769 cx: &mut Context<Self>,
9770 ) {
9771 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9772 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9773 continue;
9774 };
9775 self.edit_breakpoint_at_anchor(
9776 anchor,
9777 breakpoint,
9778 BreakpointEditAction::InvertState,
9779 cx,
9780 );
9781 }
9782 }
9783
9784 pub fn toggle_breakpoint(
9785 &mut self,
9786 _: &crate::actions::ToggleBreakpoint,
9787 window: &mut Window,
9788 cx: &mut Context<Self>,
9789 ) {
9790 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9791 if let Some(breakpoint) = breakpoint {
9792 self.edit_breakpoint_at_anchor(
9793 anchor,
9794 breakpoint,
9795 BreakpointEditAction::Toggle,
9796 cx,
9797 );
9798 } else {
9799 self.edit_breakpoint_at_anchor(
9800 anchor,
9801 Breakpoint::new_standard(),
9802 BreakpointEditAction::Toggle,
9803 cx,
9804 );
9805 }
9806 }
9807 }
9808
9809 pub fn edit_breakpoint_at_anchor(
9810 &mut self,
9811 breakpoint_position: Anchor,
9812 breakpoint: Breakpoint,
9813 edit_action: BreakpointEditAction,
9814 cx: &mut Context<Self>,
9815 ) {
9816 let Some(breakpoint_store) = &self.breakpoint_store else {
9817 return;
9818 };
9819
9820 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9821 if breakpoint_position == Anchor::min() {
9822 self.buffer()
9823 .read(cx)
9824 .excerpt_buffer_ids()
9825 .into_iter()
9826 .next()
9827 } else {
9828 None
9829 }
9830 }) else {
9831 return;
9832 };
9833
9834 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9835 return;
9836 };
9837
9838 breakpoint_store.update(cx, |breakpoint_store, cx| {
9839 breakpoint_store.toggle_breakpoint(
9840 buffer,
9841 BreakpointWithPosition {
9842 position: breakpoint_position.text_anchor,
9843 bp: breakpoint,
9844 },
9845 edit_action,
9846 cx,
9847 );
9848 });
9849
9850 cx.notify();
9851 }
9852
9853 #[cfg(any(test, feature = "test-support"))]
9854 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9855 self.breakpoint_store.clone()
9856 }
9857
9858 pub fn prepare_restore_change(
9859 &self,
9860 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9861 hunk: &MultiBufferDiffHunk,
9862 cx: &mut App,
9863 ) -> Option<()> {
9864 if hunk.is_created_file() {
9865 return None;
9866 }
9867 let buffer = self.buffer.read(cx);
9868 let diff = buffer.diff_for(hunk.buffer_id)?;
9869 let buffer = buffer.buffer(hunk.buffer_id)?;
9870 let buffer = buffer.read(cx);
9871 let original_text = diff
9872 .read(cx)
9873 .base_text()
9874 .as_rope()
9875 .slice(hunk.diff_base_byte_range.clone());
9876 let buffer_snapshot = buffer.snapshot();
9877 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9878 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9879 probe
9880 .0
9881 .start
9882 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9883 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9884 }) {
9885 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9886 Some(())
9887 } else {
9888 None
9889 }
9890 }
9891
9892 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9893 self.manipulate_lines(window, cx, |lines| lines.reverse())
9894 }
9895
9896 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9897 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9898 }
9899
9900 fn manipulate_lines<Fn>(
9901 &mut self,
9902 window: &mut Window,
9903 cx: &mut Context<Self>,
9904 mut callback: Fn,
9905 ) where
9906 Fn: FnMut(&mut Vec<&str>),
9907 {
9908 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9909
9910 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9911 let buffer = self.buffer.read(cx).snapshot(cx);
9912
9913 let mut edits = Vec::new();
9914
9915 let selections = self.selections.all::<Point>(cx);
9916 let mut selections = selections.iter().peekable();
9917 let mut contiguous_row_selections = Vec::new();
9918 let mut new_selections = Vec::new();
9919 let mut added_lines = 0;
9920 let mut removed_lines = 0;
9921
9922 while let Some(selection) = selections.next() {
9923 let (start_row, end_row) = consume_contiguous_rows(
9924 &mut contiguous_row_selections,
9925 selection,
9926 &display_map,
9927 &mut selections,
9928 );
9929
9930 let start_point = Point::new(start_row.0, 0);
9931 let end_point = Point::new(
9932 end_row.previous_row().0,
9933 buffer.line_len(end_row.previous_row()),
9934 );
9935 let text = buffer
9936 .text_for_range(start_point..end_point)
9937 .collect::<String>();
9938
9939 let mut lines = text.split('\n').collect_vec();
9940
9941 let lines_before = lines.len();
9942 callback(&mut lines);
9943 let lines_after = lines.len();
9944
9945 edits.push((start_point..end_point, lines.join("\n")));
9946
9947 // Selections must change based on added and removed line count
9948 let start_row =
9949 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9950 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9951 new_selections.push(Selection {
9952 id: selection.id,
9953 start: start_row,
9954 end: end_row,
9955 goal: SelectionGoal::None,
9956 reversed: selection.reversed,
9957 });
9958
9959 if lines_after > lines_before {
9960 added_lines += lines_after - lines_before;
9961 } else if lines_before > lines_after {
9962 removed_lines += lines_before - lines_after;
9963 }
9964 }
9965
9966 self.transact(window, cx, |this, window, cx| {
9967 let buffer = this.buffer.update(cx, |buffer, cx| {
9968 buffer.edit(edits, None, cx);
9969 buffer.snapshot(cx)
9970 });
9971
9972 // Recalculate offsets on newly edited buffer
9973 let new_selections = new_selections
9974 .iter()
9975 .map(|s| {
9976 let start_point = Point::new(s.start.0, 0);
9977 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9978 Selection {
9979 id: s.id,
9980 start: buffer.point_to_offset(start_point),
9981 end: buffer.point_to_offset(end_point),
9982 goal: s.goal,
9983 reversed: s.reversed,
9984 }
9985 })
9986 .collect();
9987
9988 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9989 s.select(new_selections);
9990 });
9991
9992 this.request_autoscroll(Autoscroll::fit(), cx);
9993 });
9994 }
9995
9996 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9997 self.manipulate_text(window, cx, |text| {
9998 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9999 if has_upper_case_characters {
10000 text.to_lowercase()
10001 } else {
10002 text.to_uppercase()
10003 }
10004 })
10005 }
10006
10007 pub fn convert_to_upper_case(
10008 &mut self,
10009 _: &ConvertToUpperCase,
10010 window: &mut Window,
10011 cx: &mut Context<Self>,
10012 ) {
10013 self.manipulate_text(window, cx, |text| text.to_uppercase())
10014 }
10015
10016 pub fn convert_to_lower_case(
10017 &mut self,
10018 _: &ConvertToLowerCase,
10019 window: &mut Window,
10020 cx: &mut Context<Self>,
10021 ) {
10022 self.manipulate_text(window, cx, |text| text.to_lowercase())
10023 }
10024
10025 pub fn convert_to_title_case(
10026 &mut self,
10027 _: &ConvertToTitleCase,
10028 window: &mut Window,
10029 cx: &mut Context<Self>,
10030 ) {
10031 self.manipulate_text(window, cx, |text| {
10032 text.split('\n')
10033 .map(|line| line.to_case(Case::Title))
10034 .join("\n")
10035 })
10036 }
10037
10038 pub fn convert_to_snake_case(
10039 &mut self,
10040 _: &ConvertToSnakeCase,
10041 window: &mut Window,
10042 cx: &mut Context<Self>,
10043 ) {
10044 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10045 }
10046
10047 pub fn convert_to_kebab_case(
10048 &mut self,
10049 _: &ConvertToKebabCase,
10050 window: &mut Window,
10051 cx: &mut Context<Self>,
10052 ) {
10053 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10054 }
10055
10056 pub fn convert_to_upper_camel_case(
10057 &mut self,
10058 _: &ConvertToUpperCamelCase,
10059 window: &mut Window,
10060 cx: &mut Context<Self>,
10061 ) {
10062 self.manipulate_text(window, cx, |text| {
10063 text.split('\n')
10064 .map(|line| line.to_case(Case::UpperCamel))
10065 .join("\n")
10066 })
10067 }
10068
10069 pub fn convert_to_lower_camel_case(
10070 &mut self,
10071 _: &ConvertToLowerCamelCase,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10076 }
10077
10078 pub fn convert_to_opposite_case(
10079 &mut self,
10080 _: &ConvertToOppositeCase,
10081 window: &mut Window,
10082 cx: &mut Context<Self>,
10083 ) {
10084 self.manipulate_text(window, cx, |text| {
10085 text.chars()
10086 .fold(String::with_capacity(text.len()), |mut t, c| {
10087 if c.is_uppercase() {
10088 t.extend(c.to_lowercase());
10089 } else {
10090 t.extend(c.to_uppercase());
10091 }
10092 t
10093 })
10094 })
10095 }
10096
10097 pub fn convert_to_rot13(
10098 &mut self,
10099 _: &ConvertToRot13,
10100 window: &mut Window,
10101 cx: &mut Context<Self>,
10102 ) {
10103 self.manipulate_text(window, cx, |text| {
10104 text.chars()
10105 .map(|c| match c {
10106 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10107 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10108 _ => c,
10109 })
10110 .collect()
10111 })
10112 }
10113
10114 pub fn convert_to_rot47(
10115 &mut self,
10116 _: &ConvertToRot47,
10117 window: &mut Window,
10118 cx: &mut Context<Self>,
10119 ) {
10120 self.manipulate_text(window, cx, |text| {
10121 text.chars()
10122 .map(|c| {
10123 let code_point = c as u32;
10124 if code_point >= 33 && code_point <= 126 {
10125 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10126 }
10127 c
10128 })
10129 .collect()
10130 })
10131 }
10132
10133 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10134 where
10135 Fn: FnMut(&str) -> String,
10136 {
10137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10138 let buffer = self.buffer.read(cx).snapshot(cx);
10139
10140 let mut new_selections = Vec::new();
10141 let mut edits = Vec::new();
10142 let mut selection_adjustment = 0i32;
10143
10144 for selection in self.selections.all::<usize>(cx) {
10145 let selection_is_empty = selection.is_empty();
10146
10147 let (start, end) = if selection_is_empty {
10148 let word_range = movement::surrounding_word(
10149 &display_map,
10150 selection.start.to_display_point(&display_map),
10151 );
10152 let start = word_range.start.to_offset(&display_map, Bias::Left);
10153 let end = word_range.end.to_offset(&display_map, Bias::Left);
10154 (start, end)
10155 } else {
10156 (selection.start, selection.end)
10157 };
10158
10159 let text = buffer.text_for_range(start..end).collect::<String>();
10160 let old_length = text.len() as i32;
10161 let text = callback(&text);
10162
10163 new_selections.push(Selection {
10164 start: (start as i32 - selection_adjustment) as usize,
10165 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10166 goal: SelectionGoal::None,
10167 ..selection
10168 });
10169
10170 selection_adjustment += old_length - text.len() as i32;
10171
10172 edits.push((start..end, text));
10173 }
10174
10175 self.transact(window, cx, |this, window, cx| {
10176 this.buffer.update(cx, |buffer, cx| {
10177 buffer.edit(edits, None, cx);
10178 });
10179
10180 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10181 s.select(new_selections);
10182 });
10183
10184 this.request_autoscroll(Autoscroll::fit(), cx);
10185 });
10186 }
10187
10188 pub fn duplicate(
10189 &mut self,
10190 upwards: bool,
10191 whole_lines: bool,
10192 window: &mut Window,
10193 cx: &mut Context<Self>,
10194 ) {
10195 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10196
10197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10198 let buffer = &display_map.buffer_snapshot;
10199 let selections = self.selections.all::<Point>(cx);
10200
10201 let mut edits = Vec::new();
10202 let mut selections_iter = selections.iter().peekable();
10203 while let Some(selection) = selections_iter.next() {
10204 let mut rows = selection.spanned_rows(false, &display_map);
10205 // duplicate line-wise
10206 if whole_lines || selection.start == selection.end {
10207 // Avoid duplicating the same lines twice.
10208 while let Some(next_selection) = selections_iter.peek() {
10209 let next_rows = next_selection.spanned_rows(false, &display_map);
10210 if next_rows.start < rows.end {
10211 rows.end = next_rows.end;
10212 selections_iter.next().unwrap();
10213 } else {
10214 break;
10215 }
10216 }
10217
10218 // Copy the text from the selected row region and splice it either at the start
10219 // or end of the region.
10220 let start = Point::new(rows.start.0, 0);
10221 let end = Point::new(
10222 rows.end.previous_row().0,
10223 buffer.line_len(rows.end.previous_row()),
10224 );
10225 let text = buffer
10226 .text_for_range(start..end)
10227 .chain(Some("\n"))
10228 .collect::<String>();
10229 let insert_location = if upwards {
10230 Point::new(rows.end.0, 0)
10231 } else {
10232 start
10233 };
10234 edits.push((insert_location..insert_location, text));
10235 } else {
10236 // duplicate character-wise
10237 let start = selection.start;
10238 let end = selection.end;
10239 let text = buffer.text_for_range(start..end).collect::<String>();
10240 edits.push((selection.end..selection.end, text));
10241 }
10242 }
10243
10244 self.transact(window, cx, |this, _, cx| {
10245 this.buffer.update(cx, |buffer, cx| {
10246 buffer.edit(edits, None, cx);
10247 });
10248
10249 this.request_autoscroll(Autoscroll::fit(), cx);
10250 });
10251 }
10252
10253 pub fn duplicate_line_up(
10254 &mut self,
10255 _: &DuplicateLineUp,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) {
10259 self.duplicate(true, true, window, cx);
10260 }
10261
10262 pub fn duplicate_line_down(
10263 &mut self,
10264 _: &DuplicateLineDown,
10265 window: &mut Window,
10266 cx: &mut Context<Self>,
10267 ) {
10268 self.duplicate(false, true, window, cx);
10269 }
10270
10271 pub fn duplicate_selection(
10272 &mut self,
10273 _: &DuplicateSelection,
10274 window: &mut Window,
10275 cx: &mut Context<Self>,
10276 ) {
10277 self.duplicate(false, false, window, cx);
10278 }
10279
10280 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10281 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10282
10283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10284 let buffer = self.buffer.read(cx).snapshot(cx);
10285
10286 let mut edits = Vec::new();
10287 let mut unfold_ranges = Vec::new();
10288 let mut refold_creases = Vec::new();
10289
10290 let selections = self.selections.all::<Point>(cx);
10291 let mut selections = selections.iter().peekable();
10292 let mut contiguous_row_selections = Vec::new();
10293 let mut new_selections = Vec::new();
10294
10295 while let Some(selection) = selections.next() {
10296 // Find all the selections that span a contiguous row range
10297 let (start_row, end_row) = consume_contiguous_rows(
10298 &mut contiguous_row_selections,
10299 selection,
10300 &display_map,
10301 &mut selections,
10302 );
10303
10304 // Move the text spanned by the row range to be before the line preceding the row range
10305 if start_row.0 > 0 {
10306 let range_to_move = Point::new(
10307 start_row.previous_row().0,
10308 buffer.line_len(start_row.previous_row()),
10309 )
10310 ..Point::new(
10311 end_row.previous_row().0,
10312 buffer.line_len(end_row.previous_row()),
10313 );
10314 let insertion_point = display_map
10315 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10316 .0;
10317
10318 // Don't move lines across excerpts
10319 if buffer
10320 .excerpt_containing(insertion_point..range_to_move.end)
10321 .is_some()
10322 {
10323 let text = buffer
10324 .text_for_range(range_to_move.clone())
10325 .flat_map(|s| s.chars())
10326 .skip(1)
10327 .chain(['\n'])
10328 .collect::<String>();
10329
10330 edits.push((
10331 buffer.anchor_after(range_to_move.start)
10332 ..buffer.anchor_before(range_to_move.end),
10333 String::new(),
10334 ));
10335 let insertion_anchor = buffer.anchor_after(insertion_point);
10336 edits.push((insertion_anchor..insertion_anchor, text));
10337
10338 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10339
10340 // Move selections up
10341 new_selections.extend(contiguous_row_selections.drain(..).map(
10342 |mut selection| {
10343 selection.start.row -= row_delta;
10344 selection.end.row -= row_delta;
10345 selection
10346 },
10347 ));
10348
10349 // Move folds up
10350 unfold_ranges.push(range_to_move.clone());
10351 for fold in display_map.folds_in_range(
10352 buffer.anchor_before(range_to_move.start)
10353 ..buffer.anchor_after(range_to_move.end),
10354 ) {
10355 let mut start = fold.range.start.to_point(&buffer);
10356 let mut end = fold.range.end.to_point(&buffer);
10357 start.row -= row_delta;
10358 end.row -= row_delta;
10359 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10360 }
10361 }
10362 }
10363
10364 // If we didn't move line(s), preserve the existing selections
10365 new_selections.append(&mut contiguous_row_selections);
10366 }
10367
10368 self.transact(window, cx, |this, window, cx| {
10369 this.unfold_ranges(&unfold_ranges, true, true, cx);
10370 this.buffer.update(cx, |buffer, cx| {
10371 for (range, text) in edits {
10372 buffer.edit([(range, text)], None, cx);
10373 }
10374 });
10375 this.fold_creases(refold_creases, true, window, cx);
10376 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10377 s.select(new_selections);
10378 })
10379 });
10380 }
10381
10382 pub fn move_line_down(
10383 &mut self,
10384 _: &MoveLineDown,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10389
10390 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10391 let buffer = self.buffer.read(cx).snapshot(cx);
10392
10393 let mut edits = Vec::new();
10394 let mut unfold_ranges = Vec::new();
10395 let mut refold_creases = Vec::new();
10396
10397 let selections = self.selections.all::<Point>(cx);
10398 let mut selections = selections.iter().peekable();
10399 let mut contiguous_row_selections = Vec::new();
10400 let mut new_selections = Vec::new();
10401
10402 while let Some(selection) = selections.next() {
10403 // Find all the selections that span a contiguous row range
10404 let (start_row, end_row) = consume_contiguous_rows(
10405 &mut contiguous_row_selections,
10406 selection,
10407 &display_map,
10408 &mut selections,
10409 );
10410
10411 // Move the text spanned by the row range to be after the last line of the row range
10412 if end_row.0 <= buffer.max_point().row {
10413 let range_to_move =
10414 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10415 let insertion_point = display_map
10416 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10417 .0;
10418
10419 // Don't move lines across excerpt boundaries
10420 if buffer
10421 .excerpt_containing(range_to_move.start..insertion_point)
10422 .is_some()
10423 {
10424 let mut text = String::from("\n");
10425 text.extend(buffer.text_for_range(range_to_move.clone()));
10426 text.pop(); // Drop trailing newline
10427 edits.push((
10428 buffer.anchor_after(range_to_move.start)
10429 ..buffer.anchor_before(range_to_move.end),
10430 String::new(),
10431 ));
10432 let insertion_anchor = buffer.anchor_after(insertion_point);
10433 edits.push((insertion_anchor..insertion_anchor, text));
10434
10435 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10436
10437 // Move selections down
10438 new_selections.extend(contiguous_row_selections.drain(..).map(
10439 |mut selection| {
10440 selection.start.row += row_delta;
10441 selection.end.row += row_delta;
10442 selection
10443 },
10444 ));
10445
10446 // Move folds down
10447 unfold_ranges.push(range_to_move.clone());
10448 for fold in display_map.folds_in_range(
10449 buffer.anchor_before(range_to_move.start)
10450 ..buffer.anchor_after(range_to_move.end),
10451 ) {
10452 let mut start = fold.range.start.to_point(&buffer);
10453 let mut end = fold.range.end.to_point(&buffer);
10454 start.row += row_delta;
10455 end.row += row_delta;
10456 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10457 }
10458 }
10459 }
10460
10461 // If we didn't move line(s), preserve the existing selections
10462 new_selections.append(&mut contiguous_row_selections);
10463 }
10464
10465 self.transact(window, cx, |this, window, cx| {
10466 this.unfold_ranges(&unfold_ranges, true, true, cx);
10467 this.buffer.update(cx, |buffer, cx| {
10468 for (range, text) in edits {
10469 buffer.edit([(range, text)], None, cx);
10470 }
10471 });
10472 this.fold_creases(refold_creases, true, window, cx);
10473 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10474 s.select(new_selections)
10475 });
10476 });
10477 }
10478
10479 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10480 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10481 let text_layout_details = &self.text_layout_details(window);
10482 self.transact(window, cx, |this, window, cx| {
10483 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10484 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10485 s.move_with(|display_map, selection| {
10486 if !selection.is_empty() {
10487 return;
10488 }
10489
10490 let mut head = selection.head();
10491 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10492 if head.column() == display_map.line_len(head.row()) {
10493 transpose_offset = display_map
10494 .buffer_snapshot
10495 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10496 }
10497
10498 if transpose_offset == 0 {
10499 return;
10500 }
10501
10502 *head.column_mut() += 1;
10503 head = display_map.clip_point(head, Bias::Right);
10504 let goal = SelectionGoal::HorizontalPosition(
10505 display_map
10506 .x_for_display_point(head, text_layout_details)
10507 .into(),
10508 );
10509 selection.collapse_to(head, goal);
10510
10511 let transpose_start = display_map
10512 .buffer_snapshot
10513 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10514 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10515 let transpose_end = display_map
10516 .buffer_snapshot
10517 .clip_offset(transpose_offset + 1, Bias::Right);
10518 if let Some(ch) =
10519 display_map.buffer_snapshot.chars_at(transpose_start).next()
10520 {
10521 edits.push((transpose_start..transpose_offset, String::new()));
10522 edits.push((transpose_end..transpose_end, ch.to_string()));
10523 }
10524 }
10525 });
10526 edits
10527 });
10528 this.buffer
10529 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10530 let selections = this.selections.all::<usize>(cx);
10531 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10532 s.select(selections);
10533 });
10534 });
10535 }
10536
10537 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10538 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10539 self.rewrap_impl(RewrapOptions::default(), cx)
10540 }
10541
10542 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10543 let buffer = self.buffer.read(cx).snapshot(cx);
10544 let selections = self.selections.all::<Point>(cx);
10545 let mut selections = selections.iter().peekable();
10546
10547 let mut edits = Vec::new();
10548 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10549
10550 while let Some(selection) = selections.next() {
10551 let mut start_row = selection.start.row;
10552 let mut end_row = selection.end.row;
10553
10554 // Skip selections that overlap with a range that has already been rewrapped.
10555 let selection_range = start_row..end_row;
10556 if rewrapped_row_ranges
10557 .iter()
10558 .any(|range| range.overlaps(&selection_range))
10559 {
10560 continue;
10561 }
10562
10563 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10564
10565 // Since not all lines in the selection may be at the same indent
10566 // level, choose the indent size that is the most common between all
10567 // of the lines.
10568 //
10569 // If there is a tie, we use the deepest indent.
10570 let (indent_size, indent_end) = {
10571 let mut indent_size_occurrences = HashMap::default();
10572 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10573
10574 for row in start_row..=end_row {
10575 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10576 rows_by_indent_size.entry(indent).or_default().push(row);
10577 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10578 }
10579
10580 let indent_size = indent_size_occurrences
10581 .into_iter()
10582 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10583 .map(|(indent, _)| indent)
10584 .unwrap_or_default();
10585 let row = rows_by_indent_size[&indent_size][0];
10586 let indent_end = Point::new(row, indent_size.len);
10587
10588 (indent_size, indent_end)
10589 };
10590
10591 let mut line_prefix = indent_size.chars().collect::<String>();
10592
10593 let mut inside_comment = false;
10594 if let Some(comment_prefix) =
10595 buffer
10596 .language_scope_at(selection.head())
10597 .and_then(|language| {
10598 language
10599 .line_comment_prefixes()
10600 .iter()
10601 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10602 .cloned()
10603 })
10604 {
10605 line_prefix.push_str(&comment_prefix);
10606 inside_comment = true;
10607 }
10608
10609 let language_settings = buffer.language_settings_at(selection.head(), cx);
10610 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10611 RewrapBehavior::InComments => inside_comment,
10612 RewrapBehavior::InSelections => !selection.is_empty(),
10613 RewrapBehavior::Anywhere => true,
10614 };
10615
10616 let should_rewrap = options.override_language_settings
10617 || allow_rewrap_based_on_language
10618 || self.hard_wrap.is_some();
10619 if !should_rewrap {
10620 continue;
10621 }
10622
10623 if selection.is_empty() {
10624 'expand_upwards: while start_row > 0 {
10625 let prev_row = start_row - 1;
10626 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10627 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10628 {
10629 start_row = prev_row;
10630 } else {
10631 break 'expand_upwards;
10632 }
10633 }
10634
10635 'expand_downwards: while end_row < buffer.max_point().row {
10636 let next_row = end_row + 1;
10637 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10638 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10639 {
10640 end_row = next_row;
10641 } else {
10642 break 'expand_downwards;
10643 }
10644 }
10645 }
10646
10647 let start = Point::new(start_row, 0);
10648 let start_offset = start.to_offset(&buffer);
10649 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10650 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10651 let Some(lines_without_prefixes) = selection_text
10652 .lines()
10653 .map(|line| {
10654 line.strip_prefix(&line_prefix)
10655 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10656 .with_context(|| {
10657 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10658 })
10659 })
10660 .collect::<Result<Vec<_>, _>>()
10661 .log_err()
10662 else {
10663 continue;
10664 };
10665
10666 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10667 buffer
10668 .language_settings_at(Point::new(start_row, 0), cx)
10669 .preferred_line_length as usize
10670 });
10671 let wrapped_text = wrap_with_prefix(
10672 line_prefix,
10673 lines_without_prefixes.join("\n"),
10674 wrap_column,
10675 tab_size,
10676 options.preserve_existing_whitespace,
10677 );
10678
10679 // TODO: should always use char-based diff while still supporting cursor behavior that
10680 // matches vim.
10681 let mut diff_options = DiffOptions::default();
10682 if options.override_language_settings {
10683 diff_options.max_word_diff_len = 0;
10684 diff_options.max_word_diff_line_count = 0;
10685 } else {
10686 diff_options.max_word_diff_len = usize::MAX;
10687 diff_options.max_word_diff_line_count = usize::MAX;
10688 }
10689
10690 for (old_range, new_text) in
10691 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10692 {
10693 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10694 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10695 edits.push((edit_start..edit_end, new_text));
10696 }
10697
10698 rewrapped_row_ranges.push(start_row..=end_row);
10699 }
10700
10701 self.buffer
10702 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10703 }
10704
10705 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10706 let mut text = String::new();
10707 let buffer = self.buffer.read(cx).snapshot(cx);
10708 let mut selections = self.selections.all::<Point>(cx);
10709 let mut clipboard_selections = Vec::with_capacity(selections.len());
10710 {
10711 let max_point = buffer.max_point();
10712 let mut is_first = true;
10713 for selection in &mut selections {
10714 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10715 if is_entire_line {
10716 selection.start = Point::new(selection.start.row, 0);
10717 if !selection.is_empty() && selection.end.column == 0 {
10718 selection.end = cmp::min(max_point, selection.end);
10719 } else {
10720 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10721 }
10722 selection.goal = SelectionGoal::None;
10723 }
10724 if is_first {
10725 is_first = false;
10726 } else {
10727 text += "\n";
10728 }
10729 let mut len = 0;
10730 for chunk in buffer.text_for_range(selection.start..selection.end) {
10731 text.push_str(chunk);
10732 len += chunk.len();
10733 }
10734 clipboard_selections.push(ClipboardSelection {
10735 len,
10736 is_entire_line,
10737 first_line_indent: buffer
10738 .indent_size_for_line(MultiBufferRow(selection.start.row))
10739 .len,
10740 });
10741 }
10742 }
10743
10744 self.transact(window, cx, |this, window, cx| {
10745 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10746 s.select(selections);
10747 });
10748 this.insert("", window, cx);
10749 });
10750 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10751 }
10752
10753 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10754 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10755 let item = self.cut_common(window, cx);
10756 cx.write_to_clipboard(item);
10757 }
10758
10759 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10760 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10761 self.change_selections(None, window, cx, |s| {
10762 s.move_with(|snapshot, sel| {
10763 if sel.is_empty() {
10764 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10765 }
10766 });
10767 });
10768 let item = self.cut_common(window, cx);
10769 cx.set_global(KillRing(item))
10770 }
10771
10772 pub fn kill_ring_yank(
10773 &mut self,
10774 _: &KillRingYank,
10775 window: &mut Window,
10776 cx: &mut Context<Self>,
10777 ) {
10778 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10779 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10780 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10781 (kill_ring.text().to_string(), kill_ring.metadata_json())
10782 } else {
10783 return;
10784 }
10785 } else {
10786 return;
10787 };
10788 self.do_paste(&text, metadata, false, window, cx);
10789 }
10790
10791 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10792 self.do_copy(true, cx);
10793 }
10794
10795 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10796 self.do_copy(false, cx);
10797 }
10798
10799 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10800 let selections = self.selections.all::<Point>(cx);
10801 let buffer = self.buffer.read(cx).read(cx);
10802 let mut text = String::new();
10803
10804 let mut clipboard_selections = Vec::with_capacity(selections.len());
10805 {
10806 let max_point = buffer.max_point();
10807 let mut is_first = true;
10808 for selection in &selections {
10809 let mut start = selection.start;
10810 let mut end = selection.end;
10811 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10812 if is_entire_line {
10813 start = Point::new(start.row, 0);
10814 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10815 }
10816
10817 let mut trimmed_selections = Vec::new();
10818 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10819 let row = MultiBufferRow(start.row);
10820 let first_indent = buffer.indent_size_for_line(row);
10821 if first_indent.len == 0 || start.column > first_indent.len {
10822 trimmed_selections.push(start..end);
10823 } else {
10824 trimmed_selections.push(
10825 Point::new(row.0, first_indent.len)
10826 ..Point::new(row.0, buffer.line_len(row)),
10827 );
10828 for row in start.row + 1..=end.row {
10829 let mut line_len = buffer.line_len(MultiBufferRow(row));
10830 if row == end.row {
10831 line_len = end.column;
10832 }
10833 if line_len == 0 {
10834 trimmed_selections
10835 .push(Point::new(row, 0)..Point::new(row, line_len));
10836 continue;
10837 }
10838 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10839 if row_indent_size.len >= first_indent.len {
10840 trimmed_selections.push(
10841 Point::new(row, first_indent.len)..Point::new(row, line_len),
10842 );
10843 } else {
10844 trimmed_selections.clear();
10845 trimmed_selections.push(start..end);
10846 break;
10847 }
10848 }
10849 }
10850 } else {
10851 trimmed_selections.push(start..end);
10852 }
10853
10854 for trimmed_range in trimmed_selections {
10855 if is_first {
10856 is_first = false;
10857 } else {
10858 text += "\n";
10859 }
10860 let mut len = 0;
10861 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10862 text.push_str(chunk);
10863 len += chunk.len();
10864 }
10865 clipboard_selections.push(ClipboardSelection {
10866 len,
10867 is_entire_line,
10868 first_line_indent: buffer
10869 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10870 .len,
10871 });
10872 }
10873 }
10874 }
10875
10876 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10877 text,
10878 clipboard_selections,
10879 ));
10880 }
10881
10882 pub fn do_paste(
10883 &mut self,
10884 text: &String,
10885 clipboard_selections: Option<Vec<ClipboardSelection>>,
10886 handle_entire_lines: bool,
10887 window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 if self.read_only(cx) {
10891 return;
10892 }
10893
10894 let clipboard_text = Cow::Borrowed(text);
10895
10896 self.transact(window, cx, |this, window, cx| {
10897 if let Some(mut clipboard_selections) = clipboard_selections {
10898 let old_selections = this.selections.all::<usize>(cx);
10899 let all_selections_were_entire_line =
10900 clipboard_selections.iter().all(|s| s.is_entire_line);
10901 let first_selection_indent_column =
10902 clipboard_selections.first().map(|s| s.first_line_indent);
10903 if clipboard_selections.len() != old_selections.len() {
10904 clipboard_selections.drain(..);
10905 }
10906 let cursor_offset = this.selections.last::<usize>(cx).head();
10907 let mut auto_indent_on_paste = true;
10908
10909 this.buffer.update(cx, |buffer, cx| {
10910 let snapshot = buffer.read(cx);
10911 auto_indent_on_paste = snapshot
10912 .language_settings_at(cursor_offset, cx)
10913 .auto_indent_on_paste;
10914
10915 let mut start_offset = 0;
10916 let mut edits = Vec::new();
10917 let mut original_indent_columns = Vec::new();
10918 for (ix, selection) in old_selections.iter().enumerate() {
10919 let to_insert;
10920 let entire_line;
10921 let original_indent_column;
10922 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10923 let end_offset = start_offset + clipboard_selection.len;
10924 to_insert = &clipboard_text[start_offset..end_offset];
10925 entire_line = clipboard_selection.is_entire_line;
10926 start_offset = end_offset + 1;
10927 original_indent_column = Some(clipboard_selection.first_line_indent);
10928 } else {
10929 to_insert = clipboard_text.as_str();
10930 entire_line = all_selections_were_entire_line;
10931 original_indent_column = first_selection_indent_column
10932 }
10933
10934 // If the corresponding selection was empty when this slice of the
10935 // clipboard text was written, then the entire line containing the
10936 // selection was copied. If this selection is also currently empty,
10937 // then paste the line before the current line of the buffer.
10938 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10939 let column = selection.start.to_point(&snapshot).column as usize;
10940 let line_start = selection.start - column;
10941 line_start..line_start
10942 } else {
10943 selection.range()
10944 };
10945
10946 edits.push((range, to_insert));
10947 original_indent_columns.push(original_indent_column);
10948 }
10949 drop(snapshot);
10950
10951 buffer.edit(
10952 edits,
10953 if auto_indent_on_paste {
10954 Some(AutoindentMode::Block {
10955 original_indent_columns,
10956 })
10957 } else {
10958 None
10959 },
10960 cx,
10961 );
10962 });
10963
10964 let selections = this.selections.all::<usize>(cx);
10965 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10966 s.select(selections)
10967 });
10968 } else {
10969 this.insert(&clipboard_text, window, cx);
10970 }
10971 });
10972 }
10973
10974 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10975 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10976 if let Some(item) = cx.read_from_clipboard() {
10977 let entries = item.entries();
10978
10979 match entries.first() {
10980 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10981 // of all the pasted entries.
10982 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10983 .do_paste(
10984 clipboard_string.text(),
10985 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10986 true,
10987 window,
10988 cx,
10989 ),
10990 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10991 }
10992 }
10993 }
10994
10995 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10996 if self.read_only(cx) {
10997 return;
10998 }
10999
11000 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11001
11002 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11003 if let Some((selections, _)) =
11004 self.selection_history.transaction(transaction_id).cloned()
11005 {
11006 self.change_selections(None, window, cx, |s| {
11007 s.select_anchors(selections.to_vec());
11008 });
11009 } else {
11010 log::error!(
11011 "No entry in selection_history found for undo. \
11012 This may correspond to a bug where undo does not update the selection. \
11013 If this is occurring, please add details to \
11014 https://github.com/zed-industries/zed/issues/22692"
11015 );
11016 }
11017 self.request_autoscroll(Autoscroll::fit(), cx);
11018 self.unmark_text(window, cx);
11019 self.refresh_inline_completion(true, false, window, cx);
11020 cx.emit(EditorEvent::Edited { transaction_id });
11021 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11022 }
11023 }
11024
11025 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11026 if self.read_only(cx) {
11027 return;
11028 }
11029
11030 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11031
11032 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11033 if let Some((_, Some(selections))) =
11034 self.selection_history.transaction(transaction_id).cloned()
11035 {
11036 self.change_selections(None, window, cx, |s| {
11037 s.select_anchors(selections.to_vec());
11038 });
11039 } else {
11040 log::error!(
11041 "No entry in selection_history found for redo. \
11042 This may correspond to a bug where undo does not update the selection. \
11043 If this is occurring, please add details to \
11044 https://github.com/zed-industries/zed/issues/22692"
11045 );
11046 }
11047 self.request_autoscroll(Autoscroll::fit(), cx);
11048 self.unmark_text(window, cx);
11049 self.refresh_inline_completion(true, false, window, cx);
11050 cx.emit(EditorEvent::Edited { transaction_id });
11051 }
11052 }
11053
11054 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11055 self.buffer
11056 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11057 }
11058
11059 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11060 self.buffer
11061 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11062 }
11063
11064 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11067 s.move_with(|map, selection| {
11068 let cursor = if selection.is_empty() {
11069 movement::left(map, selection.start)
11070 } else {
11071 selection.start
11072 };
11073 selection.collapse_to(cursor, SelectionGoal::None);
11074 });
11075 })
11076 }
11077
11078 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11079 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11080 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11081 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11082 })
11083 }
11084
11085 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11086 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11088 s.move_with(|map, selection| {
11089 let cursor = if selection.is_empty() {
11090 movement::right(map, selection.end)
11091 } else {
11092 selection.end
11093 };
11094 selection.collapse_to(cursor, SelectionGoal::None)
11095 });
11096 })
11097 }
11098
11099 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11100 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11101 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11102 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11103 })
11104 }
11105
11106 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11107 if self.take_rename(true, window, cx).is_some() {
11108 return;
11109 }
11110
11111 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11112 cx.propagate();
11113 return;
11114 }
11115
11116 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11117
11118 let text_layout_details = &self.text_layout_details(window);
11119 let selection_count = self.selections.count();
11120 let first_selection = self.selections.first_anchor();
11121
11122 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11123 s.move_with(|map, selection| {
11124 if !selection.is_empty() {
11125 selection.goal = SelectionGoal::None;
11126 }
11127 let (cursor, goal) = movement::up(
11128 map,
11129 selection.start,
11130 selection.goal,
11131 false,
11132 text_layout_details,
11133 );
11134 selection.collapse_to(cursor, goal);
11135 });
11136 });
11137
11138 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11139 {
11140 cx.propagate();
11141 }
11142 }
11143
11144 pub fn move_up_by_lines(
11145 &mut self,
11146 action: &MoveUpByLines,
11147 window: &mut Window,
11148 cx: &mut Context<Self>,
11149 ) {
11150 if self.take_rename(true, window, cx).is_some() {
11151 return;
11152 }
11153
11154 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11155 cx.propagate();
11156 return;
11157 }
11158
11159 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11160
11161 let text_layout_details = &self.text_layout_details(window);
11162
11163 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11164 s.move_with(|map, selection| {
11165 if !selection.is_empty() {
11166 selection.goal = SelectionGoal::None;
11167 }
11168 let (cursor, goal) = movement::up_by_rows(
11169 map,
11170 selection.start,
11171 action.lines,
11172 selection.goal,
11173 false,
11174 text_layout_details,
11175 );
11176 selection.collapse_to(cursor, goal);
11177 });
11178 })
11179 }
11180
11181 pub fn move_down_by_lines(
11182 &mut self,
11183 action: &MoveDownByLines,
11184 window: &mut Window,
11185 cx: &mut Context<Self>,
11186 ) {
11187 if self.take_rename(true, window, cx).is_some() {
11188 return;
11189 }
11190
11191 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11192 cx.propagate();
11193 return;
11194 }
11195
11196 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11197
11198 let text_layout_details = &self.text_layout_details(window);
11199
11200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11201 s.move_with(|map, selection| {
11202 if !selection.is_empty() {
11203 selection.goal = SelectionGoal::None;
11204 }
11205 let (cursor, goal) = movement::down_by_rows(
11206 map,
11207 selection.start,
11208 action.lines,
11209 selection.goal,
11210 false,
11211 text_layout_details,
11212 );
11213 selection.collapse_to(cursor, goal);
11214 });
11215 })
11216 }
11217
11218 pub fn select_down_by_lines(
11219 &mut self,
11220 action: &SelectDownByLines,
11221 window: &mut Window,
11222 cx: &mut Context<Self>,
11223 ) {
11224 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11225 let text_layout_details = &self.text_layout_details(window);
11226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11227 s.move_heads_with(|map, head, goal| {
11228 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11229 })
11230 })
11231 }
11232
11233 pub fn select_up_by_lines(
11234 &mut self,
11235 action: &SelectUpByLines,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11240 let text_layout_details = &self.text_layout_details(window);
11241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11242 s.move_heads_with(|map, head, goal| {
11243 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11244 })
11245 })
11246 }
11247
11248 pub fn select_page_up(
11249 &mut self,
11250 _: &SelectPageUp,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 let Some(row_count) = self.visible_row_count() else {
11255 return;
11256 };
11257
11258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11259
11260 let text_layout_details = &self.text_layout_details(window);
11261
11262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11263 s.move_heads_with(|map, head, goal| {
11264 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11265 })
11266 })
11267 }
11268
11269 pub fn move_page_up(
11270 &mut self,
11271 action: &MovePageUp,
11272 window: &mut Window,
11273 cx: &mut Context<Self>,
11274 ) {
11275 if self.take_rename(true, window, cx).is_some() {
11276 return;
11277 }
11278
11279 if self
11280 .context_menu
11281 .borrow_mut()
11282 .as_mut()
11283 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11284 .unwrap_or(false)
11285 {
11286 return;
11287 }
11288
11289 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11290 cx.propagate();
11291 return;
11292 }
11293
11294 let Some(row_count) = self.visible_row_count() else {
11295 return;
11296 };
11297
11298 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11299
11300 let autoscroll = if action.center_cursor {
11301 Autoscroll::center()
11302 } else {
11303 Autoscroll::fit()
11304 };
11305
11306 let text_layout_details = &self.text_layout_details(window);
11307
11308 self.change_selections(Some(autoscroll), window, cx, |s| {
11309 s.move_with(|map, selection| {
11310 if !selection.is_empty() {
11311 selection.goal = SelectionGoal::None;
11312 }
11313 let (cursor, goal) = movement::up_by_rows(
11314 map,
11315 selection.end,
11316 row_count,
11317 selection.goal,
11318 false,
11319 text_layout_details,
11320 );
11321 selection.collapse_to(cursor, goal);
11322 });
11323 });
11324 }
11325
11326 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11327 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11328 let text_layout_details = &self.text_layout_details(window);
11329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11330 s.move_heads_with(|map, head, goal| {
11331 movement::up(map, head, goal, false, text_layout_details)
11332 })
11333 })
11334 }
11335
11336 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11337 self.take_rename(true, window, cx);
11338
11339 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11340 cx.propagate();
11341 return;
11342 }
11343
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11345
11346 let text_layout_details = &self.text_layout_details(window);
11347 let selection_count = self.selections.count();
11348 let first_selection = self.selections.first_anchor();
11349
11350 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11351 s.move_with(|map, selection| {
11352 if !selection.is_empty() {
11353 selection.goal = SelectionGoal::None;
11354 }
11355 let (cursor, goal) = movement::down(
11356 map,
11357 selection.end,
11358 selection.goal,
11359 false,
11360 text_layout_details,
11361 );
11362 selection.collapse_to(cursor, goal);
11363 });
11364 });
11365
11366 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11367 {
11368 cx.propagate();
11369 }
11370 }
11371
11372 pub fn select_page_down(
11373 &mut self,
11374 _: &SelectPageDown,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 let Some(row_count) = self.visible_row_count() else {
11379 return;
11380 };
11381
11382 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11383
11384 let text_layout_details = &self.text_layout_details(window);
11385
11386 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11387 s.move_heads_with(|map, head, goal| {
11388 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11389 })
11390 })
11391 }
11392
11393 pub fn move_page_down(
11394 &mut self,
11395 action: &MovePageDown,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 if self.take_rename(true, window, cx).is_some() {
11400 return;
11401 }
11402
11403 if self
11404 .context_menu
11405 .borrow_mut()
11406 .as_mut()
11407 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11408 .unwrap_or(false)
11409 {
11410 return;
11411 }
11412
11413 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11414 cx.propagate();
11415 return;
11416 }
11417
11418 let Some(row_count) = self.visible_row_count() else {
11419 return;
11420 };
11421
11422 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11423
11424 let autoscroll = if action.center_cursor {
11425 Autoscroll::center()
11426 } else {
11427 Autoscroll::fit()
11428 };
11429
11430 let text_layout_details = &self.text_layout_details(window);
11431 self.change_selections(Some(autoscroll), window, cx, |s| {
11432 s.move_with(|map, selection| {
11433 if !selection.is_empty() {
11434 selection.goal = SelectionGoal::None;
11435 }
11436 let (cursor, goal) = movement::down_by_rows(
11437 map,
11438 selection.end,
11439 row_count,
11440 selection.goal,
11441 false,
11442 text_layout_details,
11443 );
11444 selection.collapse_to(cursor, goal);
11445 });
11446 });
11447 }
11448
11449 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11450 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11451 let text_layout_details = &self.text_layout_details(window);
11452 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11453 s.move_heads_with(|map, head, goal| {
11454 movement::down(map, head, goal, false, text_layout_details)
11455 })
11456 });
11457 }
11458
11459 pub fn context_menu_first(
11460 &mut self,
11461 _: &ContextMenuFirst,
11462 _window: &mut Window,
11463 cx: &mut Context<Self>,
11464 ) {
11465 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11466 context_menu.select_first(self.completion_provider.as_deref(), cx);
11467 }
11468 }
11469
11470 pub fn context_menu_prev(
11471 &mut self,
11472 _: &ContextMenuPrevious,
11473 _window: &mut Window,
11474 cx: &mut Context<Self>,
11475 ) {
11476 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11477 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11478 }
11479 }
11480
11481 pub fn context_menu_next(
11482 &mut self,
11483 _: &ContextMenuNext,
11484 _window: &mut Window,
11485 cx: &mut Context<Self>,
11486 ) {
11487 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11488 context_menu.select_next(self.completion_provider.as_deref(), cx);
11489 }
11490 }
11491
11492 pub fn context_menu_last(
11493 &mut self,
11494 _: &ContextMenuLast,
11495 _window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11499 context_menu.select_last(self.completion_provider.as_deref(), cx);
11500 }
11501 }
11502
11503 pub fn move_to_previous_word_start(
11504 &mut self,
11505 _: &MoveToPreviousWordStart,
11506 window: &mut Window,
11507 cx: &mut Context<Self>,
11508 ) {
11509 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11510 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11511 s.move_cursors_with(|map, head, _| {
11512 (
11513 movement::previous_word_start(map, head),
11514 SelectionGoal::None,
11515 )
11516 });
11517 })
11518 }
11519
11520 pub fn move_to_previous_subword_start(
11521 &mut self,
11522 _: &MoveToPreviousSubwordStart,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11527 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11528 s.move_cursors_with(|map, head, _| {
11529 (
11530 movement::previous_subword_start(map, head),
11531 SelectionGoal::None,
11532 )
11533 });
11534 })
11535 }
11536
11537 pub fn select_to_previous_word_start(
11538 &mut self,
11539 _: &SelectToPreviousWordStart,
11540 window: &mut Window,
11541 cx: &mut Context<Self>,
11542 ) {
11543 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11544 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11545 s.move_heads_with(|map, head, _| {
11546 (
11547 movement::previous_word_start(map, head),
11548 SelectionGoal::None,
11549 )
11550 });
11551 })
11552 }
11553
11554 pub fn select_to_previous_subword_start(
11555 &mut self,
11556 _: &SelectToPreviousSubwordStart,
11557 window: &mut Window,
11558 cx: &mut Context<Self>,
11559 ) {
11560 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11561 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11562 s.move_heads_with(|map, head, _| {
11563 (
11564 movement::previous_subword_start(map, head),
11565 SelectionGoal::None,
11566 )
11567 });
11568 })
11569 }
11570
11571 pub fn delete_to_previous_word_start(
11572 &mut self,
11573 action: &DeleteToPreviousWordStart,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 ) {
11577 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11578 self.transact(window, cx, |this, window, cx| {
11579 this.select_autoclose_pair(window, cx);
11580 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11581 s.move_with(|map, selection| {
11582 if selection.is_empty() {
11583 let cursor = if action.ignore_newlines {
11584 movement::previous_word_start(map, selection.head())
11585 } else {
11586 movement::previous_word_start_or_newline(map, selection.head())
11587 };
11588 selection.set_head(cursor, SelectionGoal::None);
11589 }
11590 });
11591 });
11592 this.insert("", window, cx);
11593 });
11594 }
11595
11596 pub fn delete_to_previous_subword_start(
11597 &mut self,
11598 _: &DeleteToPreviousSubwordStart,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) {
11602 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11603 self.transact(window, cx, |this, window, cx| {
11604 this.select_autoclose_pair(window, cx);
11605 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11606 s.move_with(|map, selection| {
11607 if selection.is_empty() {
11608 let cursor = movement::previous_subword_start(map, selection.head());
11609 selection.set_head(cursor, SelectionGoal::None);
11610 }
11611 });
11612 });
11613 this.insert("", window, cx);
11614 });
11615 }
11616
11617 pub fn move_to_next_word_end(
11618 &mut self,
11619 _: &MoveToNextWordEnd,
11620 window: &mut Window,
11621 cx: &mut Context<Self>,
11622 ) {
11623 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11624 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11625 s.move_cursors_with(|map, head, _| {
11626 (movement::next_word_end(map, head), SelectionGoal::None)
11627 });
11628 })
11629 }
11630
11631 pub fn move_to_next_subword_end(
11632 &mut self,
11633 _: &MoveToNextSubwordEnd,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11639 s.move_cursors_with(|map, head, _| {
11640 (movement::next_subword_end(map, head), SelectionGoal::None)
11641 });
11642 })
11643 }
11644
11645 pub fn select_to_next_word_end(
11646 &mut self,
11647 _: &SelectToNextWordEnd,
11648 window: &mut Window,
11649 cx: &mut Context<Self>,
11650 ) {
11651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11653 s.move_heads_with(|map, head, _| {
11654 (movement::next_word_end(map, head), SelectionGoal::None)
11655 });
11656 })
11657 }
11658
11659 pub fn select_to_next_subword_end(
11660 &mut self,
11661 _: &SelectToNextSubwordEnd,
11662 window: &mut Window,
11663 cx: &mut Context<Self>,
11664 ) {
11665 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11666 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11667 s.move_heads_with(|map, head, _| {
11668 (movement::next_subword_end(map, head), SelectionGoal::None)
11669 });
11670 })
11671 }
11672
11673 pub fn delete_to_next_word_end(
11674 &mut self,
11675 action: &DeleteToNextWordEnd,
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 = if action.ignore_newlines {
11685 movement::next_word_end(map, selection.head())
11686 } else {
11687 movement::next_word_end_or_newline(map, selection.head())
11688 };
11689 selection.set_head(cursor, SelectionGoal::None);
11690 }
11691 });
11692 });
11693 this.insert("", window, cx);
11694 });
11695 }
11696
11697 pub fn delete_to_next_subword_end(
11698 &mut self,
11699 _: &DeleteToNextSubwordEnd,
11700 window: &mut Window,
11701 cx: &mut Context<Self>,
11702 ) {
11703 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11704 self.transact(window, cx, |this, window, cx| {
11705 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11706 s.move_with(|map, selection| {
11707 if selection.is_empty() {
11708 let cursor = movement::next_subword_end(map, selection.head());
11709 selection.set_head(cursor, SelectionGoal::None);
11710 }
11711 });
11712 });
11713 this.insert("", window, cx);
11714 });
11715 }
11716
11717 pub fn move_to_beginning_of_line(
11718 &mut self,
11719 action: &MoveToBeginningOfLine,
11720 window: &mut Window,
11721 cx: &mut Context<Self>,
11722 ) {
11723 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11724 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11725 s.move_cursors_with(|map, head, _| {
11726 (
11727 movement::indented_line_beginning(
11728 map,
11729 head,
11730 action.stop_at_soft_wraps,
11731 action.stop_at_indent,
11732 ),
11733 SelectionGoal::None,
11734 )
11735 });
11736 })
11737 }
11738
11739 pub fn select_to_beginning_of_line(
11740 &mut self,
11741 action: &SelectToBeginningOfLine,
11742 window: &mut Window,
11743 cx: &mut Context<Self>,
11744 ) {
11745 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11747 s.move_heads_with(|map, head, _| {
11748 (
11749 movement::indented_line_beginning(
11750 map,
11751 head,
11752 action.stop_at_soft_wraps,
11753 action.stop_at_indent,
11754 ),
11755 SelectionGoal::None,
11756 )
11757 });
11758 });
11759 }
11760
11761 pub fn delete_to_beginning_of_line(
11762 &mut self,
11763 action: &DeleteToBeginningOfLine,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11768 self.transact(window, cx, |this, window, cx| {
11769 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11770 s.move_with(|_, selection| {
11771 selection.reversed = true;
11772 });
11773 });
11774
11775 this.select_to_beginning_of_line(
11776 &SelectToBeginningOfLine {
11777 stop_at_soft_wraps: false,
11778 stop_at_indent: action.stop_at_indent,
11779 },
11780 window,
11781 cx,
11782 );
11783 this.backspace(&Backspace, window, cx);
11784 });
11785 }
11786
11787 pub fn move_to_end_of_line(
11788 &mut self,
11789 action: &MoveToEndOfLine,
11790 window: &mut Window,
11791 cx: &mut Context<Self>,
11792 ) {
11793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11795 s.move_cursors_with(|map, head, _| {
11796 (
11797 movement::line_end(map, head, action.stop_at_soft_wraps),
11798 SelectionGoal::None,
11799 )
11800 });
11801 })
11802 }
11803
11804 pub fn select_to_end_of_line(
11805 &mut self,
11806 action: &SelectToEndOfLine,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11811 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11812 s.move_heads_with(|map, head, _| {
11813 (
11814 movement::line_end(map, head, action.stop_at_soft_wraps),
11815 SelectionGoal::None,
11816 )
11817 });
11818 })
11819 }
11820
11821 pub fn delete_to_end_of_line(
11822 &mut self,
11823 _: &DeleteToEndOfLine,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 ) {
11827 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11828 self.transact(window, cx, |this, window, cx| {
11829 this.select_to_end_of_line(
11830 &SelectToEndOfLine {
11831 stop_at_soft_wraps: false,
11832 },
11833 window,
11834 cx,
11835 );
11836 this.delete(&Delete, window, cx);
11837 });
11838 }
11839
11840 pub fn cut_to_end_of_line(
11841 &mut self,
11842 _: &CutToEndOfLine,
11843 window: &mut Window,
11844 cx: &mut Context<Self>,
11845 ) {
11846 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11847 self.transact(window, cx, |this, window, cx| {
11848 this.select_to_end_of_line(
11849 &SelectToEndOfLine {
11850 stop_at_soft_wraps: false,
11851 },
11852 window,
11853 cx,
11854 );
11855 this.cut(&Cut, window, cx);
11856 });
11857 }
11858
11859 pub fn move_to_start_of_paragraph(
11860 &mut self,
11861 _: &MoveToStartOfParagraph,
11862 window: &mut Window,
11863 cx: &mut Context<Self>,
11864 ) {
11865 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11866 cx.propagate();
11867 return;
11868 }
11869 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11870 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11871 s.move_with(|map, selection| {
11872 selection.collapse_to(
11873 movement::start_of_paragraph(map, selection.head(), 1),
11874 SelectionGoal::None,
11875 )
11876 });
11877 })
11878 }
11879
11880 pub fn move_to_end_of_paragraph(
11881 &mut self,
11882 _: &MoveToEndOfParagraph,
11883 window: &mut Window,
11884 cx: &mut Context<Self>,
11885 ) {
11886 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11887 cx.propagate();
11888 return;
11889 }
11890 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11891 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11892 s.move_with(|map, selection| {
11893 selection.collapse_to(
11894 movement::end_of_paragraph(map, selection.head(), 1),
11895 SelectionGoal::None,
11896 )
11897 });
11898 })
11899 }
11900
11901 pub fn select_to_start_of_paragraph(
11902 &mut self,
11903 _: &SelectToStartOfParagraph,
11904 window: &mut Window,
11905 cx: &mut Context<Self>,
11906 ) {
11907 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11908 cx.propagate();
11909 return;
11910 }
11911 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11912 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11913 s.move_heads_with(|map, head, _| {
11914 (
11915 movement::start_of_paragraph(map, head, 1),
11916 SelectionGoal::None,
11917 )
11918 });
11919 })
11920 }
11921
11922 pub fn select_to_end_of_paragraph(
11923 &mut self,
11924 _: &SelectToEndOfParagraph,
11925 window: &mut Window,
11926 cx: &mut Context<Self>,
11927 ) {
11928 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11929 cx.propagate();
11930 return;
11931 }
11932 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11933 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11934 s.move_heads_with(|map, head, _| {
11935 (
11936 movement::end_of_paragraph(map, head, 1),
11937 SelectionGoal::None,
11938 )
11939 });
11940 })
11941 }
11942
11943 pub fn move_to_start_of_excerpt(
11944 &mut self,
11945 _: &MoveToStartOfExcerpt,
11946 window: &mut Window,
11947 cx: &mut Context<Self>,
11948 ) {
11949 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11950 cx.propagate();
11951 return;
11952 }
11953 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11954 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11955 s.move_with(|map, selection| {
11956 selection.collapse_to(
11957 movement::start_of_excerpt(
11958 map,
11959 selection.head(),
11960 workspace::searchable::Direction::Prev,
11961 ),
11962 SelectionGoal::None,
11963 )
11964 });
11965 })
11966 }
11967
11968 pub fn move_to_start_of_next_excerpt(
11969 &mut self,
11970 _: &MoveToStartOfNextExcerpt,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) {
11974 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11975 cx.propagate();
11976 return;
11977 }
11978
11979 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11980 s.move_with(|map, selection| {
11981 selection.collapse_to(
11982 movement::start_of_excerpt(
11983 map,
11984 selection.head(),
11985 workspace::searchable::Direction::Next,
11986 ),
11987 SelectionGoal::None,
11988 )
11989 });
11990 })
11991 }
11992
11993 pub fn move_to_end_of_excerpt(
11994 &mut self,
11995 _: &MoveToEndOfExcerpt,
11996 window: &mut Window,
11997 cx: &mut Context<Self>,
11998 ) {
11999 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12000 cx.propagate();
12001 return;
12002 }
12003 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12004 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12005 s.move_with(|map, selection| {
12006 selection.collapse_to(
12007 movement::end_of_excerpt(
12008 map,
12009 selection.head(),
12010 workspace::searchable::Direction::Next,
12011 ),
12012 SelectionGoal::None,
12013 )
12014 });
12015 })
12016 }
12017
12018 pub fn move_to_end_of_previous_excerpt(
12019 &mut self,
12020 _: &MoveToEndOfPreviousExcerpt,
12021 window: &mut Window,
12022 cx: &mut Context<Self>,
12023 ) {
12024 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12025 cx.propagate();
12026 return;
12027 }
12028 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12029 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12030 s.move_with(|map, selection| {
12031 selection.collapse_to(
12032 movement::end_of_excerpt(
12033 map,
12034 selection.head(),
12035 workspace::searchable::Direction::Prev,
12036 ),
12037 SelectionGoal::None,
12038 )
12039 });
12040 })
12041 }
12042
12043 pub fn select_to_start_of_excerpt(
12044 &mut self,
12045 _: &SelectToStartOfExcerpt,
12046 window: &mut Window,
12047 cx: &mut Context<Self>,
12048 ) {
12049 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12050 cx.propagate();
12051 return;
12052 }
12053 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12054 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12055 s.move_heads_with(|map, head, _| {
12056 (
12057 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12058 SelectionGoal::None,
12059 )
12060 });
12061 })
12062 }
12063
12064 pub fn select_to_start_of_next_excerpt(
12065 &mut self,
12066 _: &SelectToStartOfNextExcerpt,
12067 window: &mut Window,
12068 cx: &mut Context<Self>,
12069 ) {
12070 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12071 cx.propagate();
12072 return;
12073 }
12074 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12076 s.move_heads_with(|map, head, _| {
12077 (
12078 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12079 SelectionGoal::None,
12080 )
12081 });
12082 })
12083 }
12084
12085 pub fn select_to_end_of_excerpt(
12086 &mut self,
12087 _: &SelectToEndOfExcerpt,
12088 window: &mut Window,
12089 cx: &mut Context<Self>,
12090 ) {
12091 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12092 cx.propagate();
12093 return;
12094 }
12095 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12096 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12097 s.move_heads_with(|map, head, _| {
12098 (
12099 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12100 SelectionGoal::None,
12101 )
12102 });
12103 })
12104 }
12105
12106 pub fn select_to_end_of_previous_excerpt(
12107 &mut self,
12108 _: &SelectToEndOfPreviousExcerpt,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12113 cx.propagate();
12114 return;
12115 }
12116 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12117 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12118 s.move_heads_with(|map, head, _| {
12119 (
12120 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12121 SelectionGoal::None,
12122 )
12123 });
12124 })
12125 }
12126
12127 pub fn move_to_beginning(
12128 &mut self,
12129 _: &MoveToBeginning,
12130 window: &mut Window,
12131 cx: &mut Context<Self>,
12132 ) {
12133 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12134 cx.propagate();
12135 return;
12136 }
12137 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12138 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12139 s.select_ranges(vec![0..0]);
12140 });
12141 }
12142
12143 pub fn select_to_beginning(
12144 &mut self,
12145 _: &SelectToBeginning,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 let mut selection = self.selections.last::<Point>(cx);
12150 selection.set_head(Point::zero(), SelectionGoal::None);
12151 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12152 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12153 s.select(vec![selection]);
12154 });
12155 }
12156
12157 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12158 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12159 cx.propagate();
12160 return;
12161 }
12162 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12163 let cursor = self.buffer.read(cx).read(cx).len();
12164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12165 s.select_ranges(vec![cursor..cursor])
12166 });
12167 }
12168
12169 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12170 self.nav_history = nav_history;
12171 }
12172
12173 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12174 self.nav_history.as_ref()
12175 }
12176
12177 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12178 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12179 }
12180
12181 fn push_to_nav_history(
12182 &mut self,
12183 cursor_anchor: Anchor,
12184 new_position: Option<Point>,
12185 is_deactivate: bool,
12186 cx: &mut Context<Self>,
12187 ) {
12188 if let Some(nav_history) = self.nav_history.as_mut() {
12189 let buffer = self.buffer.read(cx).read(cx);
12190 let cursor_position = cursor_anchor.to_point(&buffer);
12191 let scroll_state = self.scroll_manager.anchor();
12192 let scroll_top_row = scroll_state.top_row(&buffer);
12193 drop(buffer);
12194
12195 if let Some(new_position) = new_position {
12196 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12197 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12198 return;
12199 }
12200 }
12201
12202 nav_history.push(
12203 Some(NavigationData {
12204 cursor_anchor,
12205 cursor_position,
12206 scroll_anchor: scroll_state,
12207 scroll_top_row,
12208 }),
12209 cx,
12210 );
12211 cx.emit(EditorEvent::PushedToNavHistory {
12212 anchor: cursor_anchor,
12213 is_deactivate,
12214 })
12215 }
12216 }
12217
12218 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12220 let buffer = self.buffer.read(cx).snapshot(cx);
12221 let mut selection = self.selections.first::<usize>(cx);
12222 selection.set_head(buffer.len(), SelectionGoal::None);
12223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12224 s.select(vec![selection]);
12225 });
12226 }
12227
12228 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12229 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12230 let end = self.buffer.read(cx).read(cx).len();
12231 self.change_selections(None, window, cx, |s| {
12232 s.select_ranges(vec![0..end]);
12233 });
12234 }
12235
12236 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12237 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12239 let mut selections = self.selections.all::<Point>(cx);
12240 let max_point = display_map.buffer_snapshot.max_point();
12241 for selection in &mut selections {
12242 let rows = selection.spanned_rows(true, &display_map);
12243 selection.start = Point::new(rows.start.0, 0);
12244 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12245 selection.reversed = false;
12246 }
12247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12248 s.select(selections);
12249 });
12250 }
12251
12252 pub fn split_selection_into_lines(
12253 &mut self,
12254 _: &SplitSelectionIntoLines,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 let selections = self
12259 .selections
12260 .all::<Point>(cx)
12261 .into_iter()
12262 .map(|selection| selection.start..selection.end)
12263 .collect::<Vec<_>>();
12264 self.unfold_ranges(&selections, true, true, cx);
12265
12266 let mut new_selection_ranges = Vec::new();
12267 {
12268 let buffer = self.buffer.read(cx).read(cx);
12269 for selection in selections {
12270 for row in selection.start.row..selection.end.row {
12271 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12272 new_selection_ranges.push(cursor..cursor);
12273 }
12274
12275 let is_multiline_selection = selection.start.row != selection.end.row;
12276 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12277 // so this action feels more ergonomic when paired with other selection operations
12278 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12279 if !should_skip_last {
12280 new_selection_ranges.push(selection.end..selection.end);
12281 }
12282 }
12283 }
12284 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12285 s.select_ranges(new_selection_ranges);
12286 });
12287 }
12288
12289 pub fn add_selection_above(
12290 &mut self,
12291 _: &AddSelectionAbove,
12292 window: &mut Window,
12293 cx: &mut Context<Self>,
12294 ) {
12295 self.add_selection(true, window, cx);
12296 }
12297
12298 pub fn add_selection_below(
12299 &mut self,
12300 _: &AddSelectionBelow,
12301 window: &mut Window,
12302 cx: &mut Context<Self>,
12303 ) {
12304 self.add_selection(false, window, cx);
12305 }
12306
12307 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12308 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12309
12310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12311 let mut selections = self.selections.all::<Point>(cx);
12312 let text_layout_details = self.text_layout_details(window);
12313 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12314 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12315 let range = oldest_selection.display_range(&display_map).sorted();
12316
12317 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12318 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12319 let positions = start_x.min(end_x)..start_x.max(end_x);
12320
12321 selections.clear();
12322 let mut stack = Vec::new();
12323 for row in range.start.row().0..=range.end.row().0 {
12324 if let Some(selection) = self.selections.build_columnar_selection(
12325 &display_map,
12326 DisplayRow(row),
12327 &positions,
12328 oldest_selection.reversed,
12329 &text_layout_details,
12330 ) {
12331 stack.push(selection.id);
12332 selections.push(selection);
12333 }
12334 }
12335
12336 if above {
12337 stack.reverse();
12338 }
12339
12340 AddSelectionsState { above, stack }
12341 });
12342
12343 let last_added_selection = *state.stack.last().unwrap();
12344 let mut new_selections = Vec::new();
12345 if above == state.above {
12346 let end_row = if above {
12347 DisplayRow(0)
12348 } else {
12349 display_map.max_point().row()
12350 };
12351
12352 'outer: for selection in selections {
12353 if selection.id == last_added_selection {
12354 let range = selection.display_range(&display_map).sorted();
12355 debug_assert_eq!(range.start.row(), range.end.row());
12356 let mut row = range.start.row();
12357 let positions =
12358 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12359 px(start)..px(end)
12360 } else {
12361 let start_x =
12362 display_map.x_for_display_point(range.start, &text_layout_details);
12363 let end_x =
12364 display_map.x_for_display_point(range.end, &text_layout_details);
12365 start_x.min(end_x)..start_x.max(end_x)
12366 };
12367
12368 while row != end_row {
12369 if above {
12370 row.0 -= 1;
12371 } else {
12372 row.0 += 1;
12373 }
12374
12375 if let Some(new_selection) = self.selections.build_columnar_selection(
12376 &display_map,
12377 row,
12378 &positions,
12379 selection.reversed,
12380 &text_layout_details,
12381 ) {
12382 state.stack.push(new_selection.id);
12383 if above {
12384 new_selections.push(new_selection);
12385 new_selections.push(selection);
12386 } else {
12387 new_selections.push(selection);
12388 new_selections.push(new_selection);
12389 }
12390
12391 continue 'outer;
12392 }
12393 }
12394 }
12395
12396 new_selections.push(selection);
12397 }
12398 } else {
12399 new_selections = selections;
12400 new_selections.retain(|s| s.id != last_added_selection);
12401 state.stack.pop();
12402 }
12403
12404 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12405 s.select(new_selections);
12406 });
12407 if state.stack.len() > 1 {
12408 self.add_selections_state = Some(state);
12409 }
12410 }
12411
12412 fn select_match_ranges(
12413 &mut self,
12414 range: Range<usize>,
12415 reversed: bool,
12416 replace_newest: bool,
12417 auto_scroll: Option<Autoscroll>,
12418 window: &mut Window,
12419 cx: &mut Context<Editor>,
12420 ) {
12421 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12422 self.change_selections(auto_scroll, window, cx, |s| {
12423 if replace_newest {
12424 s.delete(s.newest_anchor().id);
12425 }
12426 if reversed {
12427 s.insert_range(range.end..range.start);
12428 } else {
12429 s.insert_range(range);
12430 }
12431 });
12432 }
12433
12434 pub fn select_next_match_internal(
12435 &mut self,
12436 display_map: &DisplaySnapshot,
12437 replace_newest: bool,
12438 autoscroll: Option<Autoscroll>,
12439 window: &mut Window,
12440 cx: &mut Context<Self>,
12441 ) -> Result<()> {
12442 let buffer = &display_map.buffer_snapshot;
12443 let mut selections = self.selections.all::<usize>(cx);
12444 if let Some(mut select_next_state) = self.select_next_state.take() {
12445 let query = &select_next_state.query;
12446 if !select_next_state.done {
12447 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12448 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12449 let mut next_selected_range = None;
12450
12451 let bytes_after_last_selection =
12452 buffer.bytes_in_range(last_selection.end..buffer.len());
12453 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12454 let query_matches = query
12455 .stream_find_iter(bytes_after_last_selection)
12456 .map(|result| (last_selection.end, result))
12457 .chain(
12458 query
12459 .stream_find_iter(bytes_before_first_selection)
12460 .map(|result| (0, result)),
12461 );
12462
12463 for (start_offset, query_match) in query_matches {
12464 let query_match = query_match.unwrap(); // can only fail due to I/O
12465 let offset_range =
12466 start_offset + query_match.start()..start_offset + query_match.end();
12467 let display_range = offset_range.start.to_display_point(display_map)
12468 ..offset_range.end.to_display_point(display_map);
12469
12470 if !select_next_state.wordwise
12471 || (!movement::is_inside_word(display_map, display_range.start)
12472 && !movement::is_inside_word(display_map, display_range.end))
12473 {
12474 // TODO: This is n^2, because we might check all the selections
12475 if !selections
12476 .iter()
12477 .any(|selection| selection.range().overlaps(&offset_range))
12478 {
12479 next_selected_range = Some(offset_range);
12480 break;
12481 }
12482 }
12483 }
12484
12485 if let Some(next_selected_range) = next_selected_range {
12486 self.select_match_ranges(
12487 next_selected_range,
12488 last_selection.reversed,
12489 replace_newest,
12490 autoscroll,
12491 window,
12492 cx,
12493 );
12494 } else {
12495 select_next_state.done = true;
12496 }
12497 }
12498
12499 self.select_next_state = Some(select_next_state);
12500 } else {
12501 let mut only_carets = true;
12502 let mut same_text_selected = true;
12503 let mut selected_text = None;
12504
12505 let mut selections_iter = selections.iter().peekable();
12506 while let Some(selection) = selections_iter.next() {
12507 if selection.start != selection.end {
12508 only_carets = false;
12509 }
12510
12511 if same_text_selected {
12512 if selected_text.is_none() {
12513 selected_text =
12514 Some(buffer.text_for_range(selection.range()).collect::<String>());
12515 }
12516
12517 if let Some(next_selection) = selections_iter.peek() {
12518 if next_selection.range().len() == selection.range().len() {
12519 let next_selected_text = buffer
12520 .text_for_range(next_selection.range())
12521 .collect::<String>();
12522 if Some(next_selected_text) != selected_text {
12523 same_text_selected = false;
12524 selected_text = None;
12525 }
12526 } else {
12527 same_text_selected = false;
12528 selected_text = None;
12529 }
12530 }
12531 }
12532 }
12533
12534 if only_carets {
12535 for selection in &mut selections {
12536 let word_range = movement::surrounding_word(
12537 display_map,
12538 selection.start.to_display_point(display_map),
12539 );
12540 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12541 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12542 selection.goal = SelectionGoal::None;
12543 selection.reversed = false;
12544 self.select_match_ranges(
12545 selection.start..selection.end,
12546 selection.reversed,
12547 replace_newest,
12548 autoscroll,
12549 window,
12550 cx,
12551 );
12552 }
12553
12554 if selections.len() == 1 {
12555 let selection = selections
12556 .last()
12557 .expect("ensured that there's only one selection");
12558 let query = buffer
12559 .text_for_range(selection.start..selection.end)
12560 .collect::<String>();
12561 let is_empty = query.is_empty();
12562 let select_state = SelectNextState {
12563 query: AhoCorasick::new(&[query])?,
12564 wordwise: true,
12565 done: is_empty,
12566 };
12567 self.select_next_state = Some(select_state);
12568 } else {
12569 self.select_next_state = None;
12570 }
12571 } else if let Some(selected_text) = selected_text {
12572 self.select_next_state = Some(SelectNextState {
12573 query: AhoCorasick::new(&[selected_text])?,
12574 wordwise: false,
12575 done: false,
12576 });
12577 self.select_next_match_internal(
12578 display_map,
12579 replace_newest,
12580 autoscroll,
12581 window,
12582 cx,
12583 )?;
12584 }
12585 }
12586 Ok(())
12587 }
12588
12589 pub fn select_all_matches(
12590 &mut self,
12591 _action: &SelectAllMatches,
12592 window: &mut Window,
12593 cx: &mut Context<Self>,
12594 ) -> Result<()> {
12595 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12596
12597 self.push_to_selection_history();
12598 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12599
12600 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12601 let Some(select_next_state) = self.select_next_state.as_mut() else {
12602 return Ok(());
12603 };
12604 if select_next_state.done {
12605 return Ok(());
12606 }
12607
12608 let mut new_selections = Vec::new();
12609
12610 let reversed = self.selections.oldest::<usize>(cx).reversed;
12611 let buffer = &display_map.buffer_snapshot;
12612 let query_matches = select_next_state
12613 .query
12614 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12615
12616 for query_match in query_matches.into_iter() {
12617 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12618 let offset_range = if reversed {
12619 query_match.end()..query_match.start()
12620 } else {
12621 query_match.start()..query_match.end()
12622 };
12623 let display_range = offset_range.start.to_display_point(&display_map)
12624 ..offset_range.end.to_display_point(&display_map);
12625
12626 if !select_next_state.wordwise
12627 || (!movement::is_inside_word(&display_map, display_range.start)
12628 && !movement::is_inside_word(&display_map, display_range.end))
12629 {
12630 new_selections.push(offset_range.start..offset_range.end);
12631 }
12632 }
12633
12634 select_next_state.done = true;
12635 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12636 self.change_selections(None, window, cx, |selections| {
12637 selections.select_ranges(new_selections)
12638 });
12639
12640 Ok(())
12641 }
12642
12643 pub fn select_next(
12644 &mut self,
12645 action: &SelectNext,
12646 window: &mut Window,
12647 cx: &mut Context<Self>,
12648 ) -> Result<()> {
12649 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12650 self.push_to_selection_history();
12651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12652 self.select_next_match_internal(
12653 &display_map,
12654 action.replace_newest,
12655 Some(Autoscroll::newest()),
12656 window,
12657 cx,
12658 )?;
12659 Ok(())
12660 }
12661
12662 pub fn select_previous(
12663 &mut self,
12664 action: &SelectPrevious,
12665 window: &mut Window,
12666 cx: &mut Context<Self>,
12667 ) -> Result<()> {
12668 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12669 self.push_to_selection_history();
12670 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12671 let buffer = &display_map.buffer_snapshot;
12672 let mut selections = self.selections.all::<usize>(cx);
12673 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12674 let query = &select_prev_state.query;
12675 if !select_prev_state.done {
12676 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12677 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12678 let mut next_selected_range = None;
12679 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12680 let bytes_before_last_selection =
12681 buffer.reversed_bytes_in_range(0..last_selection.start);
12682 let bytes_after_first_selection =
12683 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12684 let query_matches = query
12685 .stream_find_iter(bytes_before_last_selection)
12686 .map(|result| (last_selection.start, result))
12687 .chain(
12688 query
12689 .stream_find_iter(bytes_after_first_selection)
12690 .map(|result| (buffer.len(), result)),
12691 );
12692 for (end_offset, query_match) in query_matches {
12693 let query_match = query_match.unwrap(); // can only fail due to I/O
12694 let offset_range =
12695 end_offset - query_match.end()..end_offset - query_match.start();
12696 let display_range = offset_range.start.to_display_point(&display_map)
12697 ..offset_range.end.to_display_point(&display_map);
12698
12699 if !select_prev_state.wordwise
12700 || (!movement::is_inside_word(&display_map, display_range.start)
12701 && !movement::is_inside_word(&display_map, display_range.end))
12702 {
12703 next_selected_range = Some(offset_range);
12704 break;
12705 }
12706 }
12707
12708 if let Some(next_selected_range) = next_selected_range {
12709 self.select_match_ranges(
12710 next_selected_range,
12711 last_selection.reversed,
12712 action.replace_newest,
12713 Some(Autoscroll::newest()),
12714 window,
12715 cx,
12716 );
12717 } else {
12718 select_prev_state.done = true;
12719 }
12720 }
12721
12722 self.select_prev_state = Some(select_prev_state);
12723 } else {
12724 let mut only_carets = true;
12725 let mut same_text_selected = true;
12726 let mut selected_text = None;
12727
12728 let mut selections_iter = selections.iter().peekable();
12729 while let Some(selection) = selections_iter.next() {
12730 if selection.start != selection.end {
12731 only_carets = false;
12732 }
12733
12734 if same_text_selected {
12735 if selected_text.is_none() {
12736 selected_text =
12737 Some(buffer.text_for_range(selection.range()).collect::<String>());
12738 }
12739
12740 if let Some(next_selection) = selections_iter.peek() {
12741 if next_selection.range().len() == selection.range().len() {
12742 let next_selected_text = buffer
12743 .text_for_range(next_selection.range())
12744 .collect::<String>();
12745 if Some(next_selected_text) != selected_text {
12746 same_text_selected = false;
12747 selected_text = None;
12748 }
12749 } else {
12750 same_text_selected = false;
12751 selected_text = None;
12752 }
12753 }
12754 }
12755 }
12756
12757 if only_carets {
12758 for selection in &mut selections {
12759 let word_range = movement::surrounding_word(
12760 &display_map,
12761 selection.start.to_display_point(&display_map),
12762 );
12763 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12764 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12765 selection.goal = SelectionGoal::None;
12766 selection.reversed = false;
12767 self.select_match_ranges(
12768 selection.start..selection.end,
12769 selection.reversed,
12770 action.replace_newest,
12771 Some(Autoscroll::newest()),
12772 window,
12773 cx,
12774 );
12775 }
12776 if selections.len() == 1 {
12777 let selection = selections
12778 .last()
12779 .expect("ensured that there's only one selection");
12780 let query = buffer
12781 .text_for_range(selection.start..selection.end)
12782 .collect::<String>();
12783 let is_empty = query.is_empty();
12784 let select_state = SelectNextState {
12785 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12786 wordwise: true,
12787 done: is_empty,
12788 };
12789 self.select_prev_state = Some(select_state);
12790 } else {
12791 self.select_prev_state = None;
12792 }
12793 } else if let Some(selected_text) = selected_text {
12794 self.select_prev_state = Some(SelectNextState {
12795 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12796 wordwise: false,
12797 done: false,
12798 });
12799 self.select_previous(action, window, cx)?;
12800 }
12801 }
12802 Ok(())
12803 }
12804
12805 pub fn find_next_match(
12806 &mut self,
12807 _: &FindNextMatch,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) -> Result<()> {
12811 let selections = self.selections.disjoint_anchors();
12812 match selections.first() {
12813 Some(first) if selections.len() >= 2 => {
12814 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12815 s.select_ranges([first.range()]);
12816 });
12817 }
12818 _ => self.select_next(
12819 &SelectNext {
12820 replace_newest: true,
12821 },
12822 window,
12823 cx,
12824 )?,
12825 }
12826 Ok(())
12827 }
12828
12829 pub fn find_previous_match(
12830 &mut self,
12831 _: &FindPreviousMatch,
12832 window: &mut Window,
12833 cx: &mut Context<Self>,
12834 ) -> Result<()> {
12835 let selections = self.selections.disjoint_anchors();
12836 match selections.last() {
12837 Some(last) if selections.len() >= 2 => {
12838 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12839 s.select_ranges([last.range()]);
12840 });
12841 }
12842 _ => self.select_previous(
12843 &SelectPrevious {
12844 replace_newest: true,
12845 },
12846 window,
12847 cx,
12848 )?,
12849 }
12850 Ok(())
12851 }
12852
12853 pub fn toggle_comments(
12854 &mut self,
12855 action: &ToggleComments,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 if self.read_only(cx) {
12860 return;
12861 }
12862 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12863 let text_layout_details = &self.text_layout_details(window);
12864 self.transact(window, cx, |this, window, cx| {
12865 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12866 let mut edits = Vec::new();
12867 let mut selection_edit_ranges = Vec::new();
12868 let mut last_toggled_row = None;
12869 let snapshot = this.buffer.read(cx).read(cx);
12870 let empty_str: Arc<str> = Arc::default();
12871 let mut suffixes_inserted = Vec::new();
12872 let ignore_indent = action.ignore_indent;
12873
12874 fn comment_prefix_range(
12875 snapshot: &MultiBufferSnapshot,
12876 row: MultiBufferRow,
12877 comment_prefix: &str,
12878 comment_prefix_whitespace: &str,
12879 ignore_indent: bool,
12880 ) -> Range<Point> {
12881 let indent_size = if ignore_indent {
12882 0
12883 } else {
12884 snapshot.indent_size_for_line(row).len
12885 };
12886
12887 let start = Point::new(row.0, indent_size);
12888
12889 let mut line_bytes = snapshot
12890 .bytes_in_range(start..snapshot.max_point())
12891 .flatten()
12892 .copied();
12893
12894 // If this line currently begins with the line comment prefix, then record
12895 // the range containing the prefix.
12896 if line_bytes
12897 .by_ref()
12898 .take(comment_prefix.len())
12899 .eq(comment_prefix.bytes())
12900 {
12901 // Include any whitespace that matches the comment prefix.
12902 let matching_whitespace_len = line_bytes
12903 .zip(comment_prefix_whitespace.bytes())
12904 .take_while(|(a, b)| a == b)
12905 .count() as u32;
12906 let end = Point::new(
12907 start.row,
12908 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12909 );
12910 start..end
12911 } else {
12912 start..start
12913 }
12914 }
12915
12916 fn comment_suffix_range(
12917 snapshot: &MultiBufferSnapshot,
12918 row: MultiBufferRow,
12919 comment_suffix: &str,
12920 comment_suffix_has_leading_space: bool,
12921 ) -> Range<Point> {
12922 let end = Point::new(row.0, snapshot.line_len(row));
12923 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12924
12925 let mut line_end_bytes = snapshot
12926 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12927 .flatten()
12928 .copied();
12929
12930 let leading_space_len = if suffix_start_column > 0
12931 && line_end_bytes.next() == Some(b' ')
12932 && comment_suffix_has_leading_space
12933 {
12934 1
12935 } else {
12936 0
12937 };
12938
12939 // If this line currently begins with the line comment prefix, then record
12940 // the range containing the prefix.
12941 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12942 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12943 start..end
12944 } else {
12945 end..end
12946 }
12947 }
12948
12949 // TODO: Handle selections that cross excerpts
12950 for selection in &mut selections {
12951 let start_column = snapshot
12952 .indent_size_for_line(MultiBufferRow(selection.start.row))
12953 .len;
12954 let language = if let Some(language) =
12955 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12956 {
12957 language
12958 } else {
12959 continue;
12960 };
12961
12962 selection_edit_ranges.clear();
12963
12964 // If multiple selections contain a given row, avoid processing that
12965 // row more than once.
12966 let mut start_row = MultiBufferRow(selection.start.row);
12967 if last_toggled_row == Some(start_row) {
12968 start_row = start_row.next_row();
12969 }
12970 let end_row =
12971 if selection.end.row > selection.start.row && selection.end.column == 0 {
12972 MultiBufferRow(selection.end.row - 1)
12973 } else {
12974 MultiBufferRow(selection.end.row)
12975 };
12976 last_toggled_row = Some(end_row);
12977
12978 if start_row > end_row {
12979 continue;
12980 }
12981
12982 // If the language has line comments, toggle those.
12983 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12984
12985 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12986 if ignore_indent {
12987 full_comment_prefixes = full_comment_prefixes
12988 .into_iter()
12989 .map(|s| Arc::from(s.trim_end()))
12990 .collect();
12991 }
12992
12993 if !full_comment_prefixes.is_empty() {
12994 let first_prefix = full_comment_prefixes
12995 .first()
12996 .expect("prefixes is non-empty");
12997 let prefix_trimmed_lengths = full_comment_prefixes
12998 .iter()
12999 .map(|p| p.trim_end_matches(' ').len())
13000 .collect::<SmallVec<[usize; 4]>>();
13001
13002 let mut all_selection_lines_are_comments = true;
13003
13004 for row in start_row.0..=end_row.0 {
13005 let row = MultiBufferRow(row);
13006 if start_row < end_row && snapshot.is_line_blank(row) {
13007 continue;
13008 }
13009
13010 let prefix_range = full_comment_prefixes
13011 .iter()
13012 .zip(prefix_trimmed_lengths.iter().copied())
13013 .map(|(prefix, trimmed_prefix_len)| {
13014 comment_prefix_range(
13015 snapshot.deref(),
13016 row,
13017 &prefix[..trimmed_prefix_len],
13018 &prefix[trimmed_prefix_len..],
13019 ignore_indent,
13020 )
13021 })
13022 .max_by_key(|range| range.end.column - range.start.column)
13023 .expect("prefixes is non-empty");
13024
13025 if prefix_range.is_empty() {
13026 all_selection_lines_are_comments = false;
13027 }
13028
13029 selection_edit_ranges.push(prefix_range);
13030 }
13031
13032 if all_selection_lines_are_comments {
13033 edits.extend(
13034 selection_edit_ranges
13035 .iter()
13036 .cloned()
13037 .map(|range| (range, empty_str.clone())),
13038 );
13039 } else {
13040 let min_column = selection_edit_ranges
13041 .iter()
13042 .map(|range| range.start.column)
13043 .min()
13044 .unwrap_or(0);
13045 edits.extend(selection_edit_ranges.iter().map(|range| {
13046 let position = Point::new(range.start.row, min_column);
13047 (position..position, first_prefix.clone())
13048 }));
13049 }
13050 } else if let Some((full_comment_prefix, comment_suffix)) =
13051 language.block_comment_delimiters()
13052 {
13053 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13054 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13055 let prefix_range = comment_prefix_range(
13056 snapshot.deref(),
13057 start_row,
13058 comment_prefix,
13059 comment_prefix_whitespace,
13060 ignore_indent,
13061 );
13062 let suffix_range = comment_suffix_range(
13063 snapshot.deref(),
13064 end_row,
13065 comment_suffix.trim_start_matches(' '),
13066 comment_suffix.starts_with(' '),
13067 );
13068
13069 if prefix_range.is_empty() || suffix_range.is_empty() {
13070 edits.push((
13071 prefix_range.start..prefix_range.start,
13072 full_comment_prefix.clone(),
13073 ));
13074 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13075 suffixes_inserted.push((end_row, comment_suffix.len()));
13076 } else {
13077 edits.push((prefix_range, empty_str.clone()));
13078 edits.push((suffix_range, empty_str.clone()));
13079 }
13080 } else {
13081 continue;
13082 }
13083 }
13084
13085 drop(snapshot);
13086 this.buffer.update(cx, |buffer, cx| {
13087 buffer.edit(edits, None, cx);
13088 });
13089
13090 // Adjust selections so that they end before any comment suffixes that
13091 // were inserted.
13092 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13093 let mut selections = this.selections.all::<Point>(cx);
13094 let snapshot = this.buffer.read(cx).read(cx);
13095 for selection in &mut selections {
13096 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13097 match row.cmp(&MultiBufferRow(selection.end.row)) {
13098 Ordering::Less => {
13099 suffixes_inserted.next();
13100 continue;
13101 }
13102 Ordering::Greater => break,
13103 Ordering::Equal => {
13104 if selection.end.column == snapshot.line_len(row) {
13105 if selection.is_empty() {
13106 selection.start.column -= suffix_len as u32;
13107 }
13108 selection.end.column -= suffix_len as u32;
13109 }
13110 break;
13111 }
13112 }
13113 }
13114 }
13115
13116 drop(snapshot);
13117 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13118 s.select(selections)
13119 });
13120
13121 let selections = this.selections.all::<Point>(cx);
13122 let selections_on_single_row = selections.windows(2).all(|selections| {
13123 selections[0].start.row == selections[1].start.row
13124 && selections[0].end.row == selections[1].end.row
13125 && selections[0].start.row == selections[0].end.row
13126 });
13127 let selections_selecting = selections
13128 .iter()
13129 .any(|selection| selection.start != selection.end);
13130 let advance_downwards = action.advance_downwards
13131 && selections_on_single_row
13132 && !selections_selecting
13133 && !matches!(this.mode, EditorMode::SingleLine { .. });
13134
13135 if advance_downwards {
13136 let snapshot = this.buffer.read(cx).snapshot(cx);
13137
13138 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13139 s.move_cursors_with(|display_snapshot, display_point, _| {
13140 let mut point = display_point.to_point(display_snapshot);
13141 point.row += 1;
13142 point = snapshot.clip_point(point, Bias::Left);
13143 let display_point = point.to_display_point(display_snapshot);
13144 let goal = SelectionGoal::HorizontalPosition(
13145 display_snapshot
13146 .x_for_display_point(display_point, text_layout_details)
13147 .into(),
13148 );
13149 (display_point, goal)
13150 })
13151 });
13152 }
13153 });
13154 }
13155
13156 pub fn select_enclosing_symbol(
13157 &mut self,
13158 _: &SelectEnclosingSymbol,
13159 window: &mut Window,
13160 cx: &mut Context<Self>,
13161 ) {
13162 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13163
13164 let buffer = self.buffer.read(cx).snapshot(cx);
13165 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13166
13167 fn update_selection(
13168 selection: &Selection<usize>,
13169 buffer_snap: &MultiBufferSnapshot,
13170 ) -> Option<Selection<usize>> {
13171 let cursor = selection.head();
13172 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13173 for symbol in symbols.iter().rev() {
13174 let start = symbol.range.start.to_offset(buffer_snap);
13175 let end = symbol.range.end.to_offset(buffer_snap);
13176 let new_range = start..end;
13177 if start < selection.start || end > selection.end {
13178 return Some(Selection {
13179 id: selection.id,
13180 start: new_range.start,
13181 end: new_range.end,
13182 goal: SelectionGoal::None,
13183 reversed: selection.reversed,
13184 });
13185 }
13186 }
13187 None
13188 }
13189
13190 let mut selected_larger_symbol = false;
13191 let new_selections = old_selections
13192 .iter()
13193 .map(|selection| match update_selection(selection, &buffer) {
13194 Some(new_selection) => {
13195 if new_selection.range() != selection.range() {
13196 selected_larger_symbol = true;
13197 }
13198 new_selection
13199 }
13200 None => selection.clone(),
13201 })
13202 .collect::<Vec<_>>();
13203
13204 if selected_larger_symbol {
13205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13206 s.select(new_selections);
13207 });
13208 }
13209 }
13210
13211 pub fn select_larger_syntax_node(
13212 &mut self,
13213 _: &SelectLargerSyntaxNode,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 let Some(visible_row_count) = self.visible_row_count() else {
13218 return;
13219 };
13220 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13221 if old_selections.is_empty() {
13222 return;
13223 }
13224
13225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13226
13227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13228 let buffer = self.buffer.read(cx).snapshot(cx);
13229
13230 let mut selected_larger_node = false;
13231 let mut new_selections = old_selections
13232 .iter()
13233 .map(|selection| {
13234 let old_range = selection.start..selection.end;
13235
13236 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13237 // manually select word at selection
13238 if ["string_content", "inline"].contains(&node.kind()) {
13239 let word_range = {
13240 let display_point = buffer
13241 .offset_to_point(old_range.start)
13242 .to_display_point(&display_map);
13243 let Range { start, end } =
13244 movement::surrounding_word(&display_map, display_point);
13245 start.to_point(&display_map).to_offset(&buffer)
13246 ..end.to_point(&display_map).to_offset(&buffer)
13247 };
13248 // ignore if word is already selected
13249 if !word_range.is_empty() && old_range != word_range {
13250 let last_word_range = {
13251 let display_point = buffer
13252 .offset_to_point(old_range.end)
13253 .to_display_point(&display_map);
13254 let Range { start, end } =
13255 movement::surrounding_word(&display_map, display_point);
13256 start.to_point(&display_map).to_offset(&buffer)
13257 ..end.to_point(&display_map).to_offset(&buffer)
13258 };
13259 // only select word if start and end point belongs to same word
13260 if word_range == last_word_range {
13261 selected_larger_node = true;
13262 return Selection {
13263 id: selection.id,
13264 start: word_range.start,
13265 end: word_range.end,
13266 goal: SelectionGoal::None,
13267 reversed: selection.reversed,
13268 };
13269 }
13270 }
13271 }
13272 }
13273
13274 let mut new_range = old_range.clone();
13275 while let Some((_node, containing_range)) =
13276 buffer.syntax_ancestor(new_range.clone())
13277 {
13278 new_range = match containing_range {
13279 MultiOrSingleBufferOffsetRange::Single(_) => break,
13280 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13281 };
13282 if !display_map.intersects_fold(new_range.start)
13283 && !display_map.intersects_fold(new_range.end)
13284 {
13285 break;
13286 }
13287 }
13288
13289 selected_larger_node |= new_range != old_range;
13290 Selection {
13291 id: selection.id,
13292 start: new_range.start,
13293 end: new_range.end,
13294 goal: SelectionGoal::None,
13295 reversed: selection.reversed,
13296 }
13297 })
13298 .collect::<Vec<_>>();
13299
13300 if !selected_larger_node {
13301 return; // don't put this call in the history
13302 }
13303
13304 // scroll based on transformation done to the last selection created by the user
13305 let (last_old, last_new) = old_selections
13306 .last()
13307 .zip(new_selections.last().cloned())
13308 .expect("old_selections isn't empty");
13309
13310 // revert selection
13311 let is_selection_reversed = {
13312 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13313 new_selections.last_mut().expect("checked above").reversed =
13314 should_newest_selection_be_reversed;
13315 should_newest_selection_be_reversed
13316 };
13317
13318 if selected_larger_node {
13319 self.select_syntax_node_history.disable_clearing = true;
13320 self.change_selections(None, window, cx, |s| {
13321 s.select(new_selections.clone());
13322 });
13323 self.select_syntax_node_history.disable_clearing = false;
13324 }
13325
13326 let start_row = last_new.start.to_display_point(&display_map).row().0;
13327 let end_row = last_new.end.to_display_point(&display_map).row().0;
13328 let selection_height = end_row - start_row + 1;
13329 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13330
13331 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13332 let scroll_behavior = if fits_on_the_screen {
13333 self.request_autoscroll(Autoscroll::fit(), cx);
13334 SelectSyntaxNodeScrollBehavior::FitSelection
13335 } else if is_selection_reversed {
13336 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13337 SelectSyntaxNodeScrollBehavior::CursorTop
13338 } else {
13339 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13340 SelectSyntaxNodeScrollBehavior::CursorBottom
13341 };
13342
13343 self.select_syntax_node_history.push((
13344 old_selections,
13345 scroll_behavior,
13346 is_selection_reversed,
13347 ));
13348 }
13349
13350 pub fn select_smaller_syntax_node(
13351 &mut self,
13352 _: &SelectSmallerSyntaxNode,
13353 window: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13357
13358 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13359 self.select_syntax_node_history.pop()
13360 {
13361 if let Some(selection) = selections.last_mut() {
13362 selection.reversed = is_selection_reversed;
13363 }
13364
13365 self.select_syntax_node_history.disable_clearing = true;
13366 self.change_selections(None, window, cx, |s| {
13367 s.select(selections.to_vec());
13368 });
13369 self.select_syntax_node_history.disable_clearing = false;
13370
13371 match scroll_behavior {
13372 SelectSyntaxNodeScrollBehavior::CursorTop => {
13373 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13374 }
13375 SelectSyntaxNodeScrollBehavior::FitSelection => {
13376 self.request_autoscroll(Autoscroll::fit(), cx);
13377 }
13378 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13379 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13380 }
13381 }
13382 }
13383 }
13384
13385 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13386 if !EditorSettings::get_global(cx).gutter.runnables {
13387 self.clear_tasks();
13388 return Task::ready(());
13389 }
13390 let project = self.project.as_ref().map(Entity::downgrade);
13391 let task_sources = self.lsp_task_sources(cx);
13392 cx.spawn_in(window, async move |editor, cx| {
13393 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13394 let Some(project) = project.and_then(|p| p.upgrade()) else {
13395 return;
13396 };
13397 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13398 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13399 }) else {
13400 return;
13401 };
13402
13403 let hide_runnables = project
13404 .update(cx, |project, cx| {
13405 // Do not display any test indicators in non-dev server remote projects.
13406 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13407 })
13408 .unwrap_or(true);
13409 if hide_runnables {
13410 return;
13411 }
13412 let new_rows =
13413 cx.background_spawn({
13414 let snapshot = display_snapshot.clone();
13415 async move {
13416 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13417 }
13418 })
13419 .await;
13420 let Ok(lsp_tasks) =
13421 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13422 else {
13423 return;
13424 };
13425 let lsp_tasks = lsp_tasks.await;
13426
13427 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13428 lsp_tasks
13429 .into_iter()
13430 .flat_map(|(kind, tasks)| {
13431 tasks.into_iter().filter_map(move |(location, task)| {
13432 Some((kind.clone(), location?, task))
13433 })
13434 })
13435 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13436 let buffer = location.target.buffer;
13437 let buffer_snapshot = buffer.read(cx).snapshot();
13438 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13439 |(excerpt_id, snapshot, _)| {
13440 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13441 display_snapshot
13442 .buffer_snapshot
13443 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13444 } else {
13445 None
13446 }
13447 },
13448 );
13449 if let Some(offset) = offset {
13450 let task_buffer_range =
13451 location.target.range.to_point(&buffer_snapshot);
13452 let context_buffer_range =
13453 task_buffer_range.to_offset(&buffer_snapshot);
13454 let context_range = BufferOffset(context_buffer_range.start)
13455 ..BufferOffset(context_buffer_range.end);
13456
13457 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13458 .or_insert_with(|| RunnableTasks {
13459 templates: Vec::new(),
13460 offset,
13461 column: task_buffer_range.start.column,
13462 extra_variables: HashMap::default(),
13463 context_range,
13464 })
13465 .templates
13466 .push((kind, task.original_task().clone()));
13467 }
13468
13469 acc
13470 })
13471 }) else {
13472 return;
13473 };
13474
13475 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13476 editor
13477 .update(cx, |editor, _| {
13478 editor.clear_tasks();
13479 for (key, mut value) in rows {
13480 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13481 value.templates.extend(lsp_tasks.templates);
13482 }
13483
13484 editor.insert_tasks(key, value);
13485 }
13486 for (key, value) in lsp_tasks_by_rows {
13487 editor.insert_tasks(key, value);
13488 }
13489 })
13490 .ok();
13491 })
13492 }
13493 fn fetch_runnable_ranges(
13494 snapshot: &DisplaySnapshot,
13495 range: Range<Anchor>,
13496 ) -> Vec<language::RunnableRange> {
13497 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13498 }
13499
13500 fn runnable_rows(
13501 project: Entity<Project>,
13502 snapshot: DisplaySnapshot,
13503 runnable_ranges: Vec<RunnableRange>,
13504 mut cx: AsyncWindowContext,
13505 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13506 runnable_ranges
13507 .into_iter()
13508 .filter_map(|mut runnable| {
13509 let tasks = cx
13510 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13511 .ok()?;
13512 if tasks.is_empty() {
13513 return None;
13514 }
13515
13516 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13517
13518 let row = snapshot
13519 .buffer_snapshot
13520 .buffer_line_for_row(MultiBufferRow(point.row))?
13521 .1
13522 .start
13523 .row;
13524
13525 let context_range =
13526 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13527 Some((
13528 (runnable.buffer_id, row),
13529 RunnableTasks {
13530 templates: tasks,
13531 offset: snapshot
13532 .buffer_snapshot
13533 .anchor_before(runnable.run_range.start),
13534 context_range,
13535 column: point.column,
13536 extra_variables: runnable.extra_captures,
13537 },
13538 ))
13539 })
13540 .collect()
13541 }
13542
13543 fn templates_with_tags(
13544 project: &Entity<Project>,
13545 runnable: &mut Runnable,
13546 cx: &mut App,
13547 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13548 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13549 let (worktree_id, file) = project
13550 .buffer_for_id(runnable.buffer, cx)
13551 .and_then(|buffer| buffer.read(cx).file())
13552 .map(|file| (file.worktree_id(cx), file.clone()))
13553 .unzip();
13554
13555 (
13556 project.task_store().read(cx).task_inventory().cloned(),
13557 worktree_id,
13558 file,
13559 )
13560 });
13561
13562 let mut templates_with_tags = mem::take(&mut runnable.tags)
13563 .into_iter()
13564 .flat_map(|RunnableTag(tag)| {
13565 inventory
13566 .as_ref()
13567 .into_iter()
13568 .flat_map(|inventory| {
13569 inventory.read(cx).list_tasks(
13570 file.clone(),
13571 Some(runnable.language.clone()),
13572 worktree_id,
13573 cx,
13574 )
13575 })
13576 .filter(move |(_, template)| {
13577 template.tags.iter().any(|source_tag| source_tag == &tag)
13578 })
13579 })
13580 .sorted_by_key(|(kind, _)| kind.to_owned())
13581 .collect::<Vec<_>>();
13582 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13583 // Strongest source wins; if we have worktree tag binding, prefer that to
13584 // global and language bindings;
13585 // if we have a global binding, prefer that to language binding.
13586 let first_mismatch = templates_with_tags
13587 .iter()
13588 .position(|(tag_source, _)| tag_source != leading_tag_source);
13589 if let Some(index) = first_mismatch {
13590 templates_with_tags.truncate(index);
13591 }
13592 }
13593
13594 templates_with_tags
13595 }
13596
13597 pub fn move_to_enclosing_bracket(
13598 &mut self,
13599 _: &MoveToEnclosingBracket,
13600 window: &mut Window,
13601 cx: &mut Context<Self>,
13602 ) {
13603 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13604 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13605 s.move_offsets_with(|snapshot, selection| {
13606 let Some(enclosing_bracket_ranges) =
13607 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13608 else {
13609 return;
13610 };
13611
13612 let mut best_length = usize::MAX;
13613 let mut best_inside = false;
13614 let mut best_in_bracket_range = false;
13615 let mut best_destination = None;
13616 for (open, close) in enclosing_bracket_ranges {
13617 let close = close.to_inclusive();
13618 let length = close.end() - open.start;
13619 let inside = selection.start >= open.end && selection.end <= *close.start();
13620 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13621 || close.contains(&selection.head());
13622
13623 // If best is next to a bracket and current isn't, skip
13624 if !in_bracket_range && best_in_bracket_range {
13625 continue;
13626 }
13627
13628 // Prefer smaller lengths unless best is inside and current isn't
13629 if length > best_length && (best_inside || !inside) {
13630 continue;
13631 }
13632
13633 best_length = length;
13634 best_inside = inside;
13635 best_in_bracket_range = in_bracket_range;
13636 best_destination = Some(
13637 if close.contains(&selection.start) && close.contains(&selection.end) {
13638 if inside { open.end } else { open.start }
13639 } else if inside {
13640 *close.start()
13641 } else {
13642 *close.end()
13643 },
13644 );
13645 }
13646
13647 if let Some(destination) = best_destination {
13648 selection.collapse_to(destination, SelectionGoal::None);
13649 }
13650 })
13651 });
13652 }
13653
13654 pub fn undo_selection(
13655 &mut self,
13656 _: &UndoSelection,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13661 self.end_selection(window, cx);
13662 self.selection_history.mode = SelectionHistoryMode::Undoing;
13663 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13664 self.change_selections(None, window, cx, |s| {
13665 s.select_anchors(entry.selections.to_vec())
13666 });
13667 self.select_next_state = entry.select_next_state;
13668 self.select_prev_state = entry.select_prev_state;
13669 self.add_selections_state = entry.add_selections_state;
13670 self.request_autoscroll(Autoscroll::newest(), cx);
13671 }
13672 self.selection_history.mode = SelectionHistoryMode::Normal;
13673 }
13674
13675 pub fn redo_selection(
13676 &mut self,
13677 _: &RedoSelection,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13682 self.end_selection(window, cx);
13683 self.selection_history.mode = SelectionHistoryMode::Redoing;
13684 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13685 self.change_selections(None, window, cx, |s| {
13686 s.select_anchors(entry.selections.to_vec())
13687 });
13688 self.select_next_state = entry.select_next_state;
13689 self.select_prev_state = entry.select_prev_state;
13690 self.add_selections_state = entry.add_selections_state;
13691 self.request_autoscroll(Autoscroll::newest(), cx);
13692 }
13693 self.selection_history.mode = SelectionHistoryMode::Normal;
13694 }
13695
13696 pub fn expand_excerpts(
13697 &mut self,
13698 action: &ExpandExcerpts,
13699 _: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13703 }
13704
13705 pub fn expand_excerpts_down(
13706 &mut self,
13707 action: &ExpandExcerptsDown,
13708 _: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13712 }
13713
13714 pub fn expand_excerpts_up(
13715 &mut self,
13716 action: &ExpandExcerptsUp,
13717 _: &mut Window,
13718 cx: &mut Context<Self>,
13719 ) {
13720 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13721 }
13722
13723 pub fn expand_excerpts_for_direction(
13724 &mut self,
13725 lines: u32,
13726 direction: ExpandExcerptDirection,
13727
13728 cx: &mut Context<Self>,
13729 ) {
13730 let selections = self.selections.disjoint_anchors();
13731
13732 let lines = if lines == 0 {
13733 EditorSettings::get_global(cx).expand_excerpt_lines
13734 } else {
13735 lines
13736 };
13737
13738 self.buffer.update(cx, |buffer, cx| {
13739 let snapshot = buffer.snapshot(cx);
13740 let mut excerpt_ids = selections
13741 .iter()
13742 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13743 .collect::<Vec<_>>();
13744 excerpt_ids.sort();
13745 excerpt_ids.dedup();
13746 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13747 })
13748 }
13749
13750 pub fn expand_excerpt(
13751 &mut self,
13752 excerpt: ExcerptId,
13753 direction: ExpandExcerptDirection,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) {
13757 let current_scroll_position = self.scroll_position(cx);
13758 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13759 let mut should_scroll_up = false;
13760
13761 if direction == ExpandExcerptDirection::Down {
13762 let multi_buffer = self.buffer.read(cx);
13763 let snapshot = multi_buffer.snapshot(cx);
13764 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13765 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13766 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13767 let buffer_snapshot = buffer.read(cx).snapshot();
13768 let excerpt_end_row =
13769 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13770 let last_row = buffer_snapshot.max_point().row;
13771 let lines_below = last_row.saturating_sub(excerpt_end_row);
13772 should_scroll_up = lines_below >= lines_to_expand;
13773 }
13774 }
13775 }
13776 }
13777
13778 self.buffer.update(cx, |buffer, cx| {
13779 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13780 });
13781
13782 if should_scroll_up {
13783 let new_scroll_position =
13784 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13785 self.set_scroll_position(new_scroll_position, window, cx);
13786 }
13787 }
13788
13789 pub fn go_to_singleton_buffer_point(
13790 &mut self,
13791 point: Point,
13792 window: &mut Window,
13793 cx: &mut Context<Self>,
13794 ) {
13795 self.go_to_singleton_buffer_range(point..point, window, cx);
13796 }
13797
13798 pub fn go_to_singleton_buffer_range(
13799 &mut self,
13800 range: Range<Point>,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 let multibuffer = self.buffer().read(cx);
13805 let Some(buffer) = multibuffer.as_singleton() else {
13806 return;
13807 };
13808 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13809 return;
13810 };
13811 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13812 return;
13813 };
13814 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13815 s.select_anchor_ranges([start..end])
13816 });
13817 }
13818
13819 pub fn go_to_diagnostic(
13820 &mut self,
13821 _: &GoToDiagnostic,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13826 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13827 }
13828
13829 pub fn go_to_prev_diagnostic(
13830 &mut self,
13831 _: &GoToPreviousDiagnostic,
13832 window: &mut Window,
13833 cx: &mut Context<Self>,
13834 ) {
13835 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13836 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13837 }
13838
13839 pub fn go_to_diagnostic_impl(
13840 &mut self,
13841 direction: Direction,
13842 window: &mut Window,
13843 cx: &mut Context<Self>,
13844 ) {
13845 let buffer = self.buffer.read(cx).snapshot(cx);
13846 let selection = self.selections.newest::<usize>(cx);
13847
13848 let mut active_group_id = None;
13849 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13850 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13851 active_group_id = Some(active_group.group_id);
13852 }
13853 }
13854
13855 fn filtered(
13856 snapshot: EditorSnapshot,
13857 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13858 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13859 diagnostics
13860 .filter(|entry| entry.range.start != entry.range.end)
13861 .filter(|entry| !entry.diagnostic.is_unnecessary)
13862 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13863 }
13864
13865 let snapshot = self.snapshot(window, cx);
13866 let before = filtered(
13867 snapshot.clone(),
13868 buffer
13869 .diagnostics_in_range(0..selection.start)
13870 .filter(|entry| entry.range.start <= selection.start),
13871 );
13872 let after = filtered(
13873 snapshot,
13874 buffer
13875 .diagnostics_in_range(selection.start..buffer.len())
13876 .filter(|entry| entry.range.start >= selection.start),
13877 );
13878
13879 let mut found: Option<DiagnosticEntry<usize>> = None;
13880 if direction == Direction::Prev {
13881 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13882 {
13883 for diagnostic in prev_diagnostics.into_iter().rev() {
13884 if diagnostic.range.start != selection.start
13885 || active_group_id
13886 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13887 {
13888 found = Some(diagnostic);
13889 break 'outer;
13890 }
13891 }
13892 }
13893 } else {
13894 for diagnostic in after.chain(before) {
13895 if diagnostic.range.start != selection.start
13896 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13897 {
13898 found = Some(diagnostic);
13899 break;
13900 }
13901 }
13902 }
13903 let Some(next_diagnostic) = found else {
13904 return;
13905 };
13906
13907 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13908 return;
13909 };
13910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13911 s.select_ranges(vec![
13912 next_diagnostic.range.start..next_diagnostic.range.start,
13913 ])
13914 });
13915 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13916 self.refresh_inline_completion(false, true, window, cx);
13917 }
13918
13919 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13920 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13921 let snapshot = self.snapshot(window, cx);
13922 let selection = self.selections.newest::<Point>(cx);
13923 self.go_to_hunk_before_or_after_position(
13924 &snapshot,
13925 selection.head(),
13926 Direction::Next,
13927 window,
13928 cx,
13929 );
13930 }
13931
13932 pub fn go_to_hunk_before_or_after_position(
13933 &mut self,
13934 snapshot: &EditorSnapshot,
13935 position: Point,
13936 direction: Direction,
13937 window: &mut Window,
13938 cx: &mut Context<Editor>,
13939 ) {
13940 let row = if direction == Direction::Next {
13941 self.hunk_after_position(snapshot, position)
13942 .map(|hunk| hunk.row_range.start)
13943 } else {
13944 self.hunk_before_position(snapshot, position)
13945 };
13946
13947 if let Some(row) = row {
13948 let destination = Point::new(row.0, 0);
13949 let autoscroll = Autoscroll::center();
13950
13951 self.unfold_ranges(&[destination..destination], false, false, cx);
13952 self.change_selections(Some(autoscroll), window, cx, |s| {
13953 s.select_ranges([destination..destination]);
13954 });
13955 }
13956 }
13957
13958 fn hunk_after_position(
13959 &mut self,
13960 snapshot: &EditorSnapshot,
13961 position: Point,
13962 ) -> Option<MultiBufferDiffHunk> {
13963 snapshot
13964 .buffer_snapshot
13965 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13966 .find(|hunk| hunk.row_range.start.0 > position.row)
13967 .or_else(|| {
13968 snapshot
13969 .buffer_snapshot
13970 .diff_hunks_in_range(Point::zero()..position)
13971 .find(|hunk| hunk.row_range.end.0 < position.row)
13972 })
13973 }
13974
13975 fn go_to_prev_hunk(
13976 &mut self,
13977 _: &GoToPreviousHunk,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13982 let snapshot = self.snapshot(window, cx);
13983 let selection = self.selections.newest::<Point>(cx);
13984 self.go_to_hunk_before_or_after_position(
13985 &snapshot,
13986 selection.head(),
13987 Direction::Prev,
13988 window,
13989 cx,
13990 );
13991 }
13992
13993 fn hunk_before_position(
13994 &mut self,
13995 snapshot: &EditorSnapshot,
13996 position: Point,
13997 ) -> Option<MultiBufferRow> {
13998 snapshot
13999 .buffer_snapshot
14000 .diff_hunk_before(position)
14001 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14002 }
14003
14004 fn go_to_next_change(
14005 &mut self,
14006 _: &GoToNextChange,
14007 window: &mut Window,
14008 cx: &mut Context<Self>,
14009 ) {
14010 if let Some(selections) = self
14011 .change_list
14012 .next_change(1, Direction::Next)
14013 .map(|s| s.to_vec())
14014 {
14015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14016 let map = s.display_map();
14017 s.select_display_ranges(selections.iter().map(|a| {
14018 let point = a.to_display_point(&map);
14019 point..point
14020 }))
14021 })
14022 }
14023 }
14024
14025 fn go_to_previous_change(
14026 &mut self,
14027 _: &GoToPreviousChange,
14028 window: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) {
14031 if let Some(selections) = self
14032 .change_list
14033 .next_change(1, Direction::Prev)
14034 .map(|s| s.to_vec())
14035 {
14036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14037 let map = s.display_map();
14038 s.select_display_ranges(selections.iter().map(|a| {
14039 let point = a.to_display_point(&map);
14040 point..point
14041 }))
14042 })
14043 }
14044 }
14045
14046 fn go_to_line<T: 'static>(
14047 &mut self,
14048 position: Anchor,
14049 highlight_color: Option<Hsla>,
14050 window: &mut Window,
14051 cx: &mut Context<Self>,
14052 ) {
14053 let snapshot = self.snapshot(window, cx).display_snapshot;
14054 let position = position.to_point(&snapshot.buffer_snapshot);
14055 let start = snapshot
14056 .buffer_snapshot
14057 .clip_point(Point::new(position.row, 0), Bias::Left);
14058 let end = start + Point::new(1, 0);
14059 let start = snapshot.buffer_snapshot.anchor_before(start);
14060 let end = snapshot.buffer_snapshot.anchor_before(end);
14061
14062 self.highlight_rows::<T>(
14063 start..end,
14064 highlight_color
14065 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14066 Default::default(),
14067 cx,
14068 );
14069
14070 if self.buffer.read(cx).is_singleton() {
14071 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14072 }
14073 }
14074
14075 pub fn go_to_definition(
14076 &mut self,
14077 _: &GoToDefinition,
14078 window: &mut Window,
14079 cx: &mut Context<Self>,
14080 ) -> Task<Result<Navigated>> {
14081 let definition =
14082 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14083 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14084 cx.spawn_in(window, async move |editor, cx| {
14085 if definition.await? == Navigated::Yes {
14086 return Ok(Navigated::Yes);
14087 }
14088 match fallback_strategy {
14089 GoToDefinitionFallback::None => Ok(Navigated::No),
14090 GoToDefinitionFallback::FindAllReferences => {
14091 match editor.update_in(cx, |editor, window, cx| {
14092 editor.find_all_references(&FindAllReferences, window, cx)
14093 })? {
14094 Some(references) => references.await,
14095 None => Ok(Navigated::No),
14096 }
14097 }
14098 }
14099 })
14100 }
14101
14102 pub fn go_to_declaration(
14103 &mut self,
14104 _: &GoToDeclaration,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) -> Task<Result<Navigated>> {
14108 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14109 }
14110
14111 pub fn go_to_declaration_split(
14112 &mut self,
14113 _: &GoToDeclaration,
14114 window: &mut Window,
14115 cx: &mut Context<Self>,
14116 ) -> Task<Result<Navigated>> {
14117 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14118 }
14119
14120 pub fn go_to_implementation(
14121 &mut self,
14122 _: &GoToImplementation,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) -> Task<Result<Navigated>> {
14126 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14127 }
14128
14129 pub fn go_to_implementation_split(
14130 &mut self,
14131 _: &GoToImplementationSplit,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) -> Task<Result<Navigated>> {
14135 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14136 }
14137
14138 pub fn go_to_type_definition(
14139 &mut self,
14140 _: &GoToTypeDefinition,
14141 window: &mut Window,
14142 cx: &mut Context<Self>,
14143 ) -> Task<Result<Navigated>> {
14144 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14145 }
14146
14147 pub fn go_to_definition_split(
14148 &mut self,
14149 _: &GoToDefinitionSplit,
14150 window: &mut Window,
14151 cx: &mut Context<Self>,
14152 ) -> Task<Result<Navigated>> {
14153 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14154 }
14155
14156 pub fn go_to_type_definition_split(
14157 &mut self,
14158 _: &GoToTypeDefinitionSplit,
14159 window: &mut Window,
14160 cx: &mut Context<Self>,
14161 ) -> Task<Result<Navigated>> {
14162 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14163 }
14164
14165 fn go_to_definition_of_kind(
14166 &mut self,
14167 kind: GotoDefinitionKind,
14168 split: bool,
14169 window: &mut Window,
14170 cx: &mut Context<Self>,
14171 ) -> Task<Result<Navigated>> {
14172 let Some(provider) = self.semantics_provider.clone() else {
14173 return Task::ready(Ok(Navigated::No));
14174 };
14175 let head = self.selections.newest::<usize>(cx).head();
14176 let buffer = self.buffer.read(cx);
14177 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14178 text_anchor
14179 } else {
14180 return Task::ready(Ok(Navigated::No));
14181 };
14182
14183 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14184 return Task::ready(Ok(Navigated::No));
14185 };
14186
14187 cx.spawn_in(window, async move |editor, cx| {
14188 let definitions = definitions.await?;
14189 let navigated = editor
14190 .update_in(cx, |editor, window, cx| {
14191 editor.navigate_to_hover_links(
14192 Some(kind),
14193 definitions
14194 .into_iter()
14195 .filter(|location| {
14196 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14197 })
14198 .map(HoverLink::Text)
14199 .collect::<Vec<_>>(),
14200 split,
14201 window,
14202 cx,
14203 )
14204 })?
14205 .await?;
14206 anyhow::Ok(navigated)
14207 })
14208 }
14209
14210 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14211 let selection = self.selections.newest_anchor();
14212 let head = selection.head();
14213 let tail = selection.tail();
14214
14215 let Some((buffer, start_position)) =
14216 self.buffer.read(cx).text_anchor_for_position(head, cx)
14217 else {
14218 return;
14219 };
14220
14221 let end_position = if head != tail {
14222 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14223 return;
14224 };
14225 Some(pos)
14226 } else {
14227 None
14228 };
14229
14230 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14231 let url = if let Some(end_pos) = end_position {
14232 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14233 } else {
14234 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14235 };
14236
14237 if let Some(url) = url {
14238 editor.update(cx, |_, cx| {
14239 cx.open_url(&url);
14240 })
14241 } else {
14242 Ok(())
14243 }
14244 });
14245
14246 url_finder.detach();
14247 }
14248
14249 pub fn open_selected_filename(
14250 &mut self,
14251 _: &OpenSelectedFilename,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) {
14255 let Some(workspace) = self.workspace() else {
14256 return;
14257 };
14258
14259 let position = self.selections.newest_anchor().head();
14260
14261 let Some((buffer, buffer_position)) =
14262 self.buffer.read(cx).text_anchor_for_position(position, cx)
14263 else {
14264 return;
14265 };
14266
14267 let project = self.project.clone();
14268
14269 cx.spawn_in(window, async move |_, cx| {
14270 let result = find_file(&buffer, project, buffer_position, cx).await;
14271
14272 if let Some((_, path)) = result {
14273 workspace
14274 .update_in(cx, |workspace, window, cx| {
14275 workspace.open_resolved_path(path, window, cx)
14276 })?
14277 .await?;
14278 }
14279 anyhow::Ok(())
14280 })
14281 .detach();
14282 }
14283
14284 pub(crate) fn navigate_to_hover_links(
14285 &mut self,
14286 kind: Option<GotoDefinitionKind>,
14287 mut definitions: Vec<HoverLink>,
14288 split: bool,
14289 window: &mut Window,
14290 cx: &mut Context<Editor>,
14291 ) -> Task<Result<Navigated>> {
14292 // If there is one definition, just open it directly
14293 if definitions.len() == 1 {
14294 let definition = definitions.pop().unwrap();
14295
14296 enum TargetTaskResult {
14297 Location(Option<Location>),
14298 AlreadyNavigated,
14299 }
14300
14301 let target_task = match definition {
14302 HoverLink::Text(link) => {
14303 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14304 }
14305 HoverLink::InlayHint(lsp_location, server_id) => {
14306 let computation =
14307 self.compute_target_location(lsp_location, server_id, window, cx);
14308 cx.background_spawn(async move {
14309 let location = computation.await?;
14310 Ok(TargetTaskResult::Location(location))
14311 })
14312 }
14313 HoverLink::Url(url) => {
14314 cx.open_url(&url);
14315 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14316 }
14317 HoverLink::File(path) => {
14318 if let Some(workspace) = self.workspace() {
14319 cx.spawn_in(window, async move |_, cx| {
14320 workspace
14321 .update_in(cx, |workspace, window, cx| {
14322 workspace.open_resolved_path(path, window, cx)
14323 })?
14324 .await
14325 .map(|_| TargetTaskResult::AlreadyNavigated)
14326 })
14327 } else {
14328 Task::ready(Ok(TargetTaskResult::Location(None)))
14329 }
14330 }
14331 };
14332 cx.spawn_in(window, async move |editor, cx| {
14333 let target = match target_task.await.context("target resolution task")? {
14334 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14335 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14336 TargetTaskResult::Location(Some(target)) => target,
14337 };
14338
14339 editor.update_in(cx, |editor, window, cx| {
14340 let Some(workspace) = editor.workspace() else {
14341 return Navigated::No;
14342 };
14343 let pane = workspace.read(cx).active_pane().clone();
14344
14345 let range = target.range.to_point(target.buffer.read(cx));
14346 let range = editor.range_for_match(&range);
14347 let range = collapse_multiline_range(range);
14348
14349 if !split
14350 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14351 {
14352 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14353 } else {
14354 window.defer(cx, move |window, cx| {
14355 let target_editor: Entity<Self> =
14356 workspace.update(cx, |workspace, cx| {
14357 let pane = if split {
14358 workspace.adjacent_pane(window, cx)
14359 } else {
14360 workspace.active_pane().clone()
14361 };
14362
14363 workspace.open_project_item(
14364 pane,
14365 target.buffer.clone(),
14366 true,
14367 true,
14368 window,
14369 cx,
14370 )
14371 });
14372 target_editor.update(cx, |target_editor, cx| {
14373 // When selecting a definition in a different buffer, disable the nav history
14374 // to avoid creating a history entry at the previous cursor location.
14375 pane.update(cx, |pane, _| pane.disable_history());
14376 target_editor.go_to_singleton_buffer_range(range, window, cx);
14377 pane.update(cx, |pane, _| pane.enable_history());
14378 });
14379 });
14380 }
14381 Navigated::Yes
14382 })
14383 })
14384 } else if !definitions.is_empty() {
14385 cx.spawn_in(window, async move |editor, cx| {
14386 let (title, location_tasks, workspace) = editor
14387 .update_in(cx, |editor, window, cx| {
14388 let tab_kind = match kind {
14389 Some(GotoDefinitionKind::Implementation) => "Implementations",
14390 _ => "Definitions",
14391 };
14392 let title = definitions
14393 .iter()
14394 .find_map(|definition| match definition {
14395 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14396 let buffer = origin.buffer.read(cx);
14397 format!(
14398 "{} for {}",
14399 tab_kind,
14400 buffer
14401 .text_for_range(origin.range.clone())
14402 .collect::<String>()
14403 )
14404 }),
14405 HoverLink::InlayHint(_, _) => None,
14406 HoverLink::Url(_) => None,
14407 HoverLink::File(_) => None,
14408 })
14409 .unwrap_or(tab_kind.to_string());
14410 let location_tasks = definitions
14411 .into_iter()
14412 .map(|definition| match definition {
14413 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14414 HoverLink::InlayHint(lsp_location, server_id) => editor
14415 .compute_target_location(lsp_location, server_id, window, cx),
14416 HoverLink::Url(_) => Task::ready(Ok(None)),
14417 HoverLink::File(_) => Task::ready(Ok(None)),
14418 })
14419 .collect::<Vec<_>>();
14420 (title, location_tasks, editor.workspace().clone())
14421 })
14422 .context("location tasks preparation")?;
14423
14424 let locations = future::join_all(location_tasks)
14425 .await
14426 .into_iter()
14427 .filter_map(|location| location.transpose())
14428 .collect::<Result<_>>()
14429 .context("location tasks")?;
14430
14431 let Some(workspace) = workspace else {
14432 return Ok(Navigated::No);
14433 };
14434 let opened = workspace
14435 .update_in(cx, |workspace, window, cx| {
14436 Self::open_locations_in_multibuffer(
14437 workspace,
14438 locations,
14439 title,
14440 split,
14441 MultibufferSelectionMode::First,
14442 window,
14443 cx,
14444 )
14445 })
14446 .ok();
14447
14448 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14449 })
14450 } else {
14451 Task::ready(Ok(Navigated::No))
14452 }
14453 }
14454
14455 fn compute_target_location(
14456 &self,
14457 lsp_location: lsp::Location,
14458 server_id: LanguageServerId,
14459 window: &mut Window,
14460 cx: &mut Context<Self>,
14461 ) -> Task<anyhow::Result<Option<Location>>> {
14462 let Some(project) = self.project.clone() else {
14463 return Task::ready(Ok(None));
14464 };
14465
14466 cx.spawn_in(window, async move |editor, cx| {
14467 let location_task = editor.update(cx, |_, cx| {
14468 project.update(cx, |project, cx| {
14469 let language_server_name = project
14470 .language_server_statuses(cx)
14471 .find(|(id, _)| server_id == *id)
14472 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14473 language_server_name.map(|language_server_name| {
14474 project.open_local_buffer_via_lsp(
14475 lsp_location.uri.clone(),
14476 server_id,
14477 language_server_name,
14478 cx,
14479 )
14480 })
14481 })
14482 })?;
14483 let location = match location_task {
14484 Some(task) => Some({
14485 let target_buffer_handle = task.await.context("open local buffer")?;
14486 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14487 let target_start = target_buffer
14488 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14489 let target_end = target_buffer
14490 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14491 target_buffer.anchor_after(target_start)
14492 ..target_buffer.anchor_before(target_end)
14493 })?;
14494 Location {
14495 buffer: target_buffer_handle,
14496 range,
14497 }
14498 }),
14499 None => None,
14500 };
14501 Ok(location)
14502 })
14503 }
14504
14505 pub fn find_all_references(
14506 &mut self,
14507 _: &FindAllReferences,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) -> Option<Task<Result<Navigated>>> {
14511 let selection = self.selections.newest::<usize>(cx);
14512 let multi_buffer = self.buffer.read(cx);
14513 let head = selection.head();
14514
14515 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14516 let head_anchor = multi_buffer_snapshot.anchor_at(
14517 head,
14518 if head < selection.tail() {
14519 Bias::Right
14520 } else {
14521 Bias::Left
14522 },
14523 );
14524
14525 match self
14526 .find_all_references_task_sources
14527 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14528 {
14529 Ok(_) => {
14530 log::info!(
14531 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14532 );
14533 return None;
14534 }
14535 Err(i) => {
14536 self.find_all_references_task_sources.insert(i, head_anchor);
14537 }
14538 }
14539
14540 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14541 let workspace = self.workspace()?;
14542 let project = workspace.read(cx).project().clone();
14543 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14544 Some(cx.spawn_in(window, async move |editor, cx| {
14545 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14546 if let Ok(i) = editor
14547 .find_all_references_task_sources
14548 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14549 {
14550 editor.find_all_references_task_sources.remove(i);
14551 }
14552 });
14553
14554 let locations = references.await?;
14555 if locations.is_empty() {
14556 return anyhow::Ok(Navigated::No);
14557 }
14558
14559 workspace.update_in(cx, |workspace, window, cx| {
14560 let title = locations
14561 .first()
14562 .as_ref()
14563 .map(|location| {
14564 let buffer = location.buffer.read(cx);
14565 format!(
14566 "References to `{}`",
14567 buffer
14568 .text_for_range(location.range.clone())
14569 .collect::<String>()
14570 )
14571 })
14572 .unwrap();
14573 Self::open_locations_in_multibuffer(
14574 workspace,
14575 locations,
14576 title,
14577 false,
14578 MultibufferSelectionMode::First,
14579 window,
14580 cx,
14581 );
14582 Navigated::Yes
14583 })
14584 }))
14585 }
14586
14587 /// Opens a multibuffer with the given project locations in it
14588 pub fn open_locations_in_multibuffer(
14589 workspace: &mut Workspace,
14590 mut locations: Vec<Location>,
14591 title: String,
14592 split: bool,
14593 multibuffer_selection_mode: MultibufferSelectionMode,
14594 window: &mut Window,
14595 cx: &mut Context<Workspace>,
14596 ) {
14597 // If there are multiple definitions, open them in a multibuffer
14598 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14599 let mut locations = locations.into_iter().peekable();
14600 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14601 let capability = workspace.project().read(cx).capability();
14602
14603 let excerpt_buffer = cx.new(|cx| {
14604 let mut multibuffer = MultiBuffer::new(capability);
14605 while let Some(location) = locations.next() {
14606 let buffer = location.buffer.read(cx);
14607 let mut ranges_for_buffer = Vec::new();
14608 let range = location.range.to_point(buffer);
14609 ranges_for_buffer.push(range.clone());
14610
14611 while let Some(next_location) = locations.peek() {
14612 if next_location.buffer == location.buffer {
14613 ranges_for_buffer.push(next_location.range.to_point(buffer));
14614 locations.next();
14615 } else {
14616 break;
14617 }
14618 }
14619
14620 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14621 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14622 PathKey::for_buffer(&location.buffer, cx),
14623 location.buffer.clone(),
14624 ranges_for_buffer,
14625 DEFAULT_MULTIBUFFER_CONTEXT,
14626 cx,
14627 );
14628 ranges.extend(new_ranges)
14629 }
14630
14631 multibuffer.with_title(title)
14632 });
14633
14634 let editor = cx.new(|cx| {
14635 Editor::for_multibuffer(
14636 excerpt_buffer,
14637 Some(workspace.project().clone()),
14638 window,
14639 cx,
14640 )
14641 });
14642 editor.update(cx, |editor, cx| {
14643 match multibuffer_selection_mode {
14644 MultibufferSelectionMode::First => {
14645 if let Some(first_range) = ranges.first() {
14646 editor.change_selections(None, window, cx, |selections| {
14647 selections.clear_disjoint();
14648 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14649 });
14650 }
14651 editor.highlight_background::<Self>(
14652 &ranges,
14653 |theme| theme.editor_highlighted_line_background,
14654 cx,
14655 );
14656 }
14657 MultibufferSelectionMode::All => {
14658 editor.change_selections(None, window, cx, |selections| {
14659 selections.clear_disjoint();
14660 selections.select_anchor_ranges(ranges);
14661 });
14662 }
14663 }
14664 editor.register_buffers_with_language_servers(cx);
14665 });
14666
14667 let item = Box::new(editor);
14668 let item_id = item.item_id();
14669
14670 if split {
14671 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14672 } else {
14673 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14674 let (preview_item_id, preview_item_idx) =
14675 workspace.active_pane().update(cx, |pane, _| {
14676 (pane.preview_item_id(), pane.preview_item_idx())
14677 });
14678
14679 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14680
14681 if let Some(preview_item_id) = preview_item_id {
14682 workspace.active_pane().update(cx, |pane, cx| {
14683 pane.remove_item(preview_item_id, false, false, window, cx);
14684 });
14685 }
14686 } else {
14687 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14688 }
14689 }
14690 workspace.active_pane().update(cx, |pane, cx| {
14691 pane.set_preview_item_id(Some(item_id), cx);
14692 });
14693 }
14694
14695 pub fn rename(
14696 &mut self,
14697 _: &Rename,
14698 window: &mut Window,
14699 cx: &mut Context<Self>,
14700 ) -> Option<Task<Result<()>>> {
14701 use language::ToOffset as _;
14702
14703 let provider = self.semantics_provider.clone()?;
14704 let selection = self.selections.newest_anchor().clone();
14705 let (cursor_buffer, cursor_buffer_position) = self
14706 .buffer
14707 .read(cx)
14708 .text_anchor_for_position(selection.head(), cx)?;
14709 let (tail_buffer, cursor_buffer_position_end) = self
14710 .buffer
14711 .read(cx)
14712 .text_anchor_for_position(selection.tail(), cx)?;
14713 if tail_buffer != cursor_buffer {
14714 return None;
14715 }
14716
14717 let snapshot = cursor_buffer.read(cx).snapshot();
14718 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14719 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14720 let prepare_rename = provider
14721 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14722 .unwrap_or_else(|| Task::ready(Ok(None)));
14723 drop(snapshot);
14724
14725 Some(cx.spawn_in(window, async move |this, cx| {
14726 let rename_range = if let Some(range) = prepare_rename.await? {
14727 Some(range)
14728 } else {
14729 this.update(cx, |this, cx| {
14730 let buffer = this.buffer.read(cx).snapshot(cx);
14731 let mut buffer_highlights = this
14732 .document_highlights_for_position(selection.head(), &buffer)
14733 .filter(|highlight| {
14734 highlight.start.excerpt_id == selection.head().excerpt_id
14735 && highlight.end.excerpt_id == selection.head().excerpt_id
14736 });
14737 buffer_highlights
14738 .next()
14739 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14740 })?
14741 };
14742 if let Some(rename_range) = rename_range {
14743 this.update_in(cx, |this, window, cx| {
14744 let snapshot = cursor_buffer.read(cx).snapshot();
14745 let rename_buffer_range = rename_range.to_offset(&snapshot);
14746 let cursor_offset_in_rename_range =
14747 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14748 let cursor_offset_in_rename_range_end =
14749 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14750
14751 this.take_rename(false, window, cx);
14752 let buffer = this.buffer.read(cx).read(cx);
14753 let cursor_offset = selection.head().to_offset(&buffer);
14754 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14755 let rename_end = rename_start + rename_buffer_range.len();
14756 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14757 let mut old_highlight_id = None;
14758 let old_name: Arc<str> = buffer
14759 .chunks(rename_start..rename_end, true)
14760 .map(|chunk| {
14761 if old_highlight_id.is_none() {
14762 old_highlight_id = chunk.syntax_highlight_id;
14763 }
14764 chunk.text
14765 })
14766 .collect::<String>()
14767 .into();
14768
14769 drop(buffer);
14770
14771 // Position the selection in the rename editor so that it matches the current selection.
14772 this.show_local_selections = false;
14773 let rename_editor = cx.new(|cx| {
14774 let mut editor = Editor::single_line(window, cx);
14775 editor.buffer.update(cx, |buffer, cx| {
14776 buffer.edit([(0..0, old_name.clone())], None, cx)
14777 });
14778 let rename_selection_range = match cursor_offset_in_rename_range
14779 .cmp(&cursor_offset_in_rename_range_end)
14780 {
14781 Ordering::Equal => {
14782 editor.select_all(&SelectAll, window, cx);
14783 return editor;
14784 }
14785 Ordering::Less => {
14786 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14787 }
14788 Ordering::Greater => {
14789 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14790 }
14791 };
14792 if rename_selection_range.end > old_name.len() {
14793 editor.select_all(&SelectAll, window, cx);
14794 } else {
14795 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14796 s.select_ranges([rename_selection_range]);
14797 });
14798 }
14799 editor
14800 });
14801 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14802 if e == &EditorEvent::Focused {
14803 cx.emit(EditorEvent::FocusedIn)
14804 }
14805 })
14806 .detach();
14807
14808 let write_highlights =
14809 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14810 let read_highlights =
14811 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14812 let ranges = write_highlights
14813 .iter()
14814 .flat_map(|(_, ranges)| ranges.iter())
14815 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14816 .cloned()
14817 .collect();
14818
14819 this.highlight_text::<Rename>(
14820 ranges,
14821 HighlightStyle {
14822 fade_out: Some(0.6),
14823 ..Default::default()
14824 },
14825 cx,
14826 );
14827 let rename_focus_handle = rename_editor.focus_handle(cx);
14828 window.focus(&rename_focus_handle);
14829 let block_id = this.insert_blocks(
14830 [BlockProperties {
14831 style: BlockStyle::Flex,
14832 placement: BlockPlacement::Below(range.start),
14833 height: Some(1),
14834 render: Arc::new({
14835 let rename_editor = rename_editor.clone();
14836 move |cx: &mut BlockContext| {
14837 let mut text_style = cx.editor_style.text.clone();
14838 if let Some(highlight_style) = old_highlight_id
14839 .and_then(|h| h.style(&cx.editor_style.syntax))
14840 {
14841 text_style = text_style.highlight(highlight_style);
14842 }
14843 div()
14844 .block_mouse_down()
14845 .pl(cx.anchor_x)
14846 .child(EditorElement::new(
14847 &rename_editor,
14848 EditorStyle {
14849 background: cx.theme().system().transparent,
14850 local_player: cx.editor_style.local_player,
14851 text: text_style,
14852 scrollbar_width: cx.editor_style.scrollbar_width,
14853 syntax: cx.editor_style.syntax.clone(),
14854 status: cx.editor_style.status.clone(),
14855 inlay_hints_style: HighlightStyle {
14856 font_weight: Some(FontWeight::BOLD),
14857 ..make_inlay_hints_style(cx.app)
14858 },
14859 inline_completion_styles: make_suggestion_styles(
14860 cx.app,
14861 ),
14862 ..EditorStyle::default()
14863 },
14864 ))
14865 .into_any_element()
14866 }
14867 }),
14868 priority: 0,
14869 render_in_minimap: true,
14870 }],
14871 Some(Autoscroll::fit()),
14872 cx,
14873 )[0];
14874 this.pending_rename = Some(RenameState {
14875 range,
14876 old_name,
14877 editor: rename_editor,
14878 block_id,
14879 });
14880 })?;
14881 }
14882
14883 Ok(())
14884 }))
14885 }
14886
14887 pub fn confirm_rename(
14888 &mut self,
14889 _: &ConfirmRename,
14890 window: &mut Window,
14891 cx: &mut Context<Self>,
14892 ) -> Option<Task<Result<()>>> {
14893 let rename = self.take_rename(false, window, cx)?;
14894 let workspace = self.workspace()?.downgrade();
14895 let (buffer, start) = self
14896 .buffer
14897 .read(cx)
14898 .text_anchor_for_position(rename.range.start, cx)?;
14899 let (end_buffer, _) = self
14900 .buffer
14901 .read(cx)
14902 .text_anchor_for_position(rename.range.end, cx)?;
14903 if buffer != end_buffer {
14904 return None;
14905 }
14906
14907 let old_name = rename.old_name;
14908 let new_name = rename.editor.read(cx).text(cx);
14909
14910 let rename = self.semantics_provider.as_ref()?.perform_rename(
14911 &buffer,
14912 start,
14913 new_name.clone(),
14914 cx,
14915 )?;
14916
14917 Some(cx.spawn_in(window, async move |editor, cx| {
14918 let project_transaction = rename.await?;
14919 Self::open_project_transaction(
14920 &editor,
14921 workspace,
14922 project_transaction,
14923 format!("Rename: {} → {}", old_name, new_name),
14924 cx,
14925 )
14926 .await?;
14927
14928 editor.update(cx, |editor, cx| {
14929 editor.refresh_document_highlights(cx);
14930 })?;
14931 Ok(())
14932 }))
14933 }
14934
14935 fn take_rename(
14936 &mut self,
14937 moving_cursor: bool,
14938 window: &mut Window,
14939 cx: &mut Context<Self>,
14940 ) -> Option<RenameState> {
14941 let rename = self.pending_rename.take()?;
14942 if rename.editor.focus_handle(cx).is_focused(window) {
14943 window.focus(&self.focus_handle);
14944 }
14945
14946 self.remove_blocks(
14947 [rename.block_id].into_iter().collect(),
14948 Some(Autoscroll::fit()),
14949 cx,
14950 );
14951 self.clear_highlights::<Rename>(cx);
14952 self.show_local_selections = true;
14953
14954 if moving_cursor {
14955 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14956 editor.selections.newest::<usize>(cx).head()
14957 });
14958
14959 // Update the selection to match the position of the selection inside
14960 // the rename editor.
14961 let snapshot = self.buffer.read(cx).read(cx);
14962 let rename_range = rename.range.to_offset(&snapshot);
14963 let cursor_in_editor = snapshot
14964 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14965 .min(rename_range.end);
14966 drop(snapshot);
14967
14968 self.change_selections(None, window, cx, |s| {
14969 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14970 });
14971 } else {
14972 self.refresh_document_highlights(cx);
14973 }
14974
14975 Some(rename)
14976 }
14977
14978 pub fn pending_rename(&self) -> Option<&RenameState> {
14979 self.pending_rename.as_ref()
14980 }
14981
14982 fn format(
14983 &mut self,
14984 _: &Format,
14985 window: &mut Window,
14986 cx: &mut Context<Self>,
14987 ) -> Option<Task<Result<()>>> {
14988 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14989
14990 let project = match &self.project {
14991 Some(project) => project.clone(),
14992 None => return None,
14993 };
14994
14995 Some(self.perform_format(
14996 project,
14997 FormatTrigger::Manual,
14998 FormatTarget::Buffers,
14999 window,
15000 cx,
15001 ))
15002 }
15003
15004 fn format_selections(
15005 &mut self,
15006 _: &FormatSelections,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) -> Option<Task<Result<()>>> {
15010 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15011
15012 let project = match &self.project {
15013 Some(project) => project.clone(),
15014 None => return None,
15015 };
15016
15017 let ranges = self
15018 .selections
15019 .all_adjusted(cx)
15020 .into_iter()
15021 .map(|selection| selection.range())
15022 .collect_vec();
15023
15024 Some(self.perform_format(
15025 project,
15026 FormatTrigger::Manual,
15027 FormatTarget::Ranges(ranges),
15028 window,
15029 cx,
15030 ))
15031 }
15032
15033 fn perform_format(
15034 &mut self,
15035 project: Entity<Project>,
15036 trigger: FormatTrigger,
15037 target: FormatTarget,
15038 window: &mut Window,
15039 cx: &mut Context<Self>,
15040 ) -> Task<Result<()>> {
15041 let buffer = self.buffer.clone();
15042 let (buffers, target) = match target {
15043 FormatTarget::Buffers => {
15044 let mut buffers = buffer.read(cx).all_buffers();
15045 if trigger == FormatTrigger::Save {
15046 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15047 }
15048 (buffers, LspFormatTarget::Buffers)
15049 }
15050 FormatTarget::Ranges(selection_ranges) => {
15051 let multi_buffer = buffer.read(cx);
15052 let snapshot = multi_buffer.read(cx);
15053 let mut buffers = HashSet::default();
15054 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15055 BTreeMap::new();
15056 for selection_range in selection_ranges {
15057 for (buffer, buffer_range, _) in
15058 snapshot.range_to_buffer_ranges(selection_range)
15059 {
15060 let buffer_id = buffer.remote_id();
15061 let start = buffer.anchor_before(buffer_range.start);
15062 let end = buffer.anchor_after(buffer_range.end);
15063 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15064 buffer_id_to_ranges
15065 .entry(buffer_id)
15066 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15067 .or_insert_with(|| vec![start..end]);
15068 }
15069 }
15070 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15071 }
15072 };
15073
15074 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15075 let selections_prev = transaction_id_prev
15076 .and_then(|transaction_id_prev| {
15077 // default to selections as they were after the last edit, if we have them,
15078 // instead of how they are now.
15079 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15080 // will take you back to where you made the last edit, instead of staying where you scrolled
15081 self.selection_history
15082 .transaction(transaction_id_prev)
15083 .map(|t| t.0.clone())
15084 })
15085 .unwrap_or_else(|| {
15086 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15087 self.selections.disjoint_anchors()
15088 });
15089
15090 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15091 let format = project.update(cx, |project, cx| {
15092 project.format(buffers, target, true, trigger, cx)
15093 });
15094
15095 cx.spawn_in(window, async move |editor, cx| {
15096 let transaction = futures::select_biased! {
15097 transaction = format.log_err().fuse() => transaction,
15098 () = timeout => {
15099 log::warn!("timed out waiting for formatting");
15100 None
15101 }
15102 };
15103
15104 buffer
15105 .update(cx, |buffer, cx| {
15106 if let Some(transaction) = transaction {
15107 if !buffer.is_singleton() {
15108 buffer.push_transaction(&transaction.0, cx);
15109 }
15110 }
15111 cx.notify();
15112 })
15113 .ok();
15114
15115 if let Some(transaction_id_now) =
15116 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15117 {
15118 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15119 if has_new_transaction {
15120 _ = editor.update(cx, |editor, _| {
15121 editor
15122 .selection_history
15123 .insert_transaction(transaction_id_now, selections_prev);
15124 });
15125 }
15126 }
15127
15128 Ok(())
15129 })
15130 }
15131
15132 fn organize_imports(
15133 &mut self,
15134 _: &OrganizeImports,
15135 window: &mut Window,
15136 cx: &mut Context<Self>,
15137 ) -> Option<Task<Result<()>>> {
15138 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15139 let project = match &self.project {
15140 Some(project) => project.clone(),
15141 None => return None,
15142 };
15143 Some(self.perform_code_action_kind(
15144 project,
15145 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15146 window,
15147 cx,
15148 ))
15149 }
15150
15151 fn perform_code_action_kind(
15152 &mut self,
15153 project: Entity<Project>,
15154 kind: CodeActionKind,
15155 window: &mut Window,
15156 cx: &mut Context<Self>,
15157 ) -> Task<Result<()>> {
15158 let buffer = self.buffer.clone();
15159 let buffers = buffer.read(cx).all_buffers();
15160 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15161 let apply_action = project.update(cx, |project, cx| {
15162 project.apply_code_action_kind(buffers, kind, true, cx)
15163 });
15164 cx.spawn_in(window, async move |_, cx| {
15165 let transaction = futures::select_biased! {
15166 () = timeout => {
15167 log::warn!("timed out waiting for executing code action");
15168 None
15169 }
15170 transaction = apply_action.log_err().fuse() => transaction,
15171 };
15172 buffer
15173 .update(cx, |buffer, cx| {
15174 // check if we need this
15175 if let Some(transaction) = transaction {
15176 if !buffer.is_singleton() {
15177 buffer.push_transaction(&transaction.0, cx);
15178 }
15179 }
15180 cx.notify();
15181 })
15182 .ok();
15183 Ok(())
15184 })
15185 }
15186
15187 fn restart_language_server(
15188 &mut self,
15189 _: &RestartLanguageServer,
15190 _: &mut Window,
15191 cx: &mut Context<Self>,
15192 ) {
15193 if let Some(project) = self.project.clone() {
15194 self.buffer.update(cx, |multi_buffer, cx| {
15195 project.update(cx, |project, cx| {
15196 project.restart_language_servers_for_buffers(
15197 multi_buffer.all_buffers().into_iter().collect(),
15198 cx,
15199 );
15200 });
15201 })
15202 }
15203 }
15204
15205 fn stop_language_server(
15206 &mut self,
15207 _: &StopLanguageServer,
15208 _: &mut Window,
15209 cx: &mut Context<Self>,
15210 ) {
15211 if let Some(project) = self.project.clone() {
15212 self.buffer.update(cx, |multi_buffer, cx| {
15213 project.update(cx, |project, cx| {
15214 project.stop_language_servers_for_buffers(
15215 multi_buffer.all_buffers().into_iter().collect(),
15216 cx,
15217 );
15218 cx.emit(project::Event::RefreshInlayHints);
15219 });
15220 });
15221 }
15222 }
15223
15224 fn cancel_language_server_work(
15225 workspace: &mut Workspace,
15226 _: &actions::CancelLanguageServerWork,
15227 _: &mut Window,
15228 cx: &mut Context<Workspace>,
15229 ) {
15230 let project = workspace.project();
15231 let buffers = workspace
15232 .active_item(cx)
15233 .and_then(|item| item.act_as::<Editor>(cx))
15234 .map_or(HashSet::default(), |editor| {
15235 editor.read(cx).buffer.read(cx).all_buffers()
15236 });
15237 project.update(cx, |project, cx| {
15238 project.cancel_language_server_work_for_buffers(buffers, cx);
15239 });
15240 }
15241
15242 fn show_character_palette(
15243 &mut self,
15244 _: &ShowCharacterPalette,
15245 window: &mut Window,
15246 _: &mut Context<Self>,
15247 ) {
15248 window.show_character_palette();
15249 }
15250
15251 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15252 if self.mode.is_minimap() {
15253 return;
15254 }
15255
15256 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15257 let buffer = self.buffer.read(cx).snapshot(cx);
15258 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15259 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15260 let is_valid = buffer
15261 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15262 .any(|entry| {
15263 entry.diagnostic.is_primary
15264 && !entry.range.is_empty()
15265 && entry.range.start == primary_range_start
15266 && entry.diagnostic.message == active_diagnostics.active_message
15267 });
15268
15269 if !is_valid {
15270 self.dismiss_diagnostics(cx);
15271 }
15272 }
15273 }
15274
15275 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15276 match &self.active_diagnostics {
15277 ActiveDiagnostic::Group(group) => Some(group),
15278 _ => None,
15279 }
15280 }
15281
15282 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15283 self.dismiss_diagnostics(cx);
15284 self.active_diagnostics = ActiveDiagnostic::All;
15285 }
15286
15287 fn activate_diagnostics(
15288 &mut self,
15289 buffer_id: BufferId,
15290 diagnostic: DiagnosticEntry<usize>,
15291 window: &mut Window,
15292 cx: &mut Context<Self>,
15293 ) {
15294 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15295 return;
15296 }
15297 self.dismiss_diagnostics(cx);
15298 let snapshot = self.snapshot(window, cx);
15299 let buffer = self.buffer.read(cx).snapshot(cx);
15300 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15301 return;
15302 };
15303
15304 let diagnostic_group = buffer
15305 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15306 .collect::<Vec<_>>();
15307
15308 let blocks =
15309 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15310
15311 let blocks = self.display_map.update(cx, |display_map, cx| {
15312 display_map.insert_blocks(blocks, cx).into_iter().collect()
15313 });
15314 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15315 active_range: buffer.anchor_before(diagnostic.range.start)
15316 ..buffer.anchor_after(diagnostic.range.end),
15317 active_message: diagnostic.diagnostic.message.clone(),
15318 group_id: diagnostic.diagnostic.group_id,
15319 blocks,
15320 });
15321 cx.notify();
15322 }
15323
15324 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15325 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15326 return;
15327 };
15328
15329 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15330 if let ActiveDiagnostic::Group(group) = prev {
15331 self.display_map.update(cx, |display_map, cx| {
15332 display_map.remove_blocks(group.blocks, cx);
15333 });
15334 cx.notify();
15335 }
15336 }
15337
15338 /// Disable inline diagnostics rendering for this editor.
15339 pub fn disable_inline_diagnostics(&mut self) {
15340 self.inline_diagnostics_enabled = false;
15341 self.inline_diagnostics_update = Task::ready(());
15342 self.inline_diagnostics.clear();
15343 }
15344
15345 pub fn diagnostics_enabled(&self) -> bool {
15346 self.mode.is_full()
15347 }
15348
15349 pub fn inline_diagnostics_enabled(&self) -> bool {
15350 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15351 }
15352
15353 pub fn show_inline_diagnostics(&self) -> bool {
15354 self.show_inline_diagnostics
15355 }
15356
15357 pub fn toggle_inline_diagnostics(
15358 &mut self,
15359 _: &ToggleInlineDiagnostics,
15360 window: &mut Window,
15361 cx: &mut Context<Editor>,
15362 ) {
15363 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15364 self.refresh_inline_diagnostics(false, window, cx);
15365 }
15366
15367 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15368 self.diagnostics_max_severity = severity;
15369 self.display_map.update(cx, |display_map, _| {
15370 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15371 });
15372 }
15373
15374 pub fn toggle_diagnostics(
15375 &mut self,
15376 _: &ToggleDiagnostics,
15377 window: &mut Window,
15378 cx: &mut Context<Editor>,
15379 ) {
15380 if !self.diagnostics_enabled() {
15381 return;
15382 }
15383
15384 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15385 EditorSettings::get_global(cx)
15386 .diagnostics_max_severity
15387 .filter(|severity| severity != &DiagnosticSeverity::Off)
15388 .unwrap_or(DiagnosticSeverity::Hint)
15389 } else {
15390 DiagnosticSeverity::Off
15391 };
15392 self.set_max_diagnostics_severity(new_severity, cx);
15393 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15394 self.active_diagnostics = ActiveDiagnostic::None;
15395 self.inline_diagnostics_update = Task::ready(());
15396 self.inline_diagnostics.clear();
15397 } else {
15398 self.refresh_inline_diagnostics(false, window, cx);
15399 }
15400
15401 cx.notify();
15402 }
15403
15404 pub fn toggle_minimap(
15405 &mut self,
15406 _: &ToggleMinimap,
15407 window: &mut Window,
15408 cx: &mut Context<Editor>,
15409 ) {
15410 if self.supports_minimap(cx) {
15411 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15412 }
15413 }
15414
15415 fn refresh_inline_diagnostics(
15416 &mut self,
15417 debounce: bool,
15418 window: &mut Window,
15419 cx: &mut Context<Self>,
15420 ) {
15421 let max_severity = ProjectSettings::get_global(cx)
15422 .diagnostics
15423 .inline
15424 .max_severity
15425 .unwrap_or(self.diagnostics_max_severity);
15426
15427 if self.mode.is_minimap()
15428 || !self.inline_diagnostics_enabled()
15429 || !self.show_inline_diagnostics
15430 || max_severity == DiagnosticSeverity::Off
15431 {
15432 self.inline_diagnostics_update = Task::ready(());
15433 self.inline_diagnostics.clear();
15434 return;
15435 }
15436
15437 let debounce_ms = ProjectSettings::get_global(cx)
15438 .diagnostics
15439 .inline
15440 .update_debounce_ms;
15441 let debounce = if debounce && debounce_ms > 0 {
15442 Some(Duration::from_millis(debounce_ms))
15443 } else {
15444 None
15445 };
15446 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15447 let editor = editor.upgrade().unwrap();
15448
15449 if let Some(debounce) = debounce {
15450 cx.background_executor().timer(debounce).await;
15451 }
15452 let Some(snapshot) = editor
15453 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15454 .ok()
15455 else {
15456 return;
15457 };
15458
15459 let new_inline_diagnostics = cx
15460 .background_spawn(async move {
15461 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15462 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15463 let message = diagnostic_entry
15464 .diagnostic
15465 .message
15466 .split_once('\n')
15467 .map(|(line, _)| line)
15468 .map(SharedString::new)
15469 .unwrap_or_else(|| {
15470 SharedString::from(diagnostic_entry.diagnostic.message)
15471 });
15472 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15473 let (Ok(i) | Err(i)) = inline_diagnostics
15474 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15475 inline_diagnostics.insert(
15476 i,
15477 (
15478 start_anchor,
15479 InlineDiagnostic {
15480 message,
15481 group_id: diagnostic_entry.diagnostic.group_id,
15482 start: diagnostic_entry.range.start.to_point(&snapshot),
15483 is_primary: diagnostic_entry.diagnostic.is_primary,
15484 severity: diagnostic_entry.diagnostic.severity,
15485 },
15486 ),
15487 );
15488 }
15489 inline_diagnostics
15490 })
15491 .await;
15492
15493 editor
15494 .update(cx, |editor, cx| {
15495 editor.inline_diagnostics = new_inline_diagnostics;
15496 cx.notify();
15497 })
15498 .ok();
15499 });
15500 }
15501
15502 pub fn set_selections_from_remote(
15503 &mut self,
15504 selections: Vec<Selection<Anchor>>,
15505 pending_selection: Option<Selection<Anchor>>,
15506 window: &mut Window,
15507 cx: &mut Context<Self>,
15508 ) {
15509 let old_cursor_position = self.selections.newest_anchor().head();
15510 self.selections.change_with(cx, |s| {
15511 s.select_anchors(selections);
15512 if let Some(pending_selection) = pending_selection {
15513 s.set_pending(pending_selection, SelectMode::Character);
15514 } else {
15515 s.clear_pending();
15516 }
15517 });
15518 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15519 }
15520
15521 fn push_to_selection_history(&mut self) {
15522 self.selection_history.push(SelectionHistoryEntry {
15523 selections: self.selections.disjoint_anchors(),
15524 select_next_state: self.select_next_state.clone(),
15525 select_prev_state: self.select_prev_state.clone(),
15526 add_selections_state: self.add_selections_state.clone(),
15527 });
15528 }
15529
15530 pub fn transact(
15531 &mut self,
15532 window: &mut Window,
15533 cx: &mut Context<Self>,
15534 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15535 ) -> Option<TransactionId> {
15536 self.start_transaction_at(Instant::now(), window, cx);
15537 update(self, window, cx);
15538 self.end_transaction_at(Instant::now(), cx)
15539 }
15540
15541 pub fn start_transaction_at(
15542 &mut self,
15543 now: Instant,
15544 window: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) {
15547 self.end_selection(window, cx);
15548 if let Some(tx_id) = self
15549 .buffer
15550 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15551 {
15552 self.selection_history
15553 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15554 cx.emit(EditorEvent::TransactionBegun {
15555 transaction_id: tx_id,
15556 })
15557 }
15558 }
15559
15560 pub fn end_transaction_at(
15561 &mut self,
15562 now: Instant,
15563 cx: &mut Context<Self>,
15564 ) -> Option<TransactionId> {
15565 if let Some(transaction_id) = self
15566 .buffer
15567 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15568 {
15569 if let Some((_, end_selections)) =
15570 self.selection_history.transaction_mut(transaction_id)
15571 {
15572 *end_selections = Some(self.selections.disjoint_anchors());
15573 } else {
15574 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15575 }
15576
15577 cx.emit(EditorEvent::Edited { transaction_id });
15578 Some(transaction_id)
15579 } else {
15580 None
15581 }
15582 }
15583
15584 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15585 if self.selection_mark_mode {
15586 self.change_selections(None, window, cx, |s| {
15587 s.move_with(|_, sel| {
15588 sel.collapse_to(sel.head(), SelectionGoal::None);
15589 });
15590 })
15591 }
15592 self.selection_mark_mode = true;
15593 cx.notify();
15594 }
15595
15596 pub fn swap_selection_ends(
15597 &mut self,
15598 _: &actions::SwapSelectionEnds,
15599 window: &mut Window,
15600 cx: &mut Context<Self>,
15601 ) {
15602 self.change_selections(None, window, cx, |s| {
15603 s.move_with(|_, sel| {
15604 if sel.start != sel.end {
15605 sel.reversed = !sel.reversed
15606 }
15607 });
15608 });
15609 self.request_autoscroll(Autoscroll::newest(), cx);
15610 cx.notify();
15611 }
15612
15613 pub fn toggle_fold(
15614 &mut self,
15615 _: &actions::ToggleFold,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 if self.is_singleton(cx) {
15620 let selection = self.selections.newest::<Point>(cx);
15621
15622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15623 let range = if selection.is_empty() {
15624 let point = selection.head().to_display_point(&display_map);
15625 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15626 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15627 .to_point(&display_map);
15628 start..end
15629 } else {
15630 selection.range()
15631 };
15632 if display_map.folds_in_range(range).next().is_some() {
15633 self.unfold_lines(&Default::default(), window, cx)
15634 } else {
15635 self.fold(&Default::default(), window, cx)
15636 }
15637 } else {
15638 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15639 let buffer_ids: HashSet<_> = self
15640 .selections
15641 .disjoint_anchor_ranges()
15642 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15643 .collect();
15644
15645 let should_unfold = buffer_ids
15646 .iter()
15647 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15648
15649 for buffer_id in buffer_ids {
15650 if should_unfold {
15651 self.unfold_buffer(buffer_id, cx);
15652 } else {
15653 self.fold_buffer(buffer_id, cx);
15654 }
15655 }
15656 }
15657 }
15658
15659 pub fn toggle_fold_recursive(
15660 &mut self,
15661 _: &actions::ToggleFoldRecursive,
15662 window: &mut Window,
15663 cx: &mut Context<Self>,
15664 ) {
15665 let selection = self.selections.newest::<Point>(cx);
15666
15667 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15668 let range = if selection.is_empty() {
15669 let point = selection.head().to_display_point(&display_map);
15670 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15671 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15672 .to_point(&display_map);
15673 start..end
15674 } else {
15675 selection.range()
15676 };
15677 if display_map.folds_in_range(range).next().is_some() {
15678 self.unfold_recursive(&Default::default(), window, cx)
15679 } else {
15680 self.fold_recursive(&Default::default(), window, cx)
15681 }
15682 }
15683
15684 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15685 if self.is_singleton(cx) {
15686 let mut to_fold = Vec::new();
15687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15688 let selections = self.selections.all_adjusted(cx);
15689
15690 for selection in selections {
15691 let range = selection.range().sorted();
15692 let buffer_start_row = range.start.row;
15693
15694 if range.start.row != range.end.row {
15695 let mut found = false;
15696 let mut row = range.start.row;
15697 while row <= range.end.row {
15698 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15699 {
15700 found = true;
15701 row = crease.range().end.row + 1;
15702 to_fold.push(crease);
15703 } else {
15704 row += 1
15705 }
15706 }
15707 if found {
15708 continue;
15709 }
15710 }
15711
15712 for row in (0..=range.start.row).rev() {
15713 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15714 if crease.range().end.row >= buffer_start_row {
15715 to_fold.push(crease);
15716 if row <= range.start.row {
15717 break;
15718 }
15719 }
15720 }
15721 }
15722 }
15723
15724 self.fold_creases(to_fold, true, window, cx);
15725 } else {
15726 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15727 let buffer_ids = self
15728 .selections
15729 .disjoint_anchor_ranges()
15730 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15731 .collect::<HashSet<_>>();
15732 for buffer_id in buffer_ids {
15733 self.fold_buffer(buffer_id, cx);
15734 }
15735 }
15736 }
15737
15738 fn fold_at_level(
15739 &mut self,
15740 fold_at: &FoldAtLevel,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) {
15744 if !self.buffer.read(cx).is_singleton() {
15745 return;
15746 }
15747
15748 let fold_at_level = fold_at.0;
15749 let snapshot = self.buffer.read(cx).snapshot(cx);
15750 let mut to_fold = Vec::new();
15751 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15752
15753 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15754 while start_row < end_row {
15755 match self
15756 .snapshot(window, cx)
15757 .crease_for_buffer_row(MultiBufferRow(start_row))
15758 {
15759 Some(crease) => {
15760 let nested_start_row = crease.range().start.row + 1;
15761 let nested_end_row = crease.range().end.row;
15762
15763 if current_level < fold_at_level {
15764 stack.push((nested_start_row, nested_end_row, current_level + 1));
15765 } else if current_level == fold_at_level {
15766 to_fold.push(crease);
15767 }
15768
15769 start_row = nested_end_row + 1;
15770 }
15771 None => start_row += 1,
15772 }
15773 }
15774 }
15775
15776 self.fold_creases(to_fold, true, window, cx);
15777 }
15778
15779 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15780 if self.buffer.read(cx).is_singleton() {
15781 let mut fold_ranges = Vec::new();
15782 let snapshot = self.buffer.read(cx).snapshot(cx);
15783
15784 for row in 0..snapshot.max_row().0 {
15785 if let Some(foldable_range) = self
15786 .snapshot(window, cx)
15787 .crease_for_buffer_row(MultiBufferRow(row))
15788 {
15789 fold_ranges.push(foldable_range);
15790 }
15791 }
15792
15793 self.fold_creases(fold_ranges, true, window, cx);
15794 } else {
15795 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15796 editor
15797 .update_in(cx, |editor, _, cx| {
15798 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15799 editor.fold_buffer(buffer_id, cx);
15800 }
15801 })
15802 .ok();
15803 });
15804 }
15805 }
15806
15807 pub fn fold_function_bodies(
15808 &mut self,
15809 _: &actions::FoldFunctionBodies,
15810 window: &mut Window,
15811 cx: &mut Context<Self>,
15812 ) {
15813 let snapshot = self.buffer.read(cx).snapshot(cx);
15814
15815 let ranges = snapshot
15816 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15817 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15818 .collect::<Vec<_>>();
15819
15820 let creases = ranges
15821 .into_iter()
15822 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15823 .collect();
15824
15825 self.fold_creases(creases, true, window, cx);
15826 }
15827
15828 pub fn fold_recursive(
15829 &mut self,
15830 _: &actions::FoldRecursive,
15831 window: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 let mut to_fold = Vec::new();
15835 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15836 let selections = self.selections.all_adjusted(cx);
15837
15838 for selection in selections {
15839 let range = selection.range().sorted();
15840 let buffer_start_row = range.start.row;
15841
15842 if range.start.row != range.end.row {
15843 let mut found = false;
15844 for row in range.start.row..=range.end.row {
15845 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15846 found = true;
15847 to_fold.push(crease);
15848 }
15849 }
15850 if found {
15851 continue;
15852 }
15853 }
15854
15855 for row in (0..=range.start.row).rev() {
15856 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15857 if crease.range().end.row >= buffer_start_row {
15858 to_fold.push(crease);
15859 } else {
15860 break;
15861 }
15862 }
15863 }
15864 }
15865
15866 self.fold_creases(to_fold, true, window, cx);
15867 }
15868
15869 pub fn fold_at(
15870 &mut self,
15871 buffer_row: MultiBufferRow,
15872 window: &mut Window,
15873 cx: &mut Context<Self>,
15874 ) {
15875 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15876
15877 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15878 let autoscroll = self
15879 .selections
15880 .all::<Point>(cx)
15881 .iter()
15882 .any(|selection| crease.range().overlaps(&selection.range()));
15883
15884 self.fold_creases(vec![crease], autoscroll, window, cx);
15885 }
15886 }
15887
15888 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15889 if self.is_singleton(cx) {
15890 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15891 let buffer = &display_map.buffer_snapshot;
15892 let selections = self.selections.all::<Point>(cx);
15893 let ranges = selections
15894 .iter()
15895 .map(|s| {
15896 let range = s.display_range(&display_map).sorted();
15897 let mut start = range.start.to_point(&display_map);
15898 let mut end = range.end.to_point(&display_map);
15899 start.column = 0;
15900 end.column = buffer.line_len(MultiBufferRow(end.row));
15901 start..end
15902 })
15903 .collect::<Vec<_>>();
15904
15905 self.unfold_ranges(&ranges, true, true, cx);
15906 } else {
15907 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15908 let buffer_ids = self
15909 .selections
15910 .disjoint_anchor_ranges()
15911 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15912 .collect::<HashSet<_>>();
15913 for buffer_id in buffer_ids {
15914 self.unfold_buffer(buffer_id, cx);
15915 }
15916 }
15917 }
15918
15919 pub fn unfold_recursive(
15920 &mut self,
15921 _: &UnfoldRecursive,
15922 _window: &mut Window,
15923 cx: &mut Context<Self>,
15924 ) {
15925 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15926 let selections = self.selections.all::<Point>(cx);
15927 let ranges = selections
15928 .iter()
15929 .map(|s| {
15930 let mut range = s.display_range(&display_map).sorted();
15931 *range.start.column_mut() = 0;
15932 *range.end.column_mut() = display_map.line_len(range.end.row());
15933 let start = range.start.to_point(&display_map);
15934 let end = range.end.to_point(&display_map);
15935 start..end
15936 })
15937 .collect::<Vec<_>>();
15938
15939 self.unfold_ranges(&ranges, true, true, cx);
15940 }
15941
15942 pub fn unfold_at(
15943 &mut self,
15944 buffer_row: MultiBufferRow,
15945 _window: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) {
15948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15949
15950 let intersection_range = Point::new(buffer_row.0, 0)
15951 ..Point::new(
15952 buffer_row.0,
15953 display_map.buffer_snapshot.line_len(buffer_row),
15954 );
15955
15956 let autoscroll = self
15957 .selections
15958 .all::<Point>(cx)
15959 .iter()
15960 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15961
15962 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15963 }
15964
15965 pub fn unfold_all(
15966 &mut self,
15967 _: &actions::UnfoldAll,
15968 _window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 if self.buffer.read(cx).is_singleton() {
15972 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15973 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15974 } else {
15975 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15976 editor
15977 .update(cx, |editor, cx| {
15978 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15979 editor.unfold_buffer(buffer_id, cx);
15980 }
15981 })
15982 .ok();
15983 });
15984 }
15985 }
15986
15987 pub fn fold_selected_ranges(
15988 &mut self,
15989 _: &FoldSelectedRanges,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 let selections = self.selections.all_adjusted(cx);
15994 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15995 let ranges = selections
15996 .into_iter()
15997 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15998 .collect::<Vec<_>>();
15999 self.fold_creases(ranges, true, window, cx);
16000 }
16001
16002 pub fn fold_ranges<T: ToOffset + Clone>(
16003 &mut self,
16004 ranges: Vec<Range<T>>,
16005 auto_scroll: bool,
16006 window: &mut Window,
16007 cx: &mut Context<Self>,
16008 ) {
16009 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16010 let ranges = ranges
16011 .into_iter()
16012 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16013 .collect::<Vec<_>>();
16014 self.fold_creases(ranges, auto_scroll, window, cx);
16015 }
16016
16017 pub fn fold_creases<T: ToOffset + Clone>(
16018 &mut self,
16019 creases: Vec<Crease<T>>,
16020 auto_scroll: bool,
16021 _window: &mut Window,
16022 cx: &mut Context<Self>,
16023 ) {
16024 if creases.is_empty() {
16025 return;
16026 }
16027
16028 let mut buffers_affected = HashSet::default();
16029 let multi_buffer = self.buffer().read(cx);
16030 for crease in &creases {
16031 if let Some((_, buffer, _)) =
16032 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16033 {
16034 buffers_affected.insert(buffer.read(cx).remote_id());
16035 };
16036 }
16037
16038 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16039
16040 if auto_scroll {
16041 self.request_autoscroll(Autoscroll::fit(), cx);
16042 }
16043
16044 cx.notify();
16045
16046 self.scrollbar_marker_state.dirty = true;
16047 self.folds_did_change(cx);
16048 }
16049
16050 /// Removes any folds whose ranges intersect any of the given ranges.
16051 pub fn unfold_ranges<T: ToOffset + Clone>(
16052 &mut self,
16053 ranges: &[Range<T>],
16054 inclusive: bool,
16055 auto_scroll: bool,
16056 cx: &mut Context<Self>,
16057 ) {
16058 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16059 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16060 });
16061 self.folds_did_change(cx);
16062 }
16063
16064 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16065 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16066 return;
16067 }
16068 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16069 self.display_map.update(cx, |display_map, cx| {
16070 display_map.fold_buffers([buffer_id], cx)
16071 });
16072 cx.emit(EditorEvent::BufferFoldToggled {
16073 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16074 folded: true,
16075 });
16076 cx.notify();
16077 }
16078
16079 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16080 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16081 return;
16082 }
16083 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16084 self.display_map.update(cx, |display_map, cx| {
16085 display_map.unfold_buffers([buffer_id], cx);
16086 });
16087 cx.emit(EditorEvent::BufferFoldToggled {
16088 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16089 folded: false,
16090 });
16091 cx.notify();
16092 }
16093
16094 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16095 self.display_map.read(cx).is_buffer_folded(buffer)
16096 }
16097
16098 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16099 self.display_map.read(cx).folded_buffers()
16100 }
16101
16102 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16103 self.display_map.update(cx, |display_map, cx| {
16104 display_map.disable_header_for_buffer(buffer_id, cx);
16105 });
16106 cx.notify();
16107 }
16108
16109 /// Removes any folds with the given ranges.
16110 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16111 &mut self,
16112 ranges: &[Range<T>],
16113 type_id: TypeId,
16114 auto_scroll: bool,
16115 cx: &mut Context<Self>,
16116 ) {
16117 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16118 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16119 });
16120 self.folds_did_change(cx);
16121 }
16122
16123 fn remove_folds_with<T: ToOffset + Clone>(
16124 &mut self,
16125 ranges: &[Range<T>],
16126 auto_scroll: bool,
16127 cx: &mut Context<Self>,
16128 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16129 ) {
16130 if ranges.is_empty() {
16131 return;
16132 }
16133
16134 let mut buffers_affected = HashSet::default();
16135 let multi_buffer = self.buffer().read(cx);
16136 for range in ranges {
16137 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16138 buffers_affected.insert(buffer.read(cx).remote_id());
16139 };
16140 }
16141
16142 self.display_map.update(cx, update);
16143
16144 if auto_scroll {
16145 self.request_autoscroll(Autoscroll::fit(), cx);
16146 }
16147
16148 cx.notify();
16149 self.scrollbar_marker_state.dirty = true;
16150 self.active_indent_guides_state.dirty = true;
16151 }
16152
16153 pub fn update_fold_widths(
16154 &mut self,
16155 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16156 cx: &mut Context<Self>,
16157 ) -> bool {
16158 self.display_map
16159 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16160 }
16161
16162 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16163 self.display_map.read(cx).fold_placeholder.clone()
16164 }
16165
16166 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16167 self.buffer.update(cx, |buffer, cx| {
16168 buffer.set_all_diff_hunks_expanded(cx);
16169 });
16170 }
16171
16172 pub fn expand_all_diff_hunks(
16173 &mut self,
16174 _: &ExpandAllDiffHunks,
16175 _window: &mut Window,
16176 cx: &mut Context<Self>,
16177 ) {
16178 self.buffer.update(cx, |buffer, cx| {
16179 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16180 });
16181 }
16182
16183 pub fn toggle_selected_diff_hunks(
16184 &mut self,
16185 _: &ToggleSelectedDiffHunks,
16186 _window: &mut Window,
16187 cx: &mut Context<Self>,
16188 ) {
16189 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16190 self.toggle_diff_hunks_in_ranges(ranges, cx);
16191 }
16192
16193 pub fn diff_hunks_in_ranges<'a>(
16194 &'a self,
16195 ranges: &'a [Range<Anchor>],
16196 buffer: &'a MultiBufferSnapshot,
16197 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16198 ranges.iter().flat_map(move |range| {
16199 let end_excerpt_id = range.end.excerpt_id;
16200 let range = range.to_point(buffer);
16201 let mut peek_end = range.end;
16202 if range.end.row < buffer.max_row().0 {
16203 peek_end = Point::new(range.end.row + 1, 0);
16204 }
16205 buffer
16206 .diff_hunks_in_range(range.start..peek_end)
16207 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16208 })
16209 }
16210
16211 pub fn has_stageable_diff_hunks_in_ranges(
16212 &self,
16213 ranges: &[Range<Anchor>],
16214 snapshot: &MultiBufferSnapshot,
16215 ) -> bool {
16216 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16217 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16218 }
16219
16220 pub fn toggle_staged_selected_diff_hunks(
16221 &mut self,
16222 _: &::git::ToggleStaged,
16223 _: &mut Window,
16224 cx: &mut Context<Self>,
16225 ) {
16226 let snapshot = self.buffer.read(cx).snapshot(cx);
16227 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16228 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16229 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16230 }
16231
16232 pub fn set_render_diff_hunk_controls(
16233 &mut self,
16234 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16235 cx: &mut Context<Self>,
16236 ) {
16237 self.render_diff_hunk_controls = render_diff_hunk_controls;
16238 cx.notify();
16239 }
16240
16241 pub fn stage_and_next(
16242 &mut self,
16243 _: &::git::StageAndNext,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 self.do_stage_or_unstage_and_next(true, window, cx);
16248 }
16249
16250 pub fn unstage_and_next(
16251 &mut self,
16252 _: &::git::UnstageAndNext,
16253 window: &mut Window,
16254 cx: &mut Context<Self>,
16255 ) {
16256 self.do_stage_or_unstage_and_next(false, window, cx);
16257 }
16258
16259 pub fn stage_or_unstage_diff_hunks(
16260 &mut self,
16261 stage: bool,
16262 ranges: Vec<Range<Anchor>>,
16263 cx: &mut Context<Self>,
16264 ) {
16265 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16266 cx.spawn(async move |this, cx| {
16267 task.await?;
16268 this.update(cx, |this, cx| {
16269 let snapshot = this.buffer.read(cx).snapshot(cx);
16270 let chunk_by = this
16271 .diff_hunks_in_ranges(&ranges, &snapshot)
16272 .chunk_by(|hunk| hunk.buffer_id);
16273 for (buffer_id, hunks) in &chunk_by {
16274 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16275 }
16276 })
16277 })
16278 .detach_and_log_err(cx);
16279 }
16280
16281 fn save_buffers_for_ranges_if_needed(
16282 &mut self,
16283 ranges: &[Range<Anchor>],
16284 cx: &mut Context<Editor>,
16285 ) -> Task<Result<()>> {
16286 let multibuffer = self.buffer.read(cx);
16287 let snapshot = multibuffer.read(cx);
16288 let buffer_ids: HashSet<_> = ranges
16289 .iter()
16290 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16291 .collect();
16292 drop(snapshot);
16293
16294 let mut buffers = HashSet::default();
16295 for buffer_id in buffer_ids {
16296 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16297 let buffer = buffer_entity.read(cx);
16298 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16299 {
16300 buffers.insert(buffer_entity);
16301 }
16302 }
16303 }
16304
16305 if let Some(project) = &self.project {
16306 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16307 } else {
16308 Task::ready(Ok(()))
16309 }
16310 }
16311
16312 fn do_stage_or_unstage_and_next(
16313 &mut self,
16314 stage: bool,
16315 window: &mut Window,
16316 cx: &mut Context<Self>,
16317 ) {
16318 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16319
16320 if ranges.iter().any(|range| range.start != range.end) {
16321 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16322 return;
16323 }
16324
16325 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16326 let snapshot = self.snapshot(window, cx);
16327 let position = self.selections.newest::<Point>(cx).head();
16328 let mut row = snapshot
16329 .buffer_snapshot
16330 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16331 .find(|hunk| hunk.row_range.start.0 > position.row)
16332 .map(|hunk| hunk.row_range.start);
16333
16334 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16335 // Outside of the project diff editor, wrap around to the beginning.
16336 if !all_diff_hunks_expanded {
16337 row = row.or_else(|| {
16338 snapshot
16339 .buffer_snapshot
16340 .diff_hunks_in_range(Point::zero()..position)
16341 .find(|hunk| hunk.row_range.end.0 < position.row)
16342 .map(|hunk| hunk.row_range.start)
16343 });
16344 }
16345
16346 if let Some(row) = row {
16347 let destination = Point::new(row.0, 0);
16348 let autoscroll = Autoscroll::center();
16349
16350 self.unfold_ranges(&[destination..destination], false, false, cx);
16351 self.change_selections(Some(autoscroll), window, cx, |s| {
16352 s.select_ranges([destination..destination]);
16353 });
16354 }
16355 }
16356
16357 fn do_stage_or_unstage(
16358 &self,
16359 stage: bool,
16360 buffer_id: BufferId,
16361 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16362 cx: &mut App,
16363 ) -> Option<()> {
16364 let project = self.project.as_ref()?;
16365 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16366 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16367 let buffer_snapshot = buffer.read(cx).snapshot();
16368 let file_exists = buffer_snapshot
16369 .file()
16370 .is_some_and(|file| file.disk_state().exists());
16371 diff.update(cx, |diff, cx| {
16372 diff.stage_or_unstage_hunks(
16373 stage,
16374 &hunks
16375 .map(|hunk| buffer_diff::DiffHunk {
16376 buffer_range: hunk.buffer_range,
16377 diff_base_byte_range: hunk.diff_base_byte_range,
16378 secondary_status: hunk.secondary_status,
16379 range: Point::zero()..Point::zero(), // unused
16380 })
16381 .collect::<Vec<_>>(),
16382 &buffer_snapshot,
16383 file_exists,
16384 cx,
16385 )
16386 });
16387 None
16388 }
16389
16390 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16391 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16392 self.buffer
16393 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16394 }
16395
16396 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16397 self.buffer.update(cx, |buffer, cx| {
16398 let ranges = vec![Anchor::min()..Anchor::max()];
16399 if !buffer.all_diff_hunks_expanded()
16400 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16401 {
16402 buffer.collapse_diff_hunks(ranges, cx);
16403 true
16404 } else {
16405 false
16406 }
16407 })
16408 }
16409
16410 fn toggle_diff_hunks_in_ranges(
16411 &mut self,
16412 ranges: Vec<Range<Anchor>>,
16413 cx: &mut Context<Editor>,
16414 ) {
16415 self.buffer.update(cx, |buffer, cx| {
16416 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16417 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16418 })
16419 }
16420
16421 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16422 self.buffer.update(cx, |buffer, cx| {
16423 let snapshot = buffer.snapshot(cx);
16424 let excerpt_id = range.end.excerpt_id;
16425 let point_range = range.to_point(&snapshot);
16426 let expand = !buffer.single_hunk_is_expanded(range, cx);
16427 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16428 })
16429 }
16430
16431 pub(crate) fn apply_all_diff_hunks(
16432 &mut self,
16433 _: &ApplyAllDiffHunks,
16434 window: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) {
16437 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16438
16439 let buffers = self.buffer.read(cx).all_buffers();
16440 for branch_buffer in buffers {
16441 branch_buffer.update(cx, |branch_buffer, cx| {
16442 branch_buffer.merge_into_base(Vec::new(), cx);
16443 });
16444 }
16445
16446 if let Some(project) = self.project.clone() {
16447 self.save(true, project, window, cx).detach_and_log_err(cx);
16448 }
16449 }
16450
16451 pub(crate) fn apply_selected_diff_hunks(
16452 &mut self,
16453 _: &ApplyDiffHunk,
16454 window: &mut Window,
16455 cx: &mut Context<Self>,
16456 ) {
16457 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16458 let snapshot = self.snapshot(window, cx);
16459 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16460 let mut ranges_by_buffer = HashMap::default();
16461 self.transact(window, cx, |editor, _window, cx| {
16462 for hunk in hunks {
16463 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16464 ranges_by_buffer
16465 .entry(buffer.clone())
16466 .or_insert_with(Vec::new)
16467 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16468 }
16469 }
16470
16471 for (buffer, ranges) in ranges_by_buffer {
16472 buffer.update(cx, |buffer, cx| {
16473 buffer.merge_into_base(ranges, cx);
16474 });
16475 }
16476 });
16477
16478 if let Some(project) = self.project.clone() {
16479 self.save(true, project, window, cx).detach_and_log_err(cx);
16480 }
16481 }
16482
16483 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16484 if hovered != self.gutter_hovered {
16485 self.gutter_hovered = hovered;
16486 cx.notify();
16487 }
16488 }
16489
16490 pub fn insert_blocks(
16491 &mut self,
16492 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16493 autoscroll: Option<Autoscroll>,
16494 cx: &mut Context<Self>,
16495 ) -> Vec<CustomBlockId> {
16496 let blocks = self
16497 .display_map
16498 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16499 if let Some(autoscroll) = autoscroll {
16500 self.request_autoscroll(autoscroll, cx);
16501 }
16502 cx.notify();
16503 blocks
16504 }
16505
16506 pub fn resize_blocks(
16507 &mut self,
16508 heights: HashMap<CustomBlockId, u32>,
16509 autoscroll: Option<Autoscroll>,
16510 cx: &mut Context<Self>,
16511 ) {
16512 self.display_map
16513 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16514 if let Some(autoscroll) = autoscroll {
16515 self.request_autoscroll(autoscroll, cx);
16516 }
16517 cx.notify();
16518 }
16519
16520 pub fn replace_blocks(
16521 &mut self,
16522 renderers: HashMap<CustomBlockId, RenderBlock>,
16523 autoscroll: Option<Autoscroll>,
16524 cx: &mut Context<Self>,
16525 ) {
16526 self.display_map
16527 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16528 if let Some(autoscroll) = autoscroll {
16529 self.request_autoscroll(autoscroll, cx);
16530 }
16531 cx.notify();
16532 }
16533
16534 pub fn remove_blocks(
16535 &mut self,
16536 block_ids: HashSet<CustomBlockId>,
16537 autoscroll: Option<Autoscroll>,
16538 cx: &mut Context<Self>,
16539 ) {
16540 self.display_map.update(cx, |display_map, cx| {
16541 display_map.remove_blocks(block_ids, cx)
16542 });
16543 if let Some(autoscroll) = autoscroll {
16544 self.request_autoscroll(autoscroll, cx);
16545 }
16546 cx.notify();
16547 }
16548
16549 pub fn row_for_block(
16550 &self,
16551 block_id: CustomBlockId,
16552 cx: &mut Context<Self>,
16553 ) -> Option<DisplayRow> {
16554 self.display_map
16555 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16556 }
16557
16558 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16559 self.focused_block = Some(focused_block);
16560 }
16561
16562 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16563 self.focused_block.take()
16564 }
16565
16566 pub fn insert_creases(
16567 &mut self,
16568 creases: impl IntoIterator<Item = Crease<Anchor>>,
16569 cx: &mut Context<Self>,
16570 ) -> Vec<CreaseId> {
16571 self.display_map
16572 .update(cx, |map, cx| map.insert_creases(creases, cx))
16573 }
16574
16575 pub fn remove_creases(
16576 &mut self,
16577 ids: impl IntoIterator<Item = CreaseId>,
16578 cx: &mut Context<Self>,
16579 ) -> Vec<(CreaseId, Range<Anchor>)> {
16580 self.display_map
16581 .update(cx, |map, cx| map.remove_creases(ids, cx))
16582 }
16583
16584 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16585 self.display_map
16586 .update(cx, |map, cx| map.snapshot(cx))
16587 .longest_row()
16588 }
16589
16590 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16591 self.display_map
16592 .update(cx, |map, cx| map.snapshot(cx))
16593 .max_point()
16594 }
16595
16596 pub fn text(&self, cx: &App) -> String {
16597 self.buffer.read(cx).read(cx).text()
16598 }
16599
16600 pub fn is_empty(&self, cx: &App) -> bool {
16601 self.buffer.read(cx).read(cx).is_empty()
16602 }
16603
16604 pub fn text_option(&self, cx: &App) -> Option<String> {
16605 let text = self.text(cx);
16606 let text = text.trim();
16607
16608 if text.is_empty() {
16609 return None;
16610 }
16611
16612 Some(text.to_string())
16613 }
16614
16615 pub fn set_text(
16616 &mut self,
16617 text: impl Into<Arc<str>>,
16618 window: &mut Window,
16619 cx: &mut Context<Self>,
16620 ) {
16621 self.transact(window, cx, |this, _, cx| {
16622 this.buffer
16623 .read(cx)
16624 .as_singleton()
16625 .expect("you can only call set_text on editors for singleton buffers")
16626 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16627 });
16628 }
16629
16630 pub fn display_text(&self, cx: &mut App) -> String {
16631 self.display_map
16632 .update(cx, |map, cx| map.snapshot(cx))
16633 .text()
16634 }
16635
16636 fn create_minimap(
16637 &self,
16638 minimap_settings: MinimapSettings,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) -> Option<Entity<Self>> {
16642 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16643 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16644 }
16645
16646 fn initialize_new_minimap(
16647 &self,
16648 minimap_settings: MinimapSettings,
16649 window: &mut Window,
16650 cx: &mut Context<Self>,
16651 ) -> Entity<Self> {
16652 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16653
16654 let mut minimap = Editor::new_internal(
16655 EditorMode::Minimap {
16656 parent: cx.weak_entity(),
16657 },
16658 self.buffer.clone(),
16659 self.project.clone(),
16660 Some(self.display_map.clone()),
16661 window,
16662 cx,
16663 );
16664 minimap.scroll_manager.clone_state(&self.scroll_manager);
16665 minimap.set_text_style_refinement(TextStyleRefinement {
16666 font_size: Some(MINIMAP_FONT_SIZE),
16667 font_weight: Some(MINIMAP_FONT_WEIGHT),
16668 ..Default::default()
16669 });
16670 minimap.update_minimap_configuration(minimap_settings, cx);
16671 cx.new(|_| minimap)
16672 }
16673
16674 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16675 let current_line_highlight = minimap_settings
16676 .current_line_highlight
16677 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16678 self.set_current_line_highlight(Some(current_line_highlight));
16679 }
16680
16681 pub fn minimap(&self) -> Option<&Entity<Self>> {
16682 self.minimap
16683 .as_ref()
16684 .filter(|_| self.minimap_visibility.visible())
16685 }
16686
16687 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16688 let mut wrap_guides = smallvec::smallvec![];
16689
16690 if self.show_wrap_guides == Some(false) {
16691 return wrap_guides;
16692 }
16693
16694 let settings = self.buffer.read(cx).language_settings(cx);
16695 if settings.show_wrap_guides {
16696 match self.soft_wrap_mode(cx) {
16697 SoftWrap::Column(soft_wrap) => {
16698 wrap_guides.push((soft_wrap as usize, true));
16699 }
16700 SoftWrap::Bounded(soft_wrap) => {
16701 wrap_guides.push((soft_wrap as usize, true));
16702 }
16703 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16704 }
16705 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16706 }
16707
16708 wrap_guides
16709 }
16710
16711 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16712 let settings = self.buffer.read(cx).language_settings(cx);
16713 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16714 match mode {
16715 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16716 SoftWrap::None
16717 }
16718 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16719 language_settings::SoftWrap::PreferredLineLength => {
16720 SoftWrap::Column(settings.preferred_line_length)
16721 }
16722 language_settings::SoftWrap::Bounded => {
16723 SoftWrap::Bounded(settings.preferred_line_length)
16724 }
16725 }
16726 }
16727
16728 pub fn set_soft_wrap_mode(
16729 &mut self,
16730 mode: language_settings::SoftWrap,
16731
16732 cx: &mut Context<Self>,
16733 ) {
16734 self.soft_wrap_mode_override = Some(mode);
16735 cx.notify();
16736 }
16737
16738 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16739 self.hard_wrap = hard_wrap;
16740 cx.notify();
16741 }
16742
16743 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16744 self.text_style_refinement = Some(style);
16745 }
16746
16747 /// called by the Element so we know what style we were most recently rendered with.
16748 pub(crate) fn set_style(
16749 &mut self,
16750 style: EditorStyle,
16751 window: &mut Window,
16752 cx: &mut Context<Self>,
16753 ) {
16754 // We intentionally do not inform the display map about the minimap style
16755 // so that wrapping is not recalculated and stays consistent for the editor
16756 // and its linked minimap.
16757 if !self.mode.is_minimap() {
16758 let rem_size = window.rem_size();
16759 self.display_map.update(cx, |map, cx| {
16760 map.set_font(
16761 style.text.font(),
16762 style.text.font_size.to_pixels(rem_size),
16763 cx,
16764 )
16765 });
16766 }
16767 self.style = Some(style);
16768 }
16769
16770 pub fn style(&self) -> Option<&EditorStyle> {
16771 self.style.as_ref()
16772 }
16773
16774 // Called by the element. This method is not designed to be called outside of the editor
16775 // element's layout code because it does not notify when rewrapping is computed synchronously.
16776 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16777 self.display_map
16778 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16779 }
16780
16781 pub fn set_soft_wrap(&mut self) {
16782 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16783 }
16784
16785 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16786 if self.soft_wrap_mode_override.is_some() {
16787 self.soft_wrap_mode_override.take();
16788 } else {
16789 let soft_wrap = match self.soft_wrap_mode(cx) {
16790 SoftWrap::GitDiff => return,
16791 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16792 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16793 language_settings::SoftWrap::None
16794 }
16795 };
16796 self.soft_wrap_mode_override = Some(soft_wrap);
16797 }
16798 cx.notify();
16799 }
16800
16801 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16802 let Some(workspace) = self.workspace() else {
16803 return;
16804 };
16805 let fs = workspace.read(cx).app_state().fs.clone();
16806 let current_show = TabBarSettings::get_global(cx).show;
16807 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16808 setting.show = Some(!current_show);
16809 });
16810 }
16811
16812 pub fn toggle_indent_guides(
16813 &mut self,
16814 _: &ToggleIndentGuides,
16815 _: &mut Window,
16816 cx: &mut Context<Self>,
16817 ) {
16818 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16819 self.buffer
16820 .read(cx)
16821 .language_settings(cx)
16822 .indent_guides
16823 .enabled
16824 });
16825 self.show_indent_guides = Some(!currently_enabled);
16826 cx.notify();
16827 }
16828
16829 fn should_show_indent_guides(&self) -> Option<bool> {
16830 self.show_indent_guides
16831 }
16832
16833 pub fn toggle_line_numbers(
16834 &mut self,
16835 _: &ToggleLineNumbers,
16836 _: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) {
16839 let mut editor_settings = EditorSettings::get_global(cx).clone();
16840 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16841 EditorSettings::override_global(editor_settings, cx);
16842 }
16843
16844 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16845 if let Some(show_line_numbers) = self.show_line_numbers {
16846 return show_line_numbers;
16847 }
16848 EditorSettings::get_global(cx).gutter.line_numbers
16849 }
16850
16851 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16852 self.use_relative_line_numbers
16853 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16854 }
16855
16856 pub fn toggle_relative_line_numbers(
16857 &mut self,
16858 _: &ToggleRelativeLineNumbers,
16859 _: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let is_relative = self.should_use_relative_line_numbers(cx);
16863 self.set_relative_line_number(Some(!is_relative), cx)
16864 }
16865
16866 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16867 self.use_relative_line_numbers = is_relative;
16868 cx.notify();
16869 }
16870
16871 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16872 self.show_gutter = show_gutter;
16873 cx.notify();
16874 }
16875
16876 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16877 self.show_scrollbars = show_scrollbars;
16878 cx.notify();
16879 }
16880
16881 pub fn set_minimap_visibility(
16882 &mut self,
16883 minimap_visibility: MinimapVisibility,
16884 window: &mut Window,
16885 cx: &mut Context<Self>,
16886 ) {
16887 if self.minimap_visibility != minimap_visibility {
16888 if minimap_visibility.visible() && self.minimap.is_none() {
16889 let minimap_settings = EditorSettings::get_global(cx).minimap;
16890 self.minimap =
16891 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16892 }
16893 self.minimap_visibility = minimap_visibility;
16894 cx.notify();
16895 }
16896 }
16897
16898 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16899 self.set_show_scrollbars(false, cx);
16900 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16901 }
16902
16903 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16904 self.show_line_numbers = Some(show_line_numbers);
16905 cx.notify();
16906 }
16907
16908 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16909 self.disable_expand_excerpt_buttons = true;
16910 cx.notify();
16911 }
16912
16913 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16914 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16915 cx.notify();
16916 }
16917
16918 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16919 self.show_code_actions = Some(show_code_actions);
16920 cx.notify();
16921 }
16922
16923 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16924 self.show_runnables = Some(show_runnables);
16925 cx.notify();
16926 }
16927
16928 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16929 self.show_breakpoints = Some(show_breakpoints);
16930 cx.notify();
16931 }
16932
16933 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16934 if self.display_map.read(cx).masked != masked {
16935 self.display_map.update(cx, |map, _| map.masked = masked);
16936 }
16937 cx.notify()
16938 }
16939
16940 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16941 self.show_wrap_guides = Some(show_wrap_guides);
16942 cx.notify();
16943 }
16944
16945 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16946 self.show_indent_guides = Some(show_indent_guides);
16947 cx.notify();
16948 }
16949
16950 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16951 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16952 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16953 if let Some(dir) = file.abs_path(cx).parent() {
16954 return Some(dir.to_owned());
16955 }
16956 }
16957
16958 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16959 return Some(project_path.path.to_path_buf());
16960 }
16961 }
16962
16963 None
16964 }
16965
16966 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16967 self.active_excerpt(cx)?
16968 .1
16969 .read(cx)
16970 .file()
16971 .and_then(|f| f.as_local())
16972 }
16973
16974 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16975 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16976 let buffer = buffer.read(cx);
16977 if let Some(project_path) = buffer.project_path(cx) {
16978 let project = self.project.as_ref()?.read(cx);
16979 project.absolute_path(&project_path, cx)
16980 } else {
16981 buffer
16982 .file()
16983 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16984 }
16985 })
16986 }
16987
16988 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16989 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16990 let project_path = buffer.read(cx).project_path(cx)?;
16991 let project = self.project.as_ref()?.read(cx);
16992 let entry = project.entry_for_path(&project_path, cx)?;
16993 let path = entry.path.to_path_buf();
16994 Some(path)
16995 })
16996 }
16997
16998 pub fn reveal_in_finder(
16999 &mut self,
17000 _: &RevealInFileManager,
17001 _window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) {
17004 if let Some(target) = self.target_file(cx) {
17005 cx.reveal_path(&target.abs_path(cx));
17006 }
17007 }
17008
17009 pub fn copy_path(
17010 &mut self,
17011 _: &zed_actions::workspace::CopyPath,
17012 _window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) {
17015 if let Some(path) = self.target_file_abs_path(cx) {
17016 if let Some(path) = path.to_str() {
17017 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17018 }
17019 }
17020 }
17021
17022 pub fn copy_relative_path(
17023 &mut self,
17024 _: &zed_actions::workspace::CopyRelativePath,
17025 _window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 if let Some(path) = self.target_file_path(cx) {
17029 if let Some(path) = path.to_str() {
17030 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17031 }
17032 }
17033 }
17034
17035 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17036 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17037 buffer.read(cx).project_path(cx)
17038 } else {
17039 None
17040 }
17041 }
17042
17043 // Returns true if the editor handled a go-to-line request
17044 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17045 maybe!({
17046 let breakpoint_store = self.breakpoint_store.as_ref()?;
17047
17048 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17049 else {
17050 self.clear_row_highlights::<ActiveDebugLine>();
17051 return None;
17052 };
17053
17054 let position = active_stack_frame.position;
17055 let buffer_id = position.buffer_id?;
17056 let snapshot = self
17057 .project
17058 .as_ref()?
17059 .read(cx)
17060 .buffer_for_id(buffer_id, cx)?
17061 .read(cx)
17062 .snapshot();
17063
17064 let mut handled = false;
17065 for (id, ExcerptRange { context, .. }) in
17066 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17067 {
17068 if context.start.cmp(&position, &snapshot).is_ge()
17069 || context.end.cmp(&position, &snapshot).is_lt()
17070 {
17071 continue;
17072 }
17073 let snapshot = self.buffer.read(cx).snapshot(cx);
17074 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17075
17076 handled = true;
17077 self.clear_row_highlights::<ActiveDebugLine>();
17078
17079 self.go_to_line::<ActiveDebugLine>(
17080 multibuffer_anchor,
17081 Some(cx.theme().colors().editor_debugger_active_line_background),
17082 window,
17083 cx,
17084 );
17085
17086 cx.notify();
17087 }
17088
17089 handled.then_some(())
17090 })
17091 .is_some()
17092 }
17093
17094 pub fn copy_file_name_without_extension(
17095 &mut self,
17096 _: &CopyFileNameWithoutExtension,
17097 _: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) {
17100 if let Some(file) = self.target_file(cx) {
17101 if let Some(file_stem) = file.path().file_stem() {
17102 if let Some(name) = file_stem.to_str() {
17103 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17104 }
17105 }
17106 }
17107 }
17108
17109 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17110 if let Some(file) = self.target_file(cx) {
17111 if let Some(file_name) = file.path().file_name() {
17112 if let Some(name) = file_name.to_str() {
17113 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17114 }
17115 }
17116 }
17117 }
17118
17119 pub fn toggle_git_blame(
17120 &mut self,
17121 _: &::git::Blame,
17122 window: &mut Window,
17123 cx: &mut Context<Self>,
17124 ) {
17125 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17126
17127 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17128 self.start_git_blame(true, window, cx);
17129 }
17130
17131 cx.notify();
17132 }
17133
17134 pub fn toggle_git_blame_inline(
17135 &mut self,
17136 _: &ToggleGitBlameInline,
17137 window: &mut Window,
17138 cx: &mut Context<Self>,
17139 ) {
17140 self.toggle_git_blame_inline_internal(true, window, cx);
17141 cx.notify();
17142 }
17143
17144 pub fn open_git_blame_commit(
17145 &mut self,
17146 _: &OpenGitBlameCommit,
17147 window: &mut Window,
17148 cx: &mut Context<Self>,
17149 ) {
17150 self.open_git_blame_commit_internal(window, cx);
17151 }
17152
17153 fn open_git_blame_commit_internal(
17154 &mut self,
17155 window: &mut Window,
17156 cx: &mut Context<Self>,
17157 ) -> Option<()> {
17158 let blame = self.blame.as_ref()?;
17159 let snapshot = self.snapshot(window, cx);
17160 let cursor = self.selections.newest::<Point>(cx).head();
17161 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17162 let blame_entry = blame
17163 .update(cx, |blame, cx| {
17164 blame
17165 .blame_for_rows(
17166 &[RowInfo {
17167 buffer_id: Some(buffer.remote_id()),
17168 buffer_row: Some(point.row),
17169 ..Default::default()
17170 }],
17171 cx,
17172 )
17173 .next()
17174 })
17175 .flatten()?;
17176 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17177 let repo = blame.read(cx).repository(cx)?;
17178 let workspace = self.workspace()?.downgrade();
17179 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17180 None
17181 }
17182
17183 pub fn git_blame_inline_enabled(&self) -> bool {
17184 self.git_blame_inline_enabled
17185 }
17186
17187 pub fn toggle_selection_menu(
17188 &mut self,
17189 _: &ToggleSelectionMenu,
17190 _: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) {
17193 self.show_selection_menu = self
17194 .show_selection_menu
17195 .map(|show_selections_menu| !show_selections_menu)
17196 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17197
17198 cx.notify();
17199 }
17200
17201 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17202 self.show_selection_menu
17203 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17204 }
17205
17206 fn start_git_blame(
17207 &mut self,
17208 user_triggered: bool,
17209 window: &mut Window,
17210 cx: &mut Context<Self>,
17211 ) {
17212 if let Some(project) = self.project.as_ref() {
17213 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17214 return;
17215 };
17216
17217 if buffer.read(cx).file().is_none() {
17218 return;
17219 }
17220
17221 let focused = self.focus_handle(cx).contains_focused(window, cx);
17222
17223 let project = project.clone();
17224 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17225 self.blame_subscription =
17226 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17227 self.blame = Some(blame);
17228 }
17229 }
17230
17231 fn toggle_git_blame_inline_internal(
17232 &mut self,
17233 user_triggered: bool,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) {
17237 if self.git_blame_inline_enabled {
17238 self.git_blame_inline_enabled = false;
17239 self.show_git_blame_inline = false;
17240 self.show_git_blame_inline_delay_task.take();
17241 } else {
17242 self.git_blame_inline_enabled = true;
17243 self.start_git_blame_inline(user_triggered, window, cx);
17244 }
17245
17246 cx.notify();
17247 }
17248
17249 fn start_git_blame_inline(
17250 &mut self,
17251 user_triggered: bool,
17252 window: &mut Window,
17253 cx: &mut Context<Self>,
17254 ) {
17255 self.start_git_blame(user_triggered, window, cx);
17256
17257 if ProjectSettings::get_global(cx)
17258 .git
17259 .inline_blame_delay()
17260 .is_some()
17261 {
17262 self.start_inline_blame_timer(window, cx);
17263 } else {
17264 self.show_git_blame_inline = true
17265 }
17266 }
17267
17268 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17269 self.blame.as_ref()
17270 }
17271
17272 pub fn show_git_blame_gutter(&self) -> bool {
17273 self.show_git_blame_gutter
17274 }
17275
17276 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17277 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17278 }
17279
17280 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17281 self.show_git_blame_inline
17282 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17283 && !self.newest_selection_head_on_empty_line(cx)
17284 && self.has_blame_entries(cx)
17285 }
17286
17287 fn has_blame_entries(&self, cx: &App) -> bool {
17288 self.blame()
17289 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17290 }
17291
17292 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17293 let cursor_anchor = self.selections.newest_anchor().head();
17294
17295 let snapshot = self.buffer.read(cx).snapshot(cx);
17296 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17297
17298 snapshot.line_len(buffer_row) == 0
17299 }
17300
17301 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17302 let buffer_and_selection = maybe!({
17303 let selection = self.selections.newest::<Point>(cx);
17304 let selection_range = selection.range();
17305
17306 let multi_buffer = self.buffer().read(cx);
17307 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17308 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17309
17310 let (buffer, range, _) = if selection.reversed {
17311 buffer_ranges.first()
17312 } else {
17313 buffer_ranges.last()
17314 }?;
17315
17316 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17317 ..text::ToPoint::to_point(&range.end, &buffer).row;
17318 Some((
17319 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17320 selection,
17321 ))
17322 });
17323
17324 let Some((buffer, selection)) = buffer_and_selection else {
17325 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17326 };
17327
17328 let Some(project) = self.project.as_ref() else {
17329 return Task::ready(Err(anyhow!("editor does not have project")));
17330 };
17331
17332 project.update(cx, |project, cx| {
17333 project.get_permalink_to_line(&buffer, selection, cx)
17334 })
17335 }
17336
17337 pub fn copy_permalink_to_line(
17338 &mut self,
17339 _: &CopyPermalinkToLine,
17340 window: &mut Window,
17341 cx: &mut Context<Self>,
17342 ) {
17343 let permalink_task = self.get_permalink_to_line(cx);
17344 let workspace = self.workspace();
17345
17346 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17347 Ok(permalink) => {
17348 cx.update(|_, cx| {
17349 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17350 })
17351 .ok();
17352 }
17353 Err(err) => {
17354 let message = format!("Failed to copy permalink: {err}");
17355
17356 anyhow::Result::<()>::Err(err).log_err();
17357
17358 if let Some(workspace) = workspace {
17359 workspace
17360 .update_in(cx, |workspace, _, cx| {
17361 struct CopyPermalinkToLine;
17362
17363 workspace.show_toast(
17364 Toast::new(
17365 NotificationId::unique::<CopyPermalinkToLine>(),
17366 message,
17367 ),
17368 cx,
17369 )
17370 })
17371 .ok();
17372 }
17373 }
17374 })
17375 .detach();
17376 }
17377
17378 pub fn copy_file_location(
17379 &mut self,
17380 _: &CopyFileLocation,
17381 _: &mut Window,
17382 cx: &mut Context<Self>,
17383 ) {
17384 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17385 if let Some(file) = self.target_file(cx) {
17386 if let Some(path) = file.path().to_str() {
17387 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17388 }
17389 }
17390 }
17391
17392 pub fn open_permalink_to_line(
17393 &mut self,
17394 _: &OpenPermalinkToLine,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) {
17398 let permalink_task = self.get_permalink_to_line(cx);
17399 let workspace = self.workspace();
17400
17401 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17402 Ok(permalink) => {
17403 cx.update(|_, cx| {
17404 cx.open_url(permalink.as_ref());
17405 })
17406 .ok();
17407 }
17408 Err(err) => {
17409 let message = format!("Failed to open permalink: {err}");
17410
17411 anyhow::Result::<()>::Err(err).log_err();
17412
17413 if let Some(workspace) = workspace {
17414 workspace
17415 .update(cx, |workspace, cx| {
17416 struct OpenPermalinkToLine;
17417
17418 workspace.show_toast(
17419 Toast::new(
17420 NotificationId::unique::<OpenPermalinkToLine>(),
17421 message,
17422 ),
17423 cx,
17424 )
17425 })
17426 .ok();
17427 }
17428 }
17429 })
17430 .detach();
17431 }
17432
17433 pub fn insert_uuid_v4(
17434 &mut self,
17435 _: &InsertUuidV4,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) {
17439 self.insert_uuid(UuidVersion::V4, window, cx);
17440 }
17441
17442 pub fn insert_uuid_v7(
17443 &mut self,
17444 _: &InsertUuidV7,
17445 window: &mut Window,
17446 cx: &mut Context<Self>,
17447 ) {
17448 self.insert_uuid(UuidVersion::V7, window, cx);
17449 }
17450
17451 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17452 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17453 self.transact(window, cx, |this, window, cx| {
17454 let edits = this
17455 .selections
17456 .all::<Point>(cx)
17457 .into_iter()
17458 .map(|selection| {
17459 let uuid = match version {
17460 UuidVersion::V4 => uuid::Uuid::new_v4(),
17461 UuidVersion::V7 => uuid::Uuid::now_v7(),
17462 };
17463
17464 (selection.range(), uuid.to_string())
17465 });
17466 this.edit(edits, cx);
17467 this.refresh_inline_completion(true, false, window, cx);
17468 });
17469 }
17470
17471 pub fn open_selections_in_multibuffer(
17472 &mut self,
17473 _: &OpenSelectionsInMultibuffer,
17474 window: &mut Window,
17475 cx: &mut Context<Self>,
17476 ) {
17477 let multibuffer = self.buffer.read(cx);
17478
17479 let Some(buffer) = multibuffer.as_singleton() else {
17480 return;
17481 };
17482
17483 let Some(workspace) = self.workspace() else {
17484 return;
17485 };
17486
17487 let locations = self
17488 .selections
17489 .disjoint_anchors()
17490 .iter()
17491 .map(|range| Location {
17492 buffer: buffer.clone(),
17493 range: range.start.text_anchor..range.end.text_anchor,
17494 })
17495 .collect::<Vec<_>>();
17496
17497 let title = multibuffer.title(cx).to_string();
17498
17499 cx.spawn_in(window, async move |_, cx| {
17500 workspace.update_in(cx, |workspace, window, cx| {
17501 Self::open_locations_in_multibuffer(
17502 workspace,
17503 locations,
17504 format!("Selections for '{title}'"),
17505 false,
17506 MultibufferSelectionMode::All,
17507 window,
17508 cx,
17509 );
17510 })
17511 })
17512 .detach();
17513 }
17514
17515 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17516 /// last highlight added will be used.
17517 ///
17518 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17519 pub fn highlight_rows<T: 'static>(
17520 &mut self,
17521 range: Range<Anchor>,
17522 color: Hsla,
17523 options: RowHighlightOptions,
17524 cx: &mut Context<Self>,
17525 ) {
17526 let snapshot = self.buffer().read(cx).snapshot(cx);
17527 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17528 let ix = row_highlights.binary_search_by(|highlight| {
17529 Ordering::Equal
17530 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17531 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17532 });
17533
17534 if let Err(mut ix) = ix {
17535 let index = post_inc(&mut self.highlight_order);
17536
17537 // If this range intersects with the preceding highlight, then merge it with
17538 // the preceding highlight. Otherwise insert a new highlight.
17539 let mut merged = false;
17540 if ix > 0 {
17541 let prev_highlight = &mut row_highlights[ix - 1];
17542 if prev_highlight
17543 .range
17544 .end
17545 .cmp(&range.start, &snapshot)
17546 .is_ge()
17547 {
17548 ix -= 1;
17549 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17550 prev_highlight.range.end = range.end;
17551 }
17552 merged = true;
17553 prev_highlight.index = index;
17554 prev_highlight.color = color;
17555 prev_highlight.options = options;
17556 }
17557 }
17558
17559 if !merged {
17560 row_highlights.insert(
17561 ix,
17562 RowHighlight {
17563 range: range.clone(),
17564 index,
17565 color,
17566 options,
17567 type_id: TypeId::of::<T>(),
17568 },
17569 );
17570 }
17571
17572 // If any of the following highlights intersect with this one, merge them.
17573 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17574 let highlight = &row_highlights[ix];
17575 if next_highlight
17576 .range
17577 .start
17578 .cmp(&highlight.range.end, &snapshot)
17579 .is_le()
17580 {
17581 if next_highlight
17582 .range
17583 .end
17584 .cmp(&highlight.range.end, &snapshot)
17585 .is_gt()
17586 {
17587 row_highlights[ix].range.end = next_highlight.range.end;
17588 }
17589 row_highlights.remove(ix + 1);
17590 } else {
17591 break;
17592 }
17593 }
17594 }
17595 }
17596
17597 /// Remove any highlighted row ranges of the given type that intersect the
17598 /// given ranges.
17599 pub fn remove_highlighted_rows<T: 'static>(
17600 &mut self,
17601 ranges_to_remove: Vec<Range<Anchor>>,
17602 cx: &mut Context<Self>,
17603 ) {
17604 let snapshot = self.buffer().read(cx).snapshot(cx);
17605 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17606 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17607 row_highlights.retain(|highlight| {
17608 while let Some(range_to_remove) = ranges_to_remove.peek() {
17609 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17610 Ordering::Less | Ordering::Equal => {
17611 ranges_to_remove.next();
17612 }
17613 Ordering::Greater => {
17614 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17615 Ordering::Less | Ordering::Equal => {
17616 return false;
17617 }
17618 Ordering::Greater => break,
17619 }
17620 }
17621 }
17622 }
17623
17624 true
17625 })
17626 }
17627
17628 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17629 pub fn clear_row_highlights<T: 'static>(&mut self) {
17630 self.highlighted_rows.remove(&TypeId::of::<T>());
17631 }
17632
17633 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17634 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17635 self.highlighted_rows
17636 .get(&TypeId::of::<T>())
17637 .map_or(&[] as &[_], |vec| vec.as_slice())
17638 .iter()
17639 .map(|highlight| (highlight.range.clone(), highlight.color))
17640 }
17641
17642 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17643 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17644 /// Allows to ignore certain kinds of highlights.
17645 pub fn highlighted_display_rows(
17646 &self,
17647 window: &mut Window,
17648 cx: &mut App,
17649 ) -> BTreeMap<DisplayRow, LineHighlight> {
17650 let snapshot = self.snapshot(window, cx);
17651 let mut used_highlight_orders = HashMap::default();
17652 self.highlighted_rows
17653 .iter()
17654 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17655 .fold(
17656 BTreeMap::<DisplayRow, LineHighlight>::new(),
17657 |mut unique_rows, highlight| {
17658 let start = highlight.range.start.to_display_point(&snapshot);
17659 let end = highlight.range.end.to_display_point(&snapshot);
17660 let start_row = start.row().0;
17661 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17662 && end.column() == 0
17663 {
17664 end.row().0.saturating_sub(1)
17665 } else {
17666 end.row().0
17667 };
17668 for row in start_row..=end_row {
17669 let used_index =
17670 used_highlight_orders.entry(row).or_insert(highlight.index);
17671 if highlight.index >= *used_index {
17672 *used_index = highlight.index;
17673 unique_rows.insert(
17674 DisplayRow(row),
17675 LineHighlight {
17676 include_gutter: highlight.options.include_gutter,
17677 border: None,
17678 background: highlight.color.into(),
17679 type_id: Some(highlight.type_id),
17680 },
17681 );
17682 }
17683 }
17684 unique_rows
17685 },
17686 )
17687 }
17688
17689 pub fn highlighted_display_row_for_autoscroll(
17690 &self,
17691 snapshot: &DisplaySnapshot,
17692 ) -> Option<DisplayRow> {
17693 self.highlighted_rows
17694 .values()
17695 .flat_map(|highlighted_rows| highlighted_rows.iter())
17696 .filter_map(|highlight| {
17697 if highlight.options.autoscroll {
17698 Some(highlight.range.start.to_display_point(snapshot).row())
17699 } else {
17700 None
17701 }
17702 })
17703 .min()
17704 }
17705
17706 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17707 self.highlight_background::<SearchWithinRange>(
17708 ranges,
17709 |colors| colors.editor_document_highlight_read_background,
17710 cx,
17711 )
17712 }
17713
17714 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17715 self.breadcrumb_header = Some(new_header);
17716 }
17717
17718 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17719 self.clear_background_highlights::<SearchWithinRange>(cx);
17720 }
17721
17722 pub fn highlight_background<T: 'static>(
17723 &mut self,
17724 ranges: &[Range<Anchor>],
17725 color_fetcher: fn(&ThemeColors) -> Hsla,
17726 cx: &mut Context<Self>,
17727 ) {
17728 self.background_highlights
17729 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17730 self.scrollbar_marker_state.dirty = true;
17731 cx.notify();
17732 }
17733
17734 pub fn clear_background_highlights<T: 'static>(
17735 &mut self,
17736 cx: &mut Context<Self>,
17737 ) -> Option<BackgroundHighlight> {
17738 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17739 if !text_highlights.1.is_empty() {
17740 self.scrollbar_marker_state.dirty = true;
17741 cx.notify();
17742 }
17743 Some(text_highlights)
17744 }
17745
17746 pub fn highlight_gutter<T: 'static>(
17747 &mut self,
17748 ranges: &[Range<Anchor>],
17749 color_fetcher: fn(&App) -> Hsla,
17750 cx: &mut Context<Self>,
17751 ) {
17752 self.gutter_highlights
17753 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17754 cx.notify();
17755 }
17756
17757 pub fn clear_gutter_highlights<T: 'static>(
17758 &mut self,
17759 cx: &mut Context<Self>,
17760 ) -> Option<GutterHighlight> {
17761 cx.notify();
17762 self.gutter_highlights.remove(&TypeId::of::<T>())
17763 }
17764
17765 #[cfg(feature = "test-support")]
17766 pub fn all_text_background_highlights(
17767 &self,
17768 window: &mut Window,
17769 cx: &mut Context<Self>,
17770 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17771 let snapshot = self.snapshot(window, cx);
17772 let buffer = &snapshot.buffer_snapshot;
17773 let start = buffer.anchor_before(0);
17774 let end = buffer.anchor_after(buffer.len());
17775 let theme = cx.theme().colors();
17776 self.background_highlights_in_range(start..end, &snapshot, theme)
17777 }
17778
17779 #[cfg(feature = "test-support")]
17780 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17781 let snapshot = self.buffer().read(cx).snapshot(cx);
17782
17783 let highlights = self
17784 .background_highlights
17785 .get(&TypeId::of::<items::BufferSearchHighlights>());
17786
17787 if let Some((_color, ranges)) = highlights {
17788 ranges
17789 .iter()
17790 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17791 .collect_vec()
17792 } else {
17793 vec![]
17794 }
17795 }
17796
17797 fn document_highlights_for_position<'a>(
17798 &'a self,
17799 position: Anchor,
17800 buffer: &'a MultiBufferSnapshot,
17801 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17802 let read_highlights = self
17803 .background_highlights
17804 .get(&TypeId::of::<DocumentHighlightRead>())
17805 .map(|h| &h.1);
17806 let write_highlights = self
17807 .background_highlights
17808 .get(&TypeId::of::<DocumentHighlightWrite>())
17809 .map(|h| &h.1);
17810 let left_position = position.bias_left(buffer);
17811 let right_position = position.bias_right(buffer);
17812 read_highlights
17813 .into_iter()
17814 .chain(write_highlights)
17815 .flat_map(move |ranges| {
17816 let start_ix = match ranges.binary_search_by(|probe| {
17817 let cmp = probe.end.cmp(&left_position, buffer);
17818 if cmp.is_ge() {
17819 Ordering::Greater
17820 } else {
17821 Ordering::Less
17822 }
17823 }) {
17824 Ok(i) | Err(i) => i,
17825 };
17826
17827 ranges[start_ix..]
17828 .iter()
17829 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17830 })
17831 }
17832
17833 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17834 self.background_highlights
17835 .get(&TypeId::of::<T>())
17836 .map_or(false, |(_, highlights)| !highlights.is_empty())
17837 }
17838
17839 pub fn background_highlights_in_range(
17840 &self,
17841 search_range: Range<Anchor>,
17842 display_snapshot: &DisplaySnapshot,
17843 theme: &ThemeColors,
17844 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17845 let mut results = Vec::new();
17846 for (color_fetcher, ranges) in self.background_highlights.values() {
17847 let color = color_fetcher(theme);
17848 let start_ix = match ranges.binary_search_by(|probe| {
17849 let cmp = probe
17850 .end
17851 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17852 if cmp.is_gt() {
17853 Ordering::Greater
17854 } else {
17855 Ordering::Less
17856 }
17857 }) {
17858 Ok(i) | Err(i) => i,
17859 };
17860 for range in &ranges[start_ix..] {
17861 if range
17862 .start
17863 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17864 .is_ge()
17865 {
17866 break;
17867 }
17868
17869 let start = range.start.to_display_point(display_snapshot);
17870 let end = range.end.to_display_point(display_snapshot);
17871 results.push((start..end, color))
17872 }
17873 }
17874 results
17875 }
17876
17877 pub fn background_highlight_row_ranges<T: 'static>(
17878 &self,
17879 search_range: Range<Anchor>,
17880 display_snapshot: &DisplaySnapshot,
17881 count: usize,
17882 ) -> Vec<RangeInclusive<DisplayPoint>> {
17883 let mut results = Vec::new();
17884 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17885 return vec![];
17886 };
17887
17888 let start_ix = match ranges.binary_search_by(|probe| {
17889 let cmp = probe
17890 .end
17891 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17892 if cmp.is_gt() {
17893 Ordering::Greater
17894 } else {
17895 Ordering::Less
17896 }
17897 }) {
17898 Ok(i) | Err(i) => i,
17899 };
17900 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17901 if let (Some(start_display), Some(end_display)) = (start, end) {
17902 results.push(
17903 start_display.to_display_point(display_snapshot)
17904 ..=end_display.to_display_point(display_snapshot),
17905 );
17906 }
17907 };
17908 let mut start_row: Option<Point> = None;
17909 let mut end_row: Option<Point> = None;
17910 if ranges.len() > count {
17911 return Vec::new();
17912 }
17913 for range in &ranges[start_ix..] {
17914 if range
17915 .start
17916 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17917 .is_ge()
17918 {
17919 break;
17920 }
17921 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17922 if let Some(current_row) = &end_row {
17923 if end.row == current_row.row {
17924 continue;
17925 }
17926 }
17927 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17928 if start_row.is_none() {
17929 assert_eq!(end_row, None);
17930 start_row = Some(start);
17931 end_row = Some(end);
17932 continue;
17933 }
17934 if let Some(current_end) = end_row.as_mut() {
17935 if start.row > current_end.row + 1 {
17936 push_region(start_row, end_row);
17937 start_row = Some(start);
17938 end_row = Some(end);
17939 } else {
17940 // Merge two hunks.
17941 *current_end = end;
17942 }
17943 } else {
17944 unreachable!();
17945 }
17946 }
17947 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17948 push_region(start_row, end_row);
17949 results
17950 }
17951
17952 pub fn gutter_highlights_in_range(
17953 &self,
17954 search_range: Range<Anchor>,
17955 display_snapshot: &DisplaySnapshot,
17956 cx: &App,
17957 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17958 let mut results = Vec::new();
17959 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17960 let color = color_fetcher(cx);
17961 let start_ix = match ranges.binary_search_by(|probe| {
17962 let cmp = probe
17963 .end
17964 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17965 if cmp.is_gt() {
17966 Ordering::Greater
17967 } else {
17968 Ordering::Less
17969 }
17970 }) {
17971 Ok(i) | Err(i) => i,
17972 };
17973 for range in &ranges[start_ix..] {
17974 if range
17975 .start
17976 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17977 .is_ge()
17978 {
17979 break;
17980 }
17981
17982 let start = range.start.to_display_point(display_snapshot);
17983 let end = range.end.to_display_point(display_snapshot);
17984 results.push((start..end, color))
17985 }
17986 }
17987 results
17988 }
17989
17990 /// Get the text ranges corresponding to the redaction query
17991 pub fn redacted_ranges(
17992 &self,
17993 search_range: Range<Anchor>,
17994 display_snapshot: &DisplaySnapshot,
17995 cx: &App,
17996 ) -> Vec<Range<DisplayPoint>> {
17997 display_snapshot
17998 .buffer_snapshot
17999 .redacted_ranges(search_range, |file| {
18000 if let Some(file) = file {
18001 file.is_private()
18002 && EditorSettings::get(
18003 Some(SettingsLocation {
18004 worktree_id: file.worktree_id(cx),
18005 path: file.path().as_ref(),
18006 }),
18007 cx,
18008 )
18009 .redact_private_values
18010 } else {
18011 false
18012 }
18013 })
18014 .map(|range| {
18015 range.start.to_display_point(display_snapshot)
18016 ..range.end.to_display_point(display_snapshot)
18017 })
18018 .collect()
18019 }
18020
18021 pub fn highlight_text<T: 'static>(
18022 &mut self,
18023 ranges: Vec<Range<Anchor>>,
18024 style: HighlightStyle,
18025 cx: &mut Context<Self>,
18026 ) {
18027 self.display_map.update(cx, |map, _| {
18028 map.highlight_text(TypeId::of::<T>(), ranges, style)
18029 });
18030 cx.notify();
18031 }
18032
18033 pub(crate) fn highlight_inlays<T: 'static>(
18034 &mut self,
18035 highlights: Vec<InlayHighlight>,
18036 style: HighlightStyle,
18037 cx: &mut Context<Self>,
18038 ) {
18039 self.display_map.update(cx, |map, _| {
18040 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18041 });
18042 cx.notify();
18043 }
18044
18045 pub fn text_highlights<'a, T: 'static>(
18046 &'a self,
18047 cx: &'a App,
18048 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18049 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18050 }
18051
18052 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18053 let cleared = self
18054 .display_map
18055 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18056 if cleared {
18057 cx.notify();
18058 }
18059 }
18060
18061 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18062 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18063 && self.focus_handle.is_focused(window)
18064 }
18065
18066 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18067 self.show_cursor_when_unfocused = is_enabled;
18068 cx.notify();
18069 }
18070
18071 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18072 cx.notify();
18073 }
18074
18075 fn on_debug_session_event(
18076 &mut self,
18077 _session: Entity<Session>,
18078 event: &SessionEvent,
18079 cx: &mut Context<Self>,
18080 ) {
18081 match event {
18082 SessionEvent::InvalidateInlineValue => {
18083 self.refresh_inline_values(cx);
18084 }
18085 _ => {}
18086 }
18087 }
18088
18089 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18090 let Some(project) = self.project.clone() else {
18091 return;
18092 };
18093
18094 if !self.inline_value_cache.enabled {
18095 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18096 self.splice_inlays(&inlays, Vec::new(), cx);
18097 return;
18098 }
18099
18100 let current_execution_position = self
18101 .highlighted_rows
18102 .get(&TypeId::of::<ActiveDebugLine>())
18103 .and_then(|lines| lines.last().map(|line| line.range.start));
18104
18105 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18106 let inline_values = editor
18107 .update(cx, |editor, cx| {
18108 let Some(current_execution_position) = current_execution_position else {
18109 return Some(Task::ready(Ok(Vec::new())));
18110 };
18111
18112 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18113 let snapshot = buffer.snapshot(cx);
18114
18115 let excerpt = snapshot.excerpt_containing(
18116 current_execution_position..current_execution_position,
18117 )?;
18118
18119 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18120 })?;
18121
18122 let range =
18123 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18124
18125 project.inline_values(buffer, range, cx)
18126 })
18127 .ok()
18128 .flatten()?
18129 .await
18130 .context("refreshing debugger inlays")
18131 .log_err()?;
18132
18133 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18134
18135 for (buffer_id, inline_value) in inline_values
18136 .into_iter()
18137 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18138 {
18139 buffer_inline_values
18140 .entry(buffer_id)
18141 .or_default()
18142 .push(inline_value);
18143 }
18144
18145 editor
18146 .update(cx, |editor, cx| {
18147 let snapshot = editor.buffer.read(cx).snapshot(cx);
18148 let mut new_inlays = Vec::default();
18149
18150 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18151 let buffer_id = buffer_snapshot.remote_id();
18152 buffer_inline_values
18153 .get(&buffer_id)
18154 .into_iter()
18155 .flatten()
18156 .for_each(|hint| {
18157 let inlay = Inlay::debugger_hint(
18158 post_inc(&mut editor.next_inlay_id),
18159 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18160 hint.text(),
18161 );
18162
18163 new_inlays.push(inlay);
18164 });
18165 }
18166
18167 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18168 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18169
18170 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18171 })
18172 .ok()?;
18173 Some(())
18174 });
18175 }
18176
18177 fn on_buffer_event(
18178 &mut self,
18179 multibuffer: &Entity<MultiBuffer>,
18180 event: &multi_buffer::Event,
18181 window: &mut Window,
18182 cx: &mut Context<Self>,
18183 ) {
18184 match event {
18185 multi_buffer::Event::Edited {
18186 singleton_buffer_edited,
18187 edited_buffer: buffer_edited,
18188 } => {
18189 self.scrollbar_marker_state.dirty = true;
18190 self.active_indent_guides_state.dirty = true;
18191 self.refresh_active_diagnostics(cx);
18192 self.refresh_code_actions(window, cx);
18193 self.refresh_selected_text_highlights(true, window, cx);
18194 refresh_matching_bracket_highlights(self, window, cx);
18195 if self.has_active_inline_completion() {
18196 self.update_visible_inline_completion(window, cx);
18197 }
18198 if let Some(buffer) = buffer_edited {
18199 let buffer_id = buffer.read(cx).remote_id();
18200 if !self.registered_buffers.contains_key(&buffer_id) {
18201 if let Some(project) = self.project.as_ref() {
18202 project.update(cx, |project, cx| {
18203 self.registered_buffers.insert(
18204 buffer_id,
18205 project.register_buffer_with_language_servers(&buffer, cx),
18206 );
18207 })
18208 }
18209 }
18210 }
18211 cx.emit(EditorEvent::BufferEdited);
18212 cx.emit(SearchEvent::MatchesInvalidated);
18213 if *singleton_buffer_edited {
18214 if let Some(project) = &self.project {
18215 #[allow(clippy::mutable_key_type)]
18216 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18217 multibuffer
18218 .all_buffers()
18219 .into_iter()
18220 .filter_map(|buffer| {
18221 buffer.update(cx, |buffer, cx| {
18222 let language = buffer.language()?;
18223 let should_discard = project.update(cx, |project, cx| {
18224 project.is_local()
18225 && !project.has_language_servers_for(buffer, cx)
18226 });
18227 should_discard.not().then_some(language.clone())
18228 })
18229 })
18230 .collect::<HashSet<_>>()
18231 });
18232 if !languages_affected.is_empty() {
18233 self.refresh_inlay_hints(
18234 InlayHintRefreshReason::BufferEdited(languages_affected),
18235 cx,
18236 );
18237 }
18238 }
18239 }
18240
18241 let Some(project) = &self.project else { return };
18242 let (telemetry, is_via_ssh) = {
18243 let project = project.read(cx);
18244 let telemetry = project.client().telemetry().clone();
18245 let is_via_ssh = project.is_via_ssh();
18246 (telemetry, is_via_ssh)
18247 };
18248 refresh_linked_ranges(self, window, cx);
18249 telemetry.log_edit_event("editor", is_via_ssh);
18250 }
18251 multi_buffer::Event::ExcerptsAdded {
18252 buffer,
18253 predecessor,
18254 excerpts,
18255 } => {
18256 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18257 let buffer_id = buffer.read(cx).remote_id();
18258 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18259 if let Some(project) = &self.project {
18260 update_uncommitted_diff_for_buffer(
18261 cx.entity(),
18262 project,
18263 [buffer.clone()],
18264 self.buffer.clone(),
18265 cx,
18266 )
18267 .detach();
18268 }
18269 }
18270 cx.emit(EditorEvent::ExcerptsAdded {
18271 buffer: buffer.clone(),
18272 predecessor: *predecessor,
18273 excerpts: excerpts.clone(),
18274 });
18275 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18276 }
18277 multi_buffer::Event::ExcerptsRemoved {
18278 ids,
18279 removed_buffer_ids,
18280 } => {
18281 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18282 let buffer = self.buffer.read(cx);
18283 self.registered_buffers
18284 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18285 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18286 cx.emit(EditorEvent::ExcerptsRemoved {
18287 ids: ids.clone(),
18288 removed_buffer_ids: removed_buffer_ids.clone(),
18289 })
18290 }
18291 multi_buffer::Event::ExcerptsEdited {
18292 excerpt_ids,
18293 buffer_ids,
18294 } => {
18295 self.display_map.update(cx, |map, cx| {
18296 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18297 });
18298 cx.emit(EditorEvent::ExcerptsEdited {
18299 ids: excerpt_ids.clone(),
18300 })
18301 }
18302 multi_buffer::Event::ExcerptsExpanded { ids } => {
18303 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18304 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18305 }
18306 multi_buffer::Event::Reparsed(buffer_id) => {
18307 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18308 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18309
18310 cx.emit(EditorEvent::Reparsed(*buffer_id));
18311 }
18312 multi_buffer::Event::DiffHunksToggled => {
18313 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18314 }
18315 multi_buffer::Event::LanguageChanged(buffer_id) => {
18316 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18317 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18318 cx.emit(EditorEvent::Reparsed(*buffer_id));
18319 cx.notify();
18320 }
18321 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18322 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18323 multi_buffer::Event::FileHandleChanged
18324 | multi_buffer::Event::Reloaded
18325 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18326 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18327 multi_buffer::Event::DiagnosticsUpdated => {
18328 self.refresh_active_diagnostics(cx);
18329 self.refresh_inline_diagnostics(true, window, cx);
18330 self.scrollbar_marker_state.dirty = true;
18331 cx.notify();
18332 }
18333 _ => {}
18334 };
18335 }
18336
18337 pub fn start_temporary_diff_override(&mut self) {
18338 self.load_diff_task.take();
18339 self.temporary_diff_override = true;
18340 }
18341
18342 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18343 self.temporary_diff_override = false;
18344 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18345 self.buffer.update(cx, |buffer, cx| {
18346 buffer.set_all_diff_hunks_collapsed(cx);
18347 });
18348
18349 if let Some(project) = self.project.clone() {
18350 self.load_diff_task = Some(
18351 update_uncommitted_diff_for_buffer(
18352 cx.entity(),
18353 &project,
18354 self.buffer.read(cx).all_buffers(),
18355 self.buffer.clone(),
18356 cx,
18357 )
18358 .shared(),
18359 );
18360 }
18361 }
18362
18363 fn on_display_map_changed(
18364 &mut self,
18365 _: Entity<DisplayMap>,
18366 _: &mut Window,
18367 cx: &mut Context<Self>,
18368 ) {
18369 cx.notify();
18370 }
18371
18372 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18373 let new_severity = if self.diagnostics_enabled() {
18374 EditorSettings::get_global(cx)
18375 .diagnostics_max_severity
18376 .unwrap_or(DiagnosticSeverity::Hint)
18377 } else {
18378 DiagnosticSeverity::Off
18379 };
18380 self.set_max_diagnostics_severity(new_severity, cx);
18381 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18382 self.update_edit_prediction_settings(cx);
18383 self.refresh_inline_completion(true, false, window, cx);
18384 self.refresh_inlay_hints(
18385 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18386 self.selections.newest_anchor().head(),
18387 &self.buffer.read(cx).snapshot(cx),
18388 cx,
18389 )),
18390 cx,
18391 );
18392
18393 let old_cursor_shape = self.cursor_shape;
18394
18395 {
18396 let editor_settings = EditorSettings::get_global(cx);
18397 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18398 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18399 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18400 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18401 }
18402
18403 if old_cursor_shape != self.cursor_shape {
18404 cx.emit(EditorEvent::CursorShapeChanged);
18405 }
18406
18407 let project_settings = ProjectSettings::get_global(cx);
18408 self.serialize_dirty_buffers =
18409 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18410
18411 if self.mode.is_full() {
18412 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18413 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18414 if self.show_inline_diagnostics != show_inline_diagnostics {
18415 self.show_inline_diagnostics = show_inline_diagnostics;
18416 self.refresh_inline_diagnostics(false, window, cx);
18417 }
18418
18419 if self.git_blame_inline_enabled != inline_blame_enabled {
18420 self.toggle_git_blame_inline_internal(false, window, cx);
18421 }
18422
18423 let minimap_settings = EditorSettings::get_global(cx).minimap;
18424 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18425 self.set_minimap_visibility(
18426 self.minimap_visibility.toggle_visibility(),
18427 window,
18428 cx,
18429 );
18430 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18431 minimap_entity.update(cx, |minimap_editor, cx| {
18432 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18433 })
18434 }
18435 }
18436
18437 cx.notify();
18438 }
18439
18440 pub fn set_searchable(&mut self, searchable: bool) {
18441 self.searchable = searchable;
18442 }
18443
18444 pub fn searchable(&self) -> bool {
18445 self.searchable
18446 }
18447
18448 fn open_proposed_changes_editor(
18449 &mut self,
18450 _: &OpenProposedChangesEditor,
18451 window: &mut Window,
18452 cx: &mut Context<Self>,
18453 ) {
18454 let Some(workspace) = self.workspace() else {
18455 cx.propagate();
18456 return;
18457 };
18458
18459 let selections = self.selections.all::<usize>(cx);
18460 let multi_buffer = self.buffer.read(cx);
18461 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18462 let mut new_selections_by_buffer = HashMap::default();
18463 for selection in selections {
18464 for (buffer, range, _) in
18465 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18466 {
18467 let mut range = range.to_point(buffer);
18468 range.start.column = 0;
18469 range.end.column = buffer.line_len(range.end.row);
18470 new_selections_by_buffer
18471 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18472 .or_insert(Vec::new())
18473 .push(range)
18474 }
18475 }
18476
18477 let proposed_changes_buffers = new_selections_by_buffer
18478 .into_iter()
18479 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18480 .collect::<Vec<_>>();
18481 let proposed_changes_editor = cx.new(|cx| {
18482 ProposedChangesEditor::new(
18483 "Proposed changes",
18484 proposed_changes_buffers,
18485 self.project.clone(),
18486 window,
18487 cx,
18488 )
18489 });
18490
18491 window.defer(cx, move |window, cx| {
18492 workspace.update(cx, |workspace, cx| {
18493 workspace.active_pane().update(cx, |pane, cx| {
18494 pane.add_item(
18495 Box::new(proposed_changes_editor),
18496 true,
18497 true,
18498 None,
18499 window,
18500 cx,
18501 );
18502 });
18503 });
18504 });
18505 }
18506
18507 pub fn open_excerpts_in_split(
18508 &mut self,
18509 _: &OpenExcerptsSplit,
18510 window: &mut Window,
18511 cx: &mut Context<Self>,
18512 ) {
18513 self.open_excerpts_common(None, true, window, cx)
18514 }
18515
18516 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18517 self.open_excerpts_common(None, false, window, cx)
18518 }
18519
18520 fn open_excerpts_common(
18521 &mut self,
18522 jump_data: Option<JumpData>,
18523 split: bool,
18524 window: &mut Window,
18525 cx: &mut Context<Self>,
18526 ) {
18527 let Some(workspace) = self.workspace() else {
18528 cx.propagate();
18529 return;
18530 };
18531
18532 if self.buffer.read(cx).is_singleton() {
18533 cx.propagate();
18534 return;
18535 }
18536
18537 let mut new_selections_by_buffer = HashMap::default();
18538 match &jump_data {
18539 Some(JumpData::MultiBufferPoint {
18540 excerpt_id,
18541 position,
18542 anchor,
18543 line_offset_from_top,
18544 }) => {
18545 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18546 if let Some(buffer) = multi_buffer_snapshot
18547 .buffer_id_for_excerpt(*excerpt_id)
18548 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18549 {
18550 let buffer_snapshot = buffer.read(cx).snapshot();
18551 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18552 language::ToPoint::to_point(anchor, &buffer_snapshot)
18553 } else {
18554 buffer_snapshot.clip_point(*position, Bias::Left)
18555 };
18556 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18557 new_selections_by_buffer.insert(
18558 buffer,
18559 (
18560 vec![jump_to_offset..jump_to_offset],
18561 Some(*line_offset_from_top),
18562 ),
18563 );
18564 }
18565 }
18566 Some(JumpData::MultiBufferRow {
18567 row,
18568 line_offset_from_top,
18569 }) => {
18570 let point = MultiBufferPoint::new(row.0, 0);
18571 if let Some((buffer, buffer_point, _)) =
18572 self.buffer.read(cx).point_to_buffer_point(point, cx)
18573 {
18574 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18575 new_selections_by_buffer
18576 .entry(buffer)
18577 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18578 .0
18579 .push(buffer_offset..buffer_offset)
18580 }
18581 }
18582 None => {
18583 let selections = self.selections.all::<usize>(cx);
18584 let multi_buffer = self.buffer.read(cx);
18585 for selection in selections {
18586 for (snapshot, range, _, anchor) in multi_buffer
18587 .snapshot(cx)
18588 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18589 {
18590 if let Some(anchor) = anchor {
18591 // selection is in a deleted hunk
18592 let Some(buffer_id) = anchor.buffer_id else {
18593 continue;
18594 };
18595 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18596 continue;
18597 };
18598 let offset = text::ToOffset::to_offset(
18599 &anchor.text_anchor,
18600 &buffer_handle.read(cx).snapshot(),
18601 );
18602 let range = offset..offset;
18603 new_selections_by_buffer
18604 .entry(buffer_handle)
18605 .or_insert((Vec::new(), None))
18606 .0
18607 .push(range)
18608 } else {
18609 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18610 else {
18611 continue;
18612 };
18613 new_selections_by_buffer
18614 .entry(buffer_handle)
18615 .or_insert((Vec::new(), None))
18616 .0
18617 .push(range)
18618 }
18619 }
18620 }
18621 }
18622 }
18623
18624 new_selections_by_buffer
18625 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18626
18627 if new_selections_by_buffer.is_empty() {
18628 return;
18629 }
18630
18631 // We defer the pane interaction because we ourselves are a workspace item
18632 // and activating a new item causes the pane to call a method on us reentrantly,
18633 // which panics if we're on the stack.
18634 window.defer(cx, move |window, cx| {
18635 workspace.update(cx, |workspace, cx| {
18636 let pane = if split {
18637 workspace.adjacent_pane(window, cx)
18638 } else {
18639 workspace.active_pane().clone()
18640 };
18641
18642 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18643 let editor = buffer
18644 .read(cx)
18645 .file()
18646 .is_none()
18647 .then(|| {
18648 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18649 // so `workspace.open_project_item` will never find them, always opening a new editor.
18650 // Instead, we try to activate the existing editor in the pane first.
18651 let (editor, pane_item_index) =
18652 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18653 let editor = item.downcast::<Editor>()?;
18654 let singleton_buffer =
18655 editor.read(cx).buffer().read(cx).as_singleton()?;
18656 if singleton_buffer == buffer {
18657 Some((editor, i))
18658 } else {
18659 None
18660 }
18661 })?;
18662 pane.update(cx, |pane, cx| {
18663 pane.activate_item(pane_item_index, true, true, window, cx)
18664 });
18665 Some(editor)
18666 })
18667 .flatten()
18668 .unwrap_or_else(|| {
18669 workspace.open_project_item::<Self>(
18670 pane.clone(),
18671 buffer,
18672 true,
18673 true,
18674 window,
18675 cx,
18676 )
18677 });
18678
18679 editor.update(cx, |editor, cx| {
18680 let autoscroll = match scroll_offset {
18681 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18682 None => Autoscroll::newest(),
18683 };
18684 let nav_history = editor.nav_history.take();
18685 editor.change_selections(Some(autoscroll), window, cx, |s| {
18686 s.select_ranges(ranges);
18687 });
18688 editor.nav_history = nav_history;
18689 });
18690 }
18691 })
18692 });
18693 }
18694
18695 // For now, don't allow opening excerpts in buffers that aren't backed by
18696 // regular project files.
18697 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18698 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18699 }
18700
18701 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18702 let snapshot = self.buffer.read(cx).read(cx);
18703 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18704 Some(
18705 ranges
18706 .iter()
18707 .map(move |range| {
18708 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18709 })
18710 .collect(),
18711 )
18712 }
18713
18714 fn selection_replacement_ranges(
18715 &self,
18716 range: Range<OffsetUtf16>,
18717 cx: &mut App,
18718 ) -> Vec<Range<OffsetUtf16>> {
18719 let selections = self.selections.all::<OffsetUtf16>(cx);
18720 let newest_selection = selections
18721 .iter()
18722 .max_by_key(|selection| selection.id)
18723 .unwrap();
18724 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18725 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18726 let snapshot = self.buffer.read(cx).read(cx);
18727 selections
18728 .into_iter()
18729 .map(|mut selection| {
18730 selection.start.0 =
18731 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18732 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18733 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18734 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18735 })
18736 .collect()
18737 }
18738
18739 fn report_editor_event(
18740 &self,
18741 event_type: &'static str,
18742 file_extension: Option<String>,
18743 cx: &App,
18744 ) {
18745 if cfg!(any(test, feature = "test-support")) {
18746 return;
18747 }
18748
18749 let Some(project) = &self.project else { return };
18750
18751 // If None, we are in a file without an extension
18752 let file = self
18753 .buffer
18754 .read(cx)
18755 .as_singleton()
18756 .and_then(|b| b.read(cx).file());
18757 let file_extension = file_extension.or(file
18758 .as_ref()
18759 .and_then(|file| Path::new(file.file_name(cx)).extension())
18760 .and_then(|e| e.to_str())
18761 .map(|a| a.to_string()));
18762
18763 let vim_mode = vim_enabled(cx);
18764
18765 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18766 let copilot_enabled = edit_predictions_provider
18767 == language::language_settings::EditPredictionProvider::Copilot;
18768 let copilot_enabled_for_language = self
18769 .buffer
18770 .read(cx)
18771 .language_settings(cx)
18772 .show_edit_predictions;
18773
18774 let project = project.read(cx);
18775 telemetry::event!(
18776 event_type,
18777 file_extension,
18778 vim_mode,
18779 copilot_enabled,
18780 copilot_enabled_for_language,
18781 edit_predictions_provider,
18782 is_via_ssh = project.is_via_ssh(),
18783 );
18784 }
18785
18786 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18787 /// with each line being an array of {text, highlight} objects.
18788 fn copy_highlight_json(
18789 &mut self,
18790 _: &CopyHighlightJson,
18791 window: &mut Window,
18792 cx: &mut Context<Self>,
18793 ) {
18794 #[derive(Serialize)]
18795 struct Chunk<'a> {
18796 text: String,
18797 highlight: Option<&'a str>,
18798 }
18799
18800 let snapshot = self.buffer.read(cx).snapshot(cx);
18801 let range = self
18802 .selected_text_range(false, window, cx)
18803 .and_then(|selection| {
18804 if selection.range.is_empty() {
18805 None
18806 } else {
18807 Some(selection.range)
18808 }
18809 })
18810 .unwrap_or_else(|| 0..snapshot.len());
18811
18812 let chunks = snapshot.chunks(range, true);
18813 let mut lines = Vec::new();
18814 let mut line: VecDeque<Chunk> = VecDeque::new();
18815
18816 let Some(style) = self.style.as_ref() else {
18817 return;
18818 };
18819
18820 for chunk in chunks {
18821 let highlight = chunk
18822 .syntax_highlight_id
18823 .and_then(|id| id.name(&style.syntax));
18824 let mut chunk_lines = chunk.text.split('\n').peekable();
18825 while let Some(text) = chunk_lines.next() {
18826 let mut merged_with_last_token = false;
18827 if let Some(last_token) = line.back_mut() {
18828 if last_token.highlight == highlight {
18829 last_token.text.push_str(text);
18830 merged_with_last_token = true;
18831 }
18832 }
18833
18834 if !merged_with_last_token {
18835 line.push_back(Chunk {
18836 text: text.into(),
18837 highlight,
18838 });
18839 }
18840
18841 if chunk_lines.peek().is_some() {
18842 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18843 line.pop_front();
18844 }
18845 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18846 line.pop_back();
18847 }
18848
18849 lines.push(mem::take(&mut line));
18850 }
18851 }
18852 }
18853
18854 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18855 return;
18856 };
18857 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18858 }
18859
18860 pub fn open_context_menu(
18861 &mut self,
18862 _: &OpenContextMenu,
18863 window: &mut Window,
18864 cx: &mut Context<Self>,
18865 ) {
18866 self.request_autoscroll(Autoscroll::newest(), cx);
18867 let position = self.selections.newest_display(cx).start;
18868 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18869 }
18870
18871 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18872 &self.inlay_hint_cache
18873 }
18874
18875 pub fn replay_insert_event(
18876 &mut self,
18877 text: &str,
18878 relative_utf16_range: Option<Range<isize>>,
18879 window: &mut Window,
18880 cx: &mut Context<Self>,
18881 ) {
18882 if !self.input_enabled {
18883 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18884 return;
18885 }
18886 if let Some(relative_utf16_range) = relative_utf16_range {
18887 let selections = self.selections.all::<OffsetUtf16>(cx);
18888 self.change_selections(None, window, cx, |s| {
18889 let new_ranges = selections.into_iter().map(|range| {
18890 let start = OffsetUtf16(
18891 range
18892 .head()
18893 .0
18894 .saturating_add_signed(relative_utf16_range.start),
18895 );
18896 let end = OffsetUtf16(
18897 range
18898 .head()
18899 .0
18900 .saturating_add_signed(relative_utf16_range.end),
18901 );
18902 start..end
18903 });
18904 s.select_ranges(new_ranges);
18905 });
18906 }
18907
18908 self.handle_input(text, window, cx);
18909 }
18910
18911 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18912 let Some(provider) = self.semantics_provider.as_ref() else {
18913 return false;
18914 };
18915
18916 let mut supports = false;
18917 self.buffer().update(cx, |this, cx| {
18918 this.for_each_buffer(|buffer| {
18919 supports |= provider.supports_inlay_hints(buffer, cx);
18920 });
18921 });
18922
18923 supports
18924 }
18925
18926 pub fn is_focused(&self, window: &Window) -> bool {
18927 self.focus_handle.is_focused(window)
18928 }
18929
18930 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18931 cx.emit(EditorEvent::Focused);
18932
18933 if let Some(descendant) = self
18934 .last_focused_descendant
18935 .take()
18936 .and_then(|descendant| descendant.upgrade())
18937 {
18938 window.focus(&descendant);
18939 } else {
18940 if let Some(blame) = self.blame.as_ref() {
18941 blame.update(cx, GitBlame::focus)
18942 }
18943
18944 self.blink_manager.update(cx, BlinkManager::enable);
18945 self.show_cursor_names(window, cx);
18946 self.buffer.update(cx, |buffer, cx| {
18947 buffer.finalize_last_transaction(cx);
18948 if self.leader_id.is_none() {
18949 buffer.set_active_selections(
18950 &self.selections.disjoint_anchors(),
18951 self.selections.line_mode,
18952 self.cursor_shape,
18953 cx,
18954 );
18955 }
18956 });
18957 }
18958 }
18959
18960 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18961 cx.emit(EditorEvent::FocusedIn)
18962 }
18963
18964 fn handle_focus_out(
18965 &mut self,
18966 event: FocusOutEvent,
18967 _window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 if event.blurred != self.focus_handle {
18971 self.last_focused_descendant = Some(event.blurred);
18972 }
18973 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18974 }
18975
18976 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18977 self.blink_manager.update(cx, BlinkManager::disable);
18978 self.buffer
18979 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18980
18981 if let Some(blame) = self.blame.as_ref() {
18982 blame.update(cx, GitBlame::blur)
18983 }
18984 if !self.hover_state.focused(window, cx) {
18985 hide_hover(self, cx);
18986 }
18987 if !self
18988 .context_menu
18989 .borrow()
18990 .as_ref()
18991 .is_some_and(|context_menu| context_menu.focused(window, cx))
18992 {
18993 self.hide_context_menu(window, cx);
18994 }
18995 self.discard_inline_completion(false, cx);
18996 cx.emit(EditorEvent::Blurred);
18997 cx.notify();
18998 }
18999
19000 pub fn register_action<A: Action>(
19001 &mut self,
19002 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19003 ) -> Subscription {
19004 let id = self.next_editor_action_id.post_inc();
19005 let listener = Arc::new(listener);
19006 self.editor_actions.borrow_mut().insert(
19007 id,
19008 Box::new(move |window, _| {
19009 let listener = listener.clone();
19010 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19011 let action = action.downcast_ref().unwrap();
19012 if phase == DispatchPhase::Bubble {
19013 listener(action, window, cx)
19014 }
19015 })
19016 }),
19017 );
19018
19019 let editor_actions = self.editor_actions.clone();
19020 Subscription::new(move || {
19021 editor_actions.borrow_mut().remove(&id);
19022 })
19023 }
19024
19025 pub fn file_header_size(&self) -> u32 {
19026 FILE_HEADER_HEIGHT
19027 }
19028
19029 pub fn restore(
19030 &mut self,
19031 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19032 window: &mut Window,
19033 cx: &mut Context<Self>,
19034 ) {
19035 let workspace = self.workspace();
19036 let project = self.project.as_ref();
19037 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19038 let mut tasks = Vec::new();
19039 for (buffer_id, changes) in revert_changes {
19040 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19041 buffer.update(cx, |buffer, cx| {
19042 buffer.edit(
19043 changes
19044 .into_iter()
19045 .map(|(range, text)| (range, text.to_string())),
19046 None,
19047 cx,
19048 );
19049 });
19050
19051 if let Some(project) =
19052 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19053 {
19054 project.update(cx, |project, cx| {
19055 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19056 })
19057 }
19058 }
19059 }
19060 tasks
19061 });
19062 cx.spawn_in(window, async move |_, cx| {
19063 for (buffer, task) in save_tasks {
19064 let result = task.await;
19065 if result.is_err() {
19066 let Some(path) = buffer
19067 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19068 .ok()
19069 else {
19070 continue;
19071 };
19072 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19073 let Some(task) = cx
19074 .update_window_entity(&workspace, |workspace, window, cx| {
19075 workspace
19076 .open_path_preview(path, None, false, false, false, window, cx)
19077 })
19078 .ok()
19079 else {
19080 continue;
19081 };
19082 task.await.log_err();
19083 }
19084 }
19085 }
19086 })
19087 .detach();
19088 self.change_selections(None, window, cx, |selections| selections.refresh());
19089 }
19090
19091 pub fn to_pixel_point(
19092 &self,
19093 source: multi_buffer::Anchor,
19094 editor_snapshot: &EditorSnapshot,
19095 window: &mut Window,
19096 ) -> Option<gpui::Point<Pixels>> {
19097 let source_point = source.to_display_point(editor_snapshot);
19098 self.display_to_pixel_point(source_point, editor_snapshot, window)
19099 }
19100
19101 pub fn display_to_pixel_point(
19102 &self,
19103 source: DisplayPoint,
19104 editor_snapshot: &EditorSnapshot,
19105 window: &mut Window,
19106 ) -> Option<gpui::Point<Pixels>> {
19107 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19108 let text_layout_details = self.text_layout_details(window);
19109 let scroll_top = text_layout_details
19110 .scroll_anchor
19111 .scroll_position(editor_snapshot)
19112 .y;
19113
19114 if source.row().as_f32() < scroll_top.floor() {
19115 return None;
19116 }
19117 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19118 let source_y = line_height * (source.row().as_f32() - scroll_top);
19119 Some(gpui::Point::new(source_x, source_y))
19120 }
19121
19122 pub fn has_visible_completions_menu(&self) -> bool {
19123 !self.edit_prediction_preview_is_active()
19124 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19125 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19126 })
19127 }
19128
19129 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19130 if self.mode.is_minimap() {
19131 return;
19132 }
19133 self.addons
19134 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19135 }
19136
19137 pub fn unregister_addon<T: Addon>(&mut self) {
19138 self.addons.remove(&std::any::TypeId::of::<T>());
19139 }
19140
19141 pub fn addon<T: Addon>(&self) -> Option<&T> {
19142 let type_id = std::any::TypeId::of::<T>();
19143 self.addons
19144 .get(&type_id)
19145 .and_then(|item| item.to_any().downcast_ref::<T>())
19146 }
19147
19148 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19149 let type_id = std::any::TypeId::of::<T>();
19150 self.addons
19151 .get_mut(&type_id)
19152 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19153 }
19154
19155 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19156 let text_layout_details = self.text_layout_details(window);
19157 let style = &text_layout_details.editor_style;
19158 let font_id = window.text_system().resolve_font(&style.text.font());
19159 let font_size = style.text.font_size.to_pixels(window.rem_size());
19160 let line_height = style.text.line_height_in_pixels(window.rem_size());
19161 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19162
19163 gpui::Size::new(em_width, line_height)
19164 }
19165
19166 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19167 self.load_diff_task.clone()
19168 }
19169
19170 fn read_metadata_from_db(
19171 &mut self,
19172 item_id: u64,
19173 workspace_id: WorkspaceId,
19174 window: &mut Window,
19175 cx: &mut Context<Editor>,
19176 ) {
19177 if self.is_singleton(cx)
19178 && !self.mode.is_minimap()
19179 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19180 {
19181 let buffer_snapshot = OnceCell::new();
19182
19183 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19184 if !folds.is_empty() {
19185 let snapshot =
19186 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19187 self.fold_ranges(
19188 folds
19189 .into_iter()
19190 .map(|(start, end)| {
19191 snapshot.clip_offset(start, Bias::Left)
19192 ..snapshot.clip_offset(end, Bias::Right)
19193 })
19194 .collect(),
19195 false,
19196 window,
19197 cx,
19198 );
19199 }
19200 }
19201
19202 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19203 if !selections.is_empty() {
19204 let snapshot =
19205 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19206 self.change_selections(None, window, cx, |s| {
19207 s.select_ranges(selections.into_iter().map(|(start, end)| {
19208 snapshot.clip_offset(start, Bias::Left)
19209 ..snapshot.clip_offset(end, Bias::Right)
19210 }));
19211 });
19212 }
19213 };
19214 }
19215
19216 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19217 }
19218}
19219
19220fn vim_enabled(cx: &App) -> bool {
19221 cx.global::<SettingsStore>()
19222 .raw_user_settings()
19223 .get("vim_mode")
19224 == Some(&serde_json::Value::Bool(true))
19225}
19226
19227// Consider user intent and default settings
19228fn choose_completion_range(
19229 completion: &Completion,
19230 intent: CompletionIntent,
19231 buffer: &Entity<Buffer>,
19232 cx: &mut Context<Editor>,
19233) -> Range<usize> {
19234 fn should_replace(
19235 completion: &Completion,
19236 insert_range: &Range<text::Anchor>,
19237 intent: CompletionIntent,
19238 completion_mode_setting: LspInsertMode,
19239 buffer: &Buffer,
19240 ) -> bool {
19241 // specific actions take precedence over settings
19242 match intent {
19243 CompletionIntent::CompleteWithInsert => return false,
19244 CompletionIntent::CompleteWithReplace => return true,
19245 CompletionIntent::Complete | CompletionIntent::Compose => {}
19246 }
19247
19248 match completion_mode_setting {
19249 LspInsertMode::Insert => false,
19250 LspInsertMode::Replace => true,
19251 LspInsertMode::ReplaceSubsequence => {
19252 let mut text_to_replace = buffer.chars_for_range(
19253 buffer.anchor_before(completion.replace_range.start)
19254 ..buffer.anchor_after(completion.replace_range.end),
19255 );
19256 let mut completion_text = completion.new_text.chars();
19257
19258 // is `text_to_replace` a subsequence of `completion_text`
19259 text_to_replace
19260 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19261 }
19262 LspInsertMode::ReplaceSuffix => {
19263 let range_after_cursor = insert_range.end..completion.replace_range.end;
19264
19265 let text_after_cursor = buffer
19266 .text_for_range(
19267 buffer.anchor_before(range_after_cursor.start)
19268 ..buffer.anchor_after(range_after_cursor.end),
19269 )
19270 .collect::<String>();
19271 completion.new_text.ends_with(&text_after_cursor)
19272 }
19273 }
19274 }
19275
19276 let buffer = buffer.read(cx);
19277
19278 if let CompletionSource::Lsp {
19279 insert_range: Some(insert_range),
19280 ..
19281 } = &completion.source
19282 {
19283 let completion_mode_setting =
19284 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19285 .completions
19286 .lsp_insert_mode;
19287
19288 if !should_replace(
19289 completion,
19290 &insert_range,
19291 intent,
19292 completion_mode_setting,
19293 buffer,
19294 ) {
19295 return insert_range.to_offset(buffer);
19296 }
19297 }
19298
19299 completion.replace_range.to_offset(buffer)
19300}
19301
19302fn insert_extra_newline_brackets(
19303 buffer: &MultiBufferSnapshot,
19304 range: Range<usize>,
19305 language: &language::LanguageScope,
19306) -> bool {
19307 let leading_whitespace_len = buffer
19308 .reversed_chars_at(range.start)
19309 .take_while(|c| c.is_whitespace() && *c != '\n')
19310 .map(|c| c.len_utf8())
19311 .sum::<usize>();
19312 let trailing_whitespace_len = buffer
19313 .chars_at(range.end)
19314 .take_while(|c| c.is_whitespace() && *c != '\n')
19315 .map(|c| c.len_utf8())
19316 .sum::<usize>();
19317 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19318
19319 language.brackets().any(|(pair, enabled)| {
19320 let pair_start = pair.start.trim_end();
19321 let pair_end = pair.end.trim_start();
19322
19323 enabled
19324 && pair.newline
19325 && buffer.contains_str_at(range.end, pair_end)
19326 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19327 })
19328}
19329
19330fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19331 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19332 [(buffer, range, _)] => (*buffer, range.clone()),
19333 _ => return false,
19334 };
19335 let pair = {
19336 let mut result: Option<BracketMatch> = None;
19337
19338 for pair in buffer
19339 .all_bracket_ranges(range.clone())
19340 .filter(move |pair| {
19341 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19342 })
19343 {
19344 let len = pair.close_range.end - pair.open_range.start;
19345
19346 if let Some(existing) = &result {
19347 let existing_len = existing.close_range.end - existing.open_range.start;
19348 if len > existing_len {
19349 continue;
19350 }
19351 }
19352
19353 result = Some(pair);
19354 }
19355
19356 result
19357 };
19358 let Some(pair) = pair else {
19359 return false;
19360 };
19361 pair.newline_only
19362 && buffer
19363 .chars_for_range(pair.open_range.end..range.start)
19364 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19365 .all(|c| c.is_whitespace() && c != '\n')
19366}
19367
19368fn update_uncommitted_diff_for_buffer(
19369 editor: Entity<Editor>,
19370 project: &Entity<Project>,
19371 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19372 buffer: Entity<MultiBuffer>,
19373 cx: &mut App,
19374) -> Task<()> {
19375 let mut tasks = Vec::new();
19376 project.update(cx, |project, cx| {
19377 for buffer in buffers {
19378 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19379 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19380 }
19381 }
19382 });
19383 cx.spawn(async move |cx| {
19384 let diffs = future::join_all(tasks).await;
19385 if editor
19386 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19387 .unwrap_or(false)
19388 {
19389 return;
19390 }
19391
19392 buffer
19393 .update(cx, |buffer, cx| {
19394 for diff in diffs.into_iter().flatten() {
19395 buffer.add_diff(diff, cx);
19396 }
19397 })
19398 .ok();
19399 })
19400}
19401
19402fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19403 let tab_size = tab_size.get() as usize;
19404 let mut width = offset;
19405
19406 for ch in text.chars() {
19407 width += if ch == '\t' {
19408 tab_size - (width % tab_size)
19409 } else {
19410 1
19411 };
19412 }
19413
19414 width - offset
19415}
19416
19417#[cfg(test)]
19418mod tests {
19419 use super::*;
19420
19421 #[test]
19422 fn test_string_size_with_expanded_tabs() {
19423 let nz = |val| NonZeroU32::new(val).unwrap();
19424 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19425 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19426 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19427 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19428 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19429 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19430 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19431 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19432 }
19433}
19434
19435/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19436struct WordBreakingTokenizer<'a> {
19437 input: &'a str,
19438}
19439
19440impl<'a> WordBreakingTokenizer<'a> {
19441 fn new(input: &'a str) -> Self {
19442 Self { input }
19443 }
19444}
19445
19446fn is_char_ideographic(ch: char) -> bool {
19447 use unicode_script::Script::*;
19448 use unicode_script::UnicodeScript;
19449 matches!(ch.script(), Han | Tangut | Yi)
19450}
19451
19452fn is_grapheme_ideographic(text: &str) -> bool {
19453 text.chars().any(is_char_ideographic)
19454}
19455
19456fn is_grapheme_whitespace(text: &str) -> bool {
19457 text.chars().any(|x| x.is_whitespace())
19458}
19459
19460fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19461 text.chars().next().map_or(false, |ch| {
19462 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19463 })
19464}
19465
19466#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19467enum WordBreakToken<'a> {
19468 Word { token: &'a str, grapheme_len: usize },
19469 InlineWhitespace { token: &'a str, grapheme_len: usize },
19470 Newline,
19471}
19472
19473impl<'a> Iterator for WordBreakingTokenizer<'a> {
19474 /// Yields a span, the count of graphemes in the token, and whether it was
19475 /// whitespace. Note that it also breaks at word boundaries.
19476 type Item = WordBreakToken<'a>;
19477
19478 fn next(&mut self) -> Option<Self::Item> {
19479 use unicode_segmentation::UnicodeSegmentation;
19480 if self.input.is_empty() {
19481 return None;
19482 }
19483
19484 let mut iter = self.input.graphemes(true).peekable();
19485 let mut offset = 0;
19486 let mut grapheme_len = 0;
19487 if let Some(first_grapheme) = iter.next() {
19488 let is_newline = first_grapheme == "\n";
19489 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19490 offset += first_grapheme.len();
19491 grapheme_len += 1;
19492 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19493 if let Some(grapheme) = iter.peek().copied() {
19494 if should_stay_with_preceding_ideograph(grapheme) {
19495 offset += grapheme.len();
19496 grapheme_len += 1;
19497 }
19498 }
19499 } else {
19500 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19501 let mut next_word_bound = words.peek().copied();
19502 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19503 next_word_bound = words.next();
19504 }
19505 while let Some(grapheme) = iter.peek().copied() {
19506 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19507 break;
19508 };
19509 if is_grapheme_whitespace(grapheme) != is_whitespace
19510 || (grapheme == "\n") != is_newline
19511 {
19512 break;
19513 };
19514 offset += grapheme.len();
19515 grapheme_len += 1;
19516 iter.next();
19517 }
19518 }
19519 let token = &self.input[..offset];
19520 self.input = &self.input[offset..];
19521 if token == "\n" {
19522 Some(WordBreakToken::Newline)
19523 } else if is_whitespace {
19524 Some(WordBreakToken::InlineWhitespace {
19525 token,
19526 grapheme_len,
19527 })
19528 } else {
19529 Some(WordBreakToken::Word {
19530 token,
19531 grapheme_len,
19532 })
19533 }
19534 } else {
19535 None
19536 }
19537 }
19538}
19539
19540#[test]
19541fn test_word_breaking_tokenizer() {
19542 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19543 ("", &[]),
19544 (" ", &[whitespace(" ", 2)]),
19545 ("Ʒ", &[word("Ʒ", 1)]),
19546 ("Ǽ", &[word("Ǽ", 1)]),
19547 ("⋑", &[word("⋑", 1)]),
19548 ("⋑⋑", &[word("⋑⋑", 2)]),
19549 (
19550 "原理,进而",
19551 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19552 ),
19553 (
19554 "hello world",
19555 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19556 ),
19557 (
19558 "hello, world",
19559 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19560 ),
19561 (
19562 " hello world",
19563 &[
19564 whitespace(" ", 2),
19565 word("hello", 5),
19566 whitespace(" ", 1),
19567 word("world", 5),
19568 ],
19569 ),
19570 (
19571 "这是什么 \n 钢笔",
19572 &[
19573 word("这", 1),
19574 word("是", 1),
19575 word("什", 1),
19576 word("么", 1),
19577 whitespace(" ", 1),
19578 newline(),
19579 whitespace(" ", 1),
19580 word("钢", 1),
19581 word("笔", 1),
19582 ],
19583 ),
19584 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19585 ];
19586
19587 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19588 WordBreakToken::Word {
19589 token,
19590 grapheme_len,
19591 }
19592 }
19593
19594 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19595 WordBreakToken::InlineWhitespace {
19596 token,
19597 grapheme_len,
19598 }
19599 }
19600
19601 fn newline() -> WordBreakToken<'static> {
19602 WordBreakToken::Newline
19603 }
19604
19605 for (input, result) in tests {
19606 assert_eq!(
19607 WordBreakingTokenizer::new(input)
19608 .collect::<Vec<_>>()
19609 .as_slice(),
19610 *result,
19611 );
19612 }
19613}
19614
19615fn wrap_with_prefix(
19616 line_prefix: String,
19617 unwrapped_text: String,
19618 wrap_column: usize,
19619 tab_size: NonZeroU32,
19620 preserve_existing_whitespace: bool,
19621) -> String {
19622 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19623 let mut wrapped_text = String::new();
19624 let mut current_line = line_prefix.clone();
19625
19626 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19627 let mut current_line_len = line_prefix_len;
19628 let mut in_whitespace = false;
19629 for token in tokenizer {
19630 let have_preceding_whitespace = in_whitespace;
19631 match token {
19632 WordBreakToken::Word {
19633 token,
19634 grapheme_len,
19635 } => {
19636 in_whitespace = false;
19637 if current_line_len + grapheme_len > wrap_column
19638 && current_line_len != line_prefix_len
19639 {
19640 wrapped_text.push_str(current_line.trim_end());
19641 wrapped_text.push('\n');
19642 current_line.truncate(line_prefix.len());
19643 current_line_len = line_prefix_len;
19644 }
19645 current_line.push_str(token);
19646 current_line_len += grapheme_len;
19647 }
19648 WordBreakToken::InlineWhitespace {
19649 mut token,
19650 mut grapheme_len,
19651 } => {
19652 in_whitespace = true;
19653 if have_preceding_whitespace && !preserve_existing_whitespace {
19654 continue;
19655 }
19656 if !preserve_existing_whitespace {
19657 token = " ";
19658 grapheme_len = 1;
19659 }
19660 if current_line_len + grapheme_len > wrap_column {
19661 wrapped_text.push_str(current_line.trim_end());
19662 wrapped_text.push('\n');
19663 current_line.truncate(line_prefix.len());
19664 current_line_len = line_prefix_len;
19665 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19666 current_line.push_str(token);
19667 current_line_len += grapheme_len;
19668 }
19669 }
19670 WordBreakToken::Newline => {
19671 in_whitespace = true;
19672 if preserve_existing_whitespace {
19673 wrapped_text.push_str(current_line.trim_end());
19674 wrapped_text.push('\n');
19675 current_line.truncate(line_prefix.len());
19676 current_line_len = line_prefix_len;
19677 } else if have_preceding_whitespace {
19678 continue;
19679 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19680 {
19681 wrapped_text.push_str(current_line.trim_end());
19682 wrapped_text.push('\n');
19683 current_line.truncate(line_prefix.len());
19684 current_line_len = line_prefix_len;
19685 } else if current_line_len != line_prefix_len {
19686 current_line.push(' ');
19687 current_line_len += 1;
19688 }
19689 }
19690 }
19691 }
19692
19693 if !current_line.is_empty() {
19694 wrapped_text.push_str(¤t_line);
19695 }
19696 wrapped_text
19697}
19698
19699#[test]
19700fn test_wrap_with_prefix() {
19701 assert_eq!(
19702 wrap_with_prefix(
19703 "# ".to_string(),
19704 "abcdefg".to_string(),
19705 4,
19706 NonZeroU32::new(4).unwrap(),
19707 false,
19708 ),
19709 "# abcdefg"
19710 );
19711 assert_eq!(
19712 wrap_with_prefix(
19713 "".to_string(),
19714 "\thello world".to_string(),
19715 8,
19716 NonZeroU32::new(4).unwrap(),
19717 false,
19718 ),
19719 "hello\nworld"
19720 );
19721 assert_eq!(
19722 wrap_with_prefix(
19723 "// ".to_string(),
19724 "xx \nyy zz aa bb cc".to_string(),
19725 12,
19726 NonZeroU32::new(4).unwrap(),
19727 false,
19728 ),
19729 "// xx yy zz\n// aa bb cc"
19730 );
19731 assert_eq!(
19732 wrap_with_prefix(
19733 String::new(),
19734 "这是什么 \n 钢笔".to_string(),
19735 3,
19736 NonZeroU32::new(4).unwrap(),
19737 false,
19738 ),
19739 "这是什\n么 钢\n笔"
19740 );
19741}
19742
19743pub trait CollaborationHub {
19744 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19745 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19746 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19747}
19748
19749impl CollaborationHub for Entity<Project> {
19750 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19751 self.read(cx).collaborators()
19752 }
19753
19754 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19755 self.read(cx).user_store().read(cx).participant_indices()
19756 }
19757
19758 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19759 let this = self.read(cx);
19760 let user_ids = this.collaborators().values().map(|c| c.user_id);
19761 this.user_store().read_with(cx, |user_store, cx| {
19762 user_store.participant_names(user_ids, cx)
19763 })
19764 }
19765}
19766
19767pub trait SemanticsProvider {
19768 fn hover(
19769 &self,
19770 buffer: &Entity<Buffer>,
19771 position: text::Anchor,
19772 cx: &mut App,
19773 ) -> Option<Task<Vec<project::Hover>>>;
19774
19775 fn inline_values(
19776 &self,
19777 buffer_handle: Entity<Buffer>,
19778 range: Range<text::Anchor>,
19779 cx: &mut App,
19780 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19781
19782 fn inlay_hints(
19783 &self,
19784 buffer_handle: Entity<Buffer>,
19785 range: Range<text::Anchor>,
19786 cx: &mut App,
19787 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19788
19789 fn resolve_inlay_hint(
19790 &self,
19791 hint: InlayHint,
19792 buffer_handle: Entity<Buffer>,
19793 server_id: LanguageServerId,
19794 cx: &mut App,
19795 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19796
19797 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19798
19799 fn document_highlights(
19800 &self,
19801 buffer: &Entity<Buffer>,
19802 position: text::Anchor,
19803 cx: &mut App,
19804 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19805
19806 fn definitions(
19807 &self,
19808 buffer: &Entity<Buffer>,
19809 position: text::Anchor,
19810 kind: GotoDefinitionKind,
19811 cx: &mut App,
19812 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19813
19814 fn range_for_rename(
19815 &self,
19816 buffer: &Entity<Buffer>,
19817 position: text::Anchor,
19818 cx: &mut App,
19819 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19820
19821 fn perform_rename(
19822 &self,
19823 buffer: &Entity<Buffer>,
19824 position: text::Anchor,
19825 new_name: String,
19826 cx: &mut App,
19827 ) -> Option<Task<Result<ProjectTransaction>>>;
19828}
19829
19830pub trait CompletionProvider {
19831 fn completions(
19832 &self,
19833 excerpt_id: ExcerptId,
19834 buffer: &Entity<Buffer>,
19835 buffer_position: text::Anchor,
19836 trigger: CompletionContext,
19837 window: &mut Window,
19838 cx: &mut Context<Editor>,
19839 ) -> Task<Result<Option<Vec<Completion>>>>;
19840
19841 fn resolve_completions(
19842 &self,
19843 buffer: Entity<Buffer>,
19844 completion_indices: Vec<usize>,
19845 completions: Rc<RefCell<Box<[Completion]>>>,
19846 cx: &mut Context<Editor>,
19847 ) -> Task<Result<bool>>;
19848
19849 fn apply_additional_edits_for_completion(
19850 &self,
19851 _buffer: Entity<Buffer>,
19852 _completions: Rc<RefCell<Box<[Completion]>>>,
19853 _completion_index: usize,
19854 _push_to_history: bool,
19855 _cx: &mut Context<Editor>,
19856 ) -> Task<Result<Option<language::Transaction>>> {
19857 Task::ready(Ok(None))
19858 }
19859
19860 fn is_completion_trigger(
19861 &self,
19862 buffer: &Entity<Buffer>,
19863 position: language::Anchor,
19864 text: &str,
19865 trigger_in_words: bool,
19866 cx: &mut Context<Editor>,
19867 ) -> bool;
19868
19869 fn sort_completions(&self) -> bool {
19870 true
19871 }
19872
19873 fn filter_completions(&self) -> bool {
19874 true
19875 }
19876}
19877
19878pub trait CodeActionProvider {
19879 fn id(&self) -> Arc<str>;
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
19889 fn apply_code_action(
19890 &self,
19891 buffer_handle: Entity<Buffer>,
19892 action: CodeAction,
19893 excerpt_id: ExcerptId,
19894 push_to_history: bool,
19895 window: &mut Window,
19896 cx: &mut App,
19897 ) -> Task<Result<ProjectTransaction>>;
19898}
19899
19900impl CodeActionProvider for Entity<Project> {
19901 fn id(&self) -> Arc<str> {
19902 "project".into()
19903 }
19904
19905 fn code_actions(
19906 &self,
19907 buffer: &Entity<Buffer>,
19908 range: Range<text::Anchor>,
19909 _window: &mut Window,
19910 cx: &mut App,
19911 ) -> Task<Result<Vec<CodeAction>>> {
19912 self.update(cx, |project, cx| {
19913 let code_lens = project.code_lens(buffer, range.clone(), cx);
19914 let code_actions = project.code_actions(buffer, range, None, cx);
19915 cx.background_spawn(async move {
19916 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19917 Ok(code_lens
19918 .context("code lens fetch")?
19919 .into_iter()
19920 .chain(code_actions.context("code action fetch")?)
19921 .collect())
19922 })
19923 })
19924 }
19925
19926 fn apply_code_action(
19927 &self,
19928 buffer_handle: Entity<Buffer>,
19929 action: CodeAction,
19930 _excerpt_id: ExcerptId,
19931 push_to_history: bool,
19932 _window: &mut Window,
19933 cx: &mut App,
19934 ) -> Task<Result<ProjectTransaction>> {
19935 self.update(cx, |project, cx| {
19936 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19937 })
19938 }
19939}
19940
19941fn snippet_completions(
19942 project: &Project,
19943 buffer: &Entity<Buffer>,
19944 buffer_position: text::Anchor,
19945 cx: &mut App,
19946) -> Task<Result<Vec<Completion>>> {
19947 let languages = buffer.read(cx).languages_at(buffer_position);
19948 let snippet_store = project.snippets().read(cx);
19949
19950 let scopes: Vec<_> = languages
19951 .iter()
19952 .filter_map(|language| {
19953 let language_name = language.lsp_id();
19954 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19955
19956 if snippets.is_empty() {
19957 None
19958 } else {
19959 Some((language.default_scope(), snippets))
19960 }
19961 })
19962 .collect();
19963
19964 if scopes.is_empty() {
19965 return Task::ready(Ok(vec![]));
19966 }
19967
19968 let snapshot = buffer.read(cx).text_snapshot();
19969 let chars: String = snapshot
19970 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19971 .collect();
19972 let executor = cx.background_executor().clone();
19973
19974 cx.background_spawn(async move {
19975 let mut all_results: Vec<Completion> = Vec::new();
19976 for (scope, snippets) in scopes.into_iter() {
19977 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19978 let mut last_word = chars
19979 .chars()
19980 .take_while(|c| classifier.is_word(*c))
19981 .collect::<String>();
19982 last_word = last_word.chars().rev().collect();
19983
19984 if last_word.is_empty() {
19985 return Ok(vec![]);
19986 }
19987
19988 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19989 let to_lsp = |point: &text::Anchor| {
19990 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19991 point_to_lsp(end)
19992 };
19993 let lsp_end = to_lsp(&buffer_position);
19994
19995 let candidates = snippets
19996 .iter()
19997 .enumerate()
19998 .flat_map(|(ix, snippet)| {
19999 snippet
20000 .prefix
20001 .iter()
20002 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20003 })
20004 .collect::<Vec<StringMatchCandidate>>();
20005
20006 let mut matches = fuzzy::match_strings(
20007 &candidates,
20008 &last_word,
20009 last_word.chars().any(|c| c.is_uppercase()),
20010 100,
20011 &Default::default(),
20012 executor.clone(),
20013 )
20014 .await;
20015
20016 // Remove all candidates where the query's start does not match the start of any word in the candidate
20017 if let Some(query_start) = last_word.chars().next() {
20018 matches.retain(|string_match| {
20019 split_words(&string_match.string).any(|word| {
20020 // Check that the first codepoint of the word as lowercase matches the first
20021 // codepoint of the query as lowercase
20022 word.chars()
20023 .flat_map(|codepoint| codepoint.to_lowercase())
20024 .zip(query_start.to_lowercase())
20025 .all(|(word_cp, query_cp)| word_cp == query_cp)
20026 })
20027 });
20028 }
20029
20030 let matched_strings = matches
20031 .into_iter()
20032 .map(|m| m.string)
20033 .collect::<HashSet<_>>();
20034
20035 let mut result: Vec<Completion> = snippets
20036 .iter()
20037 .filter_map(|snippet| {
20038 let matching_prefix = snippet
20039 .prefix
20040 .iter()
20041 .find(|prefix| matched_strings.contains(*prefix))?;
20042 let start = as_offset - last_word.len();
20043 let start = snapshot.anchor_before(start);
20044 let range = start..buffer_position;
20045 let lsp_start = to_lsp(&start);
20046 let lsp_range = lsp::Range {
20047 start: lsp_start,
20048 end: lsp_end,
20049 };
20050 Some(Completion {
20051 replace_range: range,
20052 new_text: snippet.body.clone(),
20053 source: CompletionSource::Lsp {
20054 insert_range: None,
20055 server_id: LanguageServerId(usize::MAX),
20056 resolved: true,
20057 lsp_completion: Box::new(lsp::CompletionItem {
20058 label: snippet.prefix.first().unwrap().clone(),
20059 kind: Some(CompletionItemKind::SNIPPET),
20060 label_details: snippet.description.as_ref().map(|description| {
20061 lsp::CompletionItemLabelDetails {
20062 detail: Some(description.clone()),
20063 description: None,
20064 }
20065 }),
20066 insert_text_format: Some(InsertTextFormat::SNIPPET),
20067 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20068 lsp::InsertReplaceEdit {
20069 new_text: snippet.body.clone(),
20070 insert: lsp_range,
20071 replace: lsp_range,
20072 },
20073 )),
20074 filter_text: Some(snippet.body.clone()),
20075 sort_text: Some(char::MAX.to_string()),
20076 ..lsp::CompletionItem::default()
20077 }),
20078 lsp_defaults: None,
20079 },
20080 label: CodeLabel {
20081 text: matching_prefix.clone(),
20082 runs: Vec::new(),
20083 filter_range: 0..matching_prefix.len(),
20084 },
20085 icon_path: None,
20086 documentation: Some(
20087 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20088 single_line: snippet.name.clone().into(),
20089 plain_text: snippet
20090 .description
20091 .clone()
20092 .map(|description| description.into()),
20093 },
20094 ),
20095 insert_text_mode: None,
20096 confirm: None,
20097 })
20098 })
20099 .collect();
20100
20101 all_results.append(&mut result);
20102 }
20103
20104 Ok(all_results)
20105 })
20106}
20107
20108impl CompletionProvider for Entity<Project> {
20109 fn completions(
20110 &self,
20111 _excerpt_id: ExcerptId,
20112 buffer: &Entity<Buffer>,
20113 buffer_position: text::Anchor,
20114 options: CompletionContext,
20115 _window: &mut Window,
20116 cx: &mut Context<Editor>,
20117 ) -> Task<Result<Option<Vec<Completion>>>> {
20118 self.update(cx, |project, cx| {
20119 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20120 let project_completions = project.completions(buffer, buffer_position, options, cx);
20121 cx.background_spawn(async move {
20122 let snippets_completions = snippets.await?;
20123 match project_completions.await? {
20124 Some(mut completions) => {
20125 completions.extend(snippets_completions);
20126 Ok(Some(completions))
20127 }
20128 None => {
20129 if snippets_completions.is_empty() {
20130 Ok(None)
20131 } else {
20132 Ok(Some(snippets_completions))
20133 }
20134 }
20135 }
20136 })
20137 })
20138 }
20139
20140 fn resolve_completions(
20141 &self,
20142 buffer: Entity<Buffer>,
20143 completion_indices: Vec<usize>,
20144 completions: Rc<RefCell<Box<[Completion]>>>,
20145 cx: &mut Context<Editor>,
20146 ) -> Task<Result<bool>> {
20147 self.update(cx, |project, cx| {
20148 project.lsp_store().update(cx, |lsp_store, cx| {
20149 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20150 })
20151 })
20152 }
20153
20154 fn apply_additional_edits_for_completion(
20155 &self,
20156 buffer: Entity<Buffer>,
20157 completions: Rc<RefCell<Box<[Completion]>>>,
20158 completion_index: usize,
20159 push_to_history: bool,
20160 cx: &mut Context<Editor>,
20161 ) -> Task<Result<Option<language::Transaction>>> {
20162 self.update(cx, |project, cx| {
20163 project.lsp_store().update(cx, |lsp_store, cx| {
20164 lsp_store.apply_additional_edits_for_completion(
20165 buffer,
20166 completions,
20167 completion_index,
20168 push_to_history,
20169 cx,
20170 )
20171 })
20172 })
20173 }
20174
20175 fn is_completion_trigger(
20176 &self,
20177 buffer: &Entity<Buffer>,
20178 position: language::Anchor,
20179 text: &str,
20180 trigger_in_words: bool,
20181 cx: &mut Context<Editor>,
20182 ) -> bool {
20183 let mut chars = text.chars();
20184 let char = if let Some(char) = chars.next() {
20185 char
20186 } else {
20187 return false;
20188 };
20189 if chars.next().is_some() {
20190 return false;
20191 }
20192
20193 let buffer = buffer.read(cx);
20194 let snapshot = buffer.snapshot();
20195 if !snapshot.settings_at(position, cx).show_completions_on_input {
20196 return false;
20197 }
20198 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20199 if trigger_in_words && classifier.is_word(char) {
20200 return true;
20201 }
20202
20203 buffer.completion_triggers().contains(text)
20204 }
20205}
20206
20207impl SemanticsProvider for Entity<Project> {
20208 fn hover(
20209 &self,
20210 buffer: &Entity<Buffer>,
20211 position: text::Anchor,
20212 cx: &mut App,
20213 ) -> Option<Task<Vec<project::Hover>>> {
20214 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20215 }
20216
20217 fn document_highlights(
20218 &self,
20219 buffer: &Entity<Buffer>,
20220 position: text::Anchor,
20221 cx: &mut App,
20222 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20223 Some(self.update(cx, |project, cx| {
20224 project.document_highlights(buffer, position, cx)
20225 }))
20226 }
20227
20228 fn definitions(
20229 &self,
20230 buffer: &Entity<Buffer>,
20231 position: text::Anchor,
20232 kind: GotoDefinitionKind,
20233 cx: &mut App,
20234 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20235 Some(self.update(cx, |project, cx| match kind {
20236 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20237 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20238 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20239 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20240 }))
20241 }
20242
20243 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20244 // TODO: make this work for remote projects
20245 self.update(cx, |project, cx| {
20246 if project
20247 .active_debug_session(cx)
20248 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20249 {
20250 return true;
20251 }
20252
20253 buffer.update(cx, |buffer, cx| {
20254 project.any_language_server_supports_inlay_hints(buffer, cx)
20255 })
20256 })
20257 }
20258
20259 fn inline_values(
20260 &self,
20261 buffer_handle: Entity<Buffer>,
20262
20263 range: Range<text::Anchor>,
20264 cx: &mut App,
20265 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20266 self.update(cx, |project, cx| {
20267 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20268
20269 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20270 })
20271 }
20272
20273 fn inlay_hints(
20274 &self,
20275 buffer_handle: Entity<Buffer>,
20276 range: Range<text::Anchor>,
20277 cx: &mut App,
20278 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20279 Some(self.update(cx, |project, cx| {
20280 project.inlay_hints(buffer_handle, range, cx)
20281 }))
20282 }
20283
20284 fn resolve_inlay_hint(
20285 &self,
20286 hint: InlayHint,
20287 buffer_handle: Entity<Buffer>,
20288 server_id: LanguageServerId,
20289 cx: &mut App,
20290 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20291 Some(self.update(cx, |project, cx| {
20292 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20293 }))
20294 }
20295
20296 fn range_for_rename(
20297 &self,
20298 buffer: &Entity<Buffer>,
20299 position: text::Anchor,
20300 cx: &mut App,
20301 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20302 Some(self.update(cx, |project, cx| {
20303 let buffer = buffer.clone();
20304 let task = project.prepare_rename(buffer.clone(), position, cx);
20305 cx.spawn(async move |_, cx| {
20306 Ok(match task.await? {
20307 PrepareRenameResponse::Success(range) => Some(range),
20308 PrepareRenameResponse::InvalidPosition => None,
20309 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20310 // Fallback on using TreeSitter info to determine identifier range
20311 buffer.update(cx, |buffer, _| {
20312 let snapshot = buffer.snapshot();
20313 let (range, kind) = snapshot.surrounding_word(position);
20314 if kind != Some(CharKind::Word) {
20315 return None;
20316 }
20317 Some(
20318 snapshot.anchor_before(range.start)
20319 ..snapshot.anchor_after(range.end),
20320 )
20321 })?
20322 }
20323 })
20324 })
20325 }))
20326 }
20327
20328 fn perform_rename(
20329 &self,
20330 buffer: &Entity<Buffer>,
20331 position: text::Anchor,
20332 new_name: String,
20333 cx: &mut App,
20334 ) -> Option<Task<Result<ProjectTransaction>>> {
20335 Some(self.update(cx, |project, cx| {
20336 project.perform_rename(buffer.clone(), position, new_name, cx)
20337 }))
20338 }
20339}
20340
20341fn inlay_hint_settings(
20342 location: Anchor,
20343 snapshot: &MultiBufferSnapshot,
20344 cx: &mut Context<Editor>,
20345) -> InlayHintSettings {
20346 let file = snapshot.file_at(location);
20347 let language = snapshot.language_at(location).map(|l| l.name());
20348 language_settings(language, file, cx).inlay_hints
20349}
20350
20351fn consume_contiguous_rows(
20352 contiguous_row_selections: &mut Vec<Selection<Point>>,
20353 selection: &Selection<Point>,
20354 display_map: &DisplaySnapshot,
20355 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20356) -> (MultiBufferRow, MultiBufferRow) {
20357 contiguous_row_selections.push(selection.clone());
20358 let start_row = MultiBufferRow(selection.start.row);
20359 let mut end_row = ending_row(selection, display_map);
20360
20361 while let Some(next_selection) = selections.peek() {
20362 if next_selection.start.row <= end_row.0 {
20363 end_row = ending_row(next_selection, display_map);
20364 contiguous_row_selections.push(selections.next().unwrap().clone());
20365 } else {
20366 break;
20367 }
20368 }
20369 (start_row, end_row)
20370}
20371
20372fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20373 if next_selection.end.column > 0 || next_selection.is_empty() {
20374 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20375 } else {
20376 MultiBufferRow(next_selection.end.row)
20377 }
20378}
20379
20380impl EditorSnapshot {
20381 pub fn remote_selections_in_range<'a>(
20382 &'a self,
20383 range: &'a Range<Anchor>,
20384 collaboration_hub: &dyn CollaborationHub,
20385 cx: &'a App,
20386 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20387 let participant_names = collaboration_hub.user_names(cx);
20388 let participant_indices = collaboration_hub.user_participant_indices(cx);
20389 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20390 let collaborators_by_replica_id = collaborators_by_peer_id
20391 .values()
20392 .map(|collaborator| (collaborator.replica_id, collaborator))
20393 .collect::<HashMap<_, _>>();
20394 self.buffer_snapshot
20395 .selections_in_range(range, false)
20396 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20397 if replica_id == AGENT_REPLICA_ID {
20398 Some(RemoteSelection {
20399 replica_id,
20400 selection,
20401 cursor_shape,
20402 line_mode,
20403 collaborator_id: CollaboratorId::Agent,
20404 user_name: Some("Agent".into()),
20405 color: cx.theme().players().agent(),
20406 })
20407 } else {
20408 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20409 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20410 let user_name = participant_names.get(&collaborator.user_id).cloned();
20411 Some(RemoteSelection {
20412 replica_id,
20413 selection,
20414 cursor_shape,
20415 line_mode,
20416 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20417 user_name,
20418 color: if let Some(index) = participant_index {
20419 cx.theme().players().color_for_participant(index.0)
20420 } else {
20421 cx.theme().players().absent()
20422 },
20423 })
20424 }
20425 })
20426 }
20427
20428 pub fn hunks_for_ranges(
20429 &self,
20430 ranges: impl IntoIterator<Item = Range<Point>>,
20431 ) -> Vec<MultiBufferDiffHunk> {
20432 let mut hunks = Vec::new();
20433 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20434 HashMap::default();
20435 for query_range in ranges {
20436 let query_rows =
20437 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20438 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20439 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20440 ) {
20441 // Include deleted hunks that are adjacent to the query range, because
20442 // otherwise they would be missed.
20443 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20444 if hunk.status().is_deleted() {
20445 intersects_range |= hunk.row_range.start == query_rows.end;
20446 intersects_range |= hunk.row_range.end == query_rows.start;
20447 }
20448 if intersects_range {
20449 if !processed_buffer_rows
20450 .entry(hunk.buffer_id)
20451 .or_default()
20452 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20453 {
20454 continue;
20455 }
20456 hunks.push(hunk);
20457 }
20458 }
20459 }
20460
20461 hunks
20462 }
20463
20464 fn display_diff_hunks_for_rows<'a>(
20465 &'a self,
20466 display_rows: Range<DisplayRow>,
20467 folded_buffers: &'a HashSet<BufferId>,
20468 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20469 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20470 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20471
20472 self.buffer_snapshot
20473 .diff_hunks_in_range(buffer_start..buffer_end)
20474 .filter_map(|hunk| {
20475 if folded_buffers.contains(&hunk.buffer_id) {
20476 return None;
20477 }
20478
20479 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20480 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20481
20482 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20483 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20484
20485 let display_hunk = if hunk_display_start.column() != 0 {
20486 DisplayDiffHunk::Folded {
20487 display_row: hunk_display_start.row(),
20488 }
20489 } else {
20490 let mut end_row = hunk_display_end.row();
20491 if hunk_display_end.column() > 0 {
20492 end_row.0 += 1;
20493 }
20494 let is_created_file = hunk.is_created_file();
20495 DisplayDiffHunk::Unfolded {
20496 status: hunk.status(),
20497 diff_base_byte_range: hunk.diff_base_byte_range,
20498 display_row_range: hunk_display_start.row()..end_row,
20499 multi_buffer_range: Anchor::range_in_buffer(
20500 hunk.excerpt_id,
20501 hunk.buffer_id,
20502 hunk.buffer_range,
20503 ),
20504 is_created_file,
20505 }
20506 };
20507
20508 Some(display_hunk)
20509 })
20510 }
20511
20512 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20513 self.display_snapshot.buffer_snapshot.language_at(position)
20514 }
20515
20516 pub fn is_focused(&self) -> bool {
20517 self.is_focused
20518 }
20519
20520 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20521 self.placeholder_text.as_ref()
20522 }
20523
20524 pub fn scroll_position(&self) -> gpui::Point<f32> {
20525 self.scroll_anchor.scroll_position(&self.display_snapshot)
20526 }
20527
20528 fn gutter_dimensions(
20529 &self,
20530 font_id: FontId,
20531 font_size: Pixels,
20532 max_line_number_width: Pixels,
20533 cx: &App,
20534 ) -> Option<GutterDimensions> {
20535 if !self.show_gutter {
20536 return None;
20537 }
20538
20539 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20540 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20541
20542 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20543 matches!(
20544 ProjectSettings::get_global(cx).git.git_gutter,
20545 Some(GitGutterSetting::TrackedFiles)
20546 )
20547 });
20548 let gutter_settings = EditorSettings::get_global(cx).gutter;
20549 let show_line_numbers = self
20550 .show_line_numbers
20551 .unwrap_or(gutter_settings.line_numbers);
20552 let line_gutter_width = if show_line_numbers {
20553 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20554 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20555 max_line_number_width.max(min_width_for_number_on_gutter)
20556 } else {
20557 0.0.into()
20558 };
20559
20560 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20561 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20562
20563 let git_blame_entries_width =
20564 self.git_blame_gutter_max_author_length
20565 .map(|max_author_length| {
20566 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20567 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20568
20569 /// The number of characters to dedicate to gaps and margins.
20570 const SPACING_WIDTH: usize = 4;
20571
20572 let max_char_count = max_author_length.min(renderer.max_author_length())
20573 + ::git::SHORT_SHA_LENGTH
20574 + MAX_RELATIVE_TIMESTAMP.len()
20575 + SPACING_WIDTH;
20576
20577 em_advance * max_char_count
20578 });
20579
20580 let is_singleton = self.buffer_snapshot.is_singleton();
20581
20582 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20583 left_padding += if !is_singleton {
20584 em_width * 4.0
20585 } else if show_runnables || show_breakpoints {
20586 em_width * 3.0
20587 } else if show_git_gutter && show_line_numbers {
20588 em_width * 2.0
20589 } else if show_git_gutter || show_line_numbers {
20590 em_width
20591 } else {
20592 px(0.)
20593 };
20594
20595 let shows_folds = is_singleton && gutter_settings.folds;
20596
20597 let right_padding = if shows_folds && show_line_numbers {
20598 em_width * 4.0
20599 } else if shows_folds || (!is_singleton && show_line_numbers) {
20600 em_width * 3.0
20601 } else if show_line_numbers {
20602 em_width
20603 } else {
20604 px(0.)
20605 };
20606
20607 Some(GutterDimensions {
20608 left_padding,
20609 right_padding,
20610 width: line_gutter_width + left_padding + right_padding,
20611 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20612 git_blame_entries_width,
20613 })
20614 }
20615
20616 pub fn render_crease_toggle(
20617 &self,
20618 buffer_row: MultiBufferRow,
20619 row_contains_cursor: bool,
20620 editor: Entity<Editor>,
20621 window: &mut Window,
20622 cx: &mut App,
20623 ) -> Option<AnyElement> {
20624 let folded = self.is_line_folded(buffer_row);
20625 let mut is_foldable = false;
20626
20627 if let Some(crease) = self
20628 .crease_snapshot
20629 .query_row(buffer_row, &self.buffer_snapshot)
20630 {
20631 is_foldable = true;
20632 match crease {
20633 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20634 if let Some(render_toggle) = render_toggle {
20635 let toggle_callback =
20636 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20637 if folded {
20638 editor.update(cx, |editor, cx| {
20639 editor.fold_at(buffer_row, window, cx)
20640 });
20641 } else {
20642 editor.update(cx, |editor, cx| {
20643 editor.unfold_at(buffer_row, window, cx)
20644 });
20645 }
20646 });
20647 return Some((render_toggle)(
20648 buffer_row,
20649 folded,
20650 toggle_callback,
20651 window,
20652 cx,
20653 ));
20654 }
20655 }
20656 }
20657 }
20658
20659 is_foldable |= self.starts_indent(buffer_row);
20660
20661 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20662 Some(
20663 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20664 .toggle_state(folded)
20665 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20666 if folded {
20667 this.unfold_at(buffer_row, window, cx);
20668 } else {
20669 this.fold_at(buffer_row, window, cx);
20670 }
20671 }))
20672 .into_any_element(),
20673 )
20674 } else {
20675 None
20676 }
20677 }
20678
20679 pub fn render_crease_trailer(
20680 &self,
20681 buffer_row: MultiBufferRow,
20682 window: &mut Window,
20683 cx: &mut App,
20684 ) -> Option<AnyElement> {
20685 let folded = self.is_line_folded(buffer_row);
20686 if let Crease::Inline { render_trailer, .. } = self
20687 .crease_snapshot
20688 .query_row(buffer_row, &self.buffer_snapshot)?
20689 {
20690 let render_trailer = render_trailer.as_ref()?;
20691 Some(render_trailer(buffer_row, folded, window, cx))
20692 } else {
20693 None
20694 }
20695 }
20696}
20697
20698impl Deref for EditorSnapshot {
20699 type Target = DisplaySnapshot;
20700
20701 fn deref(&self) -> &Self::Target {
20702 &self.display_snapshot
20703 }
20704}
20705
20706#[derive(Clone, Debug, PartialEq, Eq)]
20707pub enum EditorEvent {
20708 InputIgnored {
20709 text: Arc<str>,
20710 },
20711 InputHandled {
20712 utf16_range_to_replace: Option<Range<isize>>,
20713 text: Arc<str>,
20714 },
20715 ExcerptsAdded {
20716 buffer: Entity<Buffer>,
20717 predecessor: ExcerptId,
20718 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20719 },
20720 ExcerptsRemoved {
20721 ids: Vec<ExcerptId>,
20722 removed_buffer_ids: Vec<BufferId>,
20723 },
20724 BufferFoldToggled {
20725 ids: Vec<ExcerptId>,
20726 folded: bool,
20727 },
20728 ExcerptsEdited {
20729 ids: Vec<ExcerptId>,
20730 },
20731 ExcerptsExpanded {
20732 ids: Vec<ExcerptId>,
20733 },
20734 BufferEdited,
20735 Edited {
20736 transaction_id: clock::Lamport,
20737 },
20738 Reparsed(BufferId),
20739 Focused,
20740 FocusedIn,
20741 Blurred,
20742 DirtyChanged,
20743 Saved,
20744 TitleChanged,
20745 DiffBaseChanged,
20746 SelectionsChanged {
20747 local: bool,
20748 },
20749 ScrollPositionChanged {
20750 local: bool,
20751 autoscroll: bool,
20752 },
20753 Closed,
20754 TransactionUndone {
20755 transaction_id: clock::Lamport,
20756 },
20757 TransactionBegun {
20758 transaction_id: clock::Lamport,
20759 },
20760 Reloaded,
20761 CursorShapeChanged,
20762 PushedToNavHistory {
20763 anchor: Anchor,
20764 is_deactivate: bool,
20765 },
20766}
20767
20768impl EventEmitter<EditorEvent> for Editor {}
20769
20770impl Focusable for Editor {
20771 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20772 self.focus_handle.clone()
20773 }
20774}
20775
20776impl Render for Editor {
20777 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20778 let settings = ThemeSettings::get_global(cx);
20779
20780 let mut text_style = match self.mode {
20781 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20782 color: cx.theme().colors().editor_foreground,
20783 font_family: settings.ui_font.family.clone(),
20784 font_features: settings.ui_font.features.clone(),
20785 font_fallbacks: settings.ui_font.fallbacks.clone(),
20786 font_size: rems(0.875).into(),
20787 font_weight: settings.ui_font.weight,
20788 line_height: relative(settings.buffer_line_height.value()),
20789 ..Default::default()
20790 },
20791 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20792 color: cx.theme().colors().editor_foreground,
20793 font_family: settings.buffer_font.family.clone(),
20794 font_features: settings.buffer_font.features.clone(),
20795 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20796 font_size: settings.buffer_font_size(cx).into(),
20797 font_weight: settings.buffer_font.weight,
20798 line_height: relative(settings.buffer_line_height.value()),
20799 ..Default::default()
20800 },
20801 };
20802 if let Some(text_style_refinement) = &self.text_style_refinement {
20803 text_style.refine(text_style_refinement)
20804 }
20805
20806 let background = match self.mode {
20807 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20808 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20809 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20810 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20811 };
20812
20813 EditorElement::new(
20814 &cx.entity(),
20815 EditorStyle {
20816 background,
20817 local_player: cx.theme().players().local(),
20818 text: text_style,
20819 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20820 syntax: cx.theme().syntax().clone(),
20821 status: cx.theme().status().clone(),
20822 inlay_hints_style: make_inlay_hints_style(cx),
20823 inline_completion_styles: make_suggestion_styles(cx),
20824 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20825 show_underlines: !self.mode.is_minimap(),
20826 },
20827 )
20828 }
20829}
20830
20831impl EntityInputHandler for Editor {
20832 fn text_for_range(
20833 &mut self,
20834 range_utf16: Range<usize>,
20835 adjusted_range: &mut Option<Range<usize>>,
20836 _: &mut Window,
20837 cx: &mut Context<Self>,
20838 ) -> Option<String> {
20839 let snapshot = self.buffer.read(cx).read(cx);
20840 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20841 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20842 if (start.0..end.0) != range_utf16 {
20843 adjusted_range.replace(start.0..end.0);
20844 }
20845 Some(snapshot.text_for_range(start..end).collect())
20846 }
20847
20848 fn selected_text_range(
20849 &mut self,
20850 ignore_disabled_input: bool,
20851 _: &mut Window,
20852 cx: &mut Context<Self>,
20853 ) -> Option<UTF16Selection> {
20854 // Prevent the IME menu from appearing when holding down an alphabetic key
20855 // while input is disabled.
20856 if !ignore_disabled_input && !self.input_enabled {
20857 return None;
20858 }
20859
20860 let selection = self.selections.newest::<OffsetUtf16>(cx);
20861 let range = selection.range();
20862
20863 Some(UTF16Selection {
20864 range: range.start.0..range.end.0,
20865 reversed: selection.reversed,
20866 })
20867 }
20868
20869 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20870 let snapshot = self.buffer.read(cx).read(cx);
20871 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20872 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20873 }
20874
20875 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20876 self.clear_highlights::<InputComposition>(cx);
20877 self.ime_transaction.take();
20878 }
20879
20880 fn replace_text_in_range(
20881 &mut self,
20882 range_utf16: Option<Range<usize>>,
20883 text: &str,
20884 window: &mut Window,
20885 cx: &mut Context<Self>,
20886 ) {
20887 if !self.input_enabled {
20888 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20889 return;
20890 }
20891
20892 self.transact(window, cx, |this, window, cx| {
20893 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20894 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20895 Some(this.selection_replacement_ranges(range_utf16, cx))
20896 } else {
20897 this.marked_text_ranges(cx)
20898 };
20899
20900 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20901 let newest_selection_id = this.selections.newest_anchor().id;
20902 this.selections
20903 .all::<OffsetUtf16>(cx)
20904 .iter()
20905 .zip(ranges_to_replace.iter())
20906 .find_map(|(selection, range)| {
20907 if selection.id == newest_selection_id {
20908 Some(
20909 (range.start.0 as isize - selection.head().0 as isize)
20910 ..(range.end.0 as isize - selection.head().0 as isize),
20911 )
20912 } else {
20913 None
20914 }
20915 })
20916 });
20917
20918 cx.emit(EditorEvent::InputHandled {
20919 utf16_range_to_replace: range_to_replace,
20920 text: text.into(),
20921 });
20922
20923 if let Some(new_selected_ranges) = new_selected_ranges {
20924 this.change_selections(None, window, cx, |selections| {
20925 selections.select_ranges(new_selected_ranges)
20926 });
20927 this.backspace(&Default::default(), window, cx);
20928 }
20929
20930 this.handle_input(text, window, cx);
20931 });
20932
20933 if let Some(transaction) = self.ime_transaction {
20934 self.buffer.update(cx, |buffer, cx| {
20935 buffer.group_until_transaction(transaction, cx);
20936 });
20937 }
20938
20939 self.unmark_text(window, cx);
20940 }
20941
20942 fn replace_and_mark_text_in_range(
20943 &mut self,
20944 range_utf16: Option<Range<usize>>,
20945 text: &str,
20946 new_selected_range_utf16: Option<Range<usize>>,
20947 window: &mut Window,
20948 cx: &mut Context<Self>,
20949 ) {
20950 if !self.input_enabled {
20951 return;
20952 }
20953
20954 let transaction = self.transact(window, cx, |this, window, cx| {
20955 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20956 let snapshot = this.buffer.read(cx).read(cx);
20957 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20958 for marked_range in &mut marked_ranges {
20959 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20960 marked_range.start.0 += relative_range_utf16.start;
20961 marked_range.start =
20962 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20963 marked_range.end =
20964 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20965 }
20966 }
20967 Some(marked_ranges)
20968 } else if let Some(range_utf16) = range_utf16 {
20969 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20970 Some(this.selection_replacement_ranges(range_utf16, cx))
20971 } else {
20972 None
20973 };
20974
20975 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20976 let newest_selection_id = this.selections.newest_anchor().id;
20977 this.selections
20978 .all::<OffsetUtf16>(cx)
20979 .iter()
20980 .zip(ranges_to_replace.iter())
20981 .find_map(|(selection, range)| {
20982 if selection.id == newest_selection_id {
20983 Some(
20984 (range.start.0 as isize - selection.head().0 as isize)
20985 ..(range.end.0 as isize - selection.head().0 as isize),
20986 )
20987 } else {
20988 None
20989 }
20990 })
20991 });
20992
20993 cx.emit(EditorEvent::InputHandled {
20994 utf16_range_to_replace: range_to_replace,
20995 text: text.into(),
20996 });
20997
20998 if let Some(ranges) = ranges_to_replace {
20999 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
21000 }
21001
21002 let marked_ranges = {
21003 let snapshot = this.buffer.read(cx).read(cx);
21004 this.selections
21005 .disjoint_anchors()
21006 .iter()
21007 .map(|selection| {
21008 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
21009 })
21010 .collect::<Vec<_>>()
21011 };
21012
21013 if text.is_empty() {
21014 this.unmark_text(window, cx);
21015 } else {
21016 this.highlight_text::<InputComposition>(
21017 marked_ranges.clone(),
21018 HighlightStyle {
21019 underline: Some(UnderlineStyle {
21020 thickness: px(1.),
21021 color: None,
21022 wavy: false,
21023 }),
21024 ..Default::default()
21025 },
21026 cx,
21027 );
21028 }
21029
21030 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21031 let use_autoclose = this.use_autoclose;
21032 let use_auto_surround = this.use_auto_surround;
21033 this.set_use_autoclose(false);
21034 this.set_use_auto_surround(false);
21035 this.handle_input(text, window, cx);
21036 this.set_use_autoclose(use_autoclose);
21037 this.set_use_auto_surround(use_auto_surround);
21038
21039 if let Some(new_selected_range) = new_selected_range_utf16 {
21040 let snapshot = this.buffer.read(cx).read(cx);
21041 let new_selected_ranges = marked_ranges
21042 .into_iter()
21043 .map(|marked_range| {
21044 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21045 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21046 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21047 snapshot.clip_offset_utf16(new_start, Bias::Left)
21048 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21049 })
21050 .collect::<Vec<_>>();
21051
21052 drop(snapshot);
21053 this.change_selections(None, window, cx, |selections| {
21054 selections.select_ranges(new_selected_ranges)
21055 });
21056 }
21057 });
21058
21059 self.ime_transaction = self.ime_transaction.or(transaction);
21060 if let Some(transaction) = self.ime_transaction {
21061 self.buffer.update(cx, |buffer, cx| {
21062 buffer.group_until_transaction(transaction, cx);
21063 });
21064 }
21065
21066 if self.text_highlights::<InputComposition>(cx).is_none() {
21067 self.ime_transaction.take();
21068 }
21069 }
21070
21071 fn bounds_for_range(
21072 &mut self,
21073 range_utf16: Range<usize>,
21074 element_bounds: gpui::Bounds<Pixels>,
21075 window: &mut Window,
21076 cx: &mut Context<Self>,
21077 ) -> Option<gpui::Bounds<Pixels>> {
21078 let text_layout_details = self.text_layout_details(window);
21079 let gpui::Size {
21080 width: em_width,
21081 height: line_height,
21082 } = self.character_size(window);
21083
21084 let snapshot = self.snapshot(window, cx);
21085 let scroll_position = snapshot.scroll_position();
21086 let scroll_left = scroll_position.x * em_width;
21087
21088 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21089 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21090 + self.gutter_dimensions.width
21091 + self.gutter_dimensions.margin;
21092 let y = line_height * (start.row().as_f32() - scroll_position.y);
21093
21094 Some(Bounds {
21095 origin: element_bounds.origin + point(x, y),
21096 size: size(em_width, line_height),
21097 })
21098 }
21099
21100 fn character_index_for_point(
21101 &mut self,
21102 point: gpui::Point<Pixels>,
21103 _window: &mut Window,
21104 _cx: &mut Context<Self>,
21105 ) -> Option<usize> {
21106 let position_map = self.last_position_map.as_ref()?;
21107 if !position_map.text_hitbox.contains(&point) {
21108 return None;
21109 }
21110 let display_point = position_map.point_for_position(point).previous_valid;
21111 let anchor = position_map
21112 .snapshot
21113 .display_point_to_anchor(display_point, Bias::Left);
21114 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21115 Some(utf16_offset.0)
21116 }
21117}
21118
21119trait SelectionExt {
21120 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21121 fn spanned_rows(
21122 &self,
21123 include_end_if_at_line_start: bool,
21124 map: &DisplaySnapshot,
21125 ) -> Range<MultiBufferRow>;
21126}
21127
21128impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21129 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21130 let start = self
21131 .start
21132 .to_point(&map.buffer_snapshot)
21133 .to_display_point(map);
21134 let end = self
21135 .end
21136 .to_point(&map.buffer_snapshot)
21137 .to_display_point(map);
21138 if self.reversed {
21139 end..start
21140 } else {
21141 start..end
21142 }
21143 }
21144
21145 fn spanned_rows(
21146 &self,
21147 include_end_if_at_line_start: bool,
21148 map: &DisplaySnapshot,
21149 ) -> Range<MultiBufferRow> {
21150 let start = self.start.to_point(&map.buffer_snapshot);
21151 let mut end = self.end.to_point(&map.buffer_snapshot);
21152 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21153 end.row -= 1;
21154 }
21155
21156 let buffer_start = map.prev_line_boundary(start).0;
21157 let buffer_end = map.next_line_boundary(end).0;
21158 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21159 }
21160}
21161
21162impl<T: InvalidationRegion> InvalidationStack<T> {
21163 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21164 where
21165 S: Clone + ToOffset,
21166 {
21167 while let Some(region) = self.last() {
21168 let all_selections_inside_invalidation_ranges =
21169 if selections.len() == region.ranges().len() {
21170 selections
21171 .iter()
21172 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21173 .all(|(selection, invalidation_range)| {
21174 let head = selection.head().to_offset(buffer);
21175 invalidation_range.start <= head && invalidation_range.end >= head
21176 })
21177 } else {
21178 false
21179 };
21180
21181 if all_selections_inside_invalidation_ranges {
21182 break;
21183 } else {
21184 self.pop();
21185 }
21186 }
21187 }
21188}
21189
21190impl<T> Default for InvalidationStack<T> {
21191 fn default() -> Self {
21192 Self(Default::default())
21193 }
21194}
21195
21196impl<T> Deref for InvalidationStack<T> {
21197 type Target = Vec<T>;
21198
21199 fn deref(&self) -> &Self::Target {
21200 &self.0
21201 }
21202}
21203
21204impl<T> DerefMut for InvalidationStack<T> {
21205 fn deref_mut(&mut self) -> &mut Self::Target {
21206 &mut self.0
21207 }
21208}
21209
21210impl InvalidationRegion for SnippetState {
21211 fn ranges(&self) -> &[Range<Anchor>] {
21212 &self.ranges[self.active_index]
21213 }
21214}
21215
21216fn inline_completion_edit_text(
21217 current_snapshot: &BufferSnapshot,
21218 edits: &[(Range<Anchor>, String)],
21219 edit_preview: &EditPreview,
21220 include_deletions: bool,
21221 cx: &App,
21222) -> HighlightedText {
21223 let edits = edits
21224 .iter()
21225 .map(|(anchor, text)| {
21226 (
21227 anchor.start.text_anchor..anchor.end.text_anchor,
21228 text.clone(),
21229 )
21230 })
21231 .collect::<Vec<_>>();
21232
21233 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21234}
21235
21236pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21237 match severity {
21238 lsp::DiagnosticSeverity::ERROR => colors.error,
21239 lsp::DiagnosticSeverity::WARNING => colors.warning,
21240 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21241 lsp::DiagnosticSeverity::HINT => colors.info,
21242 _ => colors.ignored,
21243 }
21244}
21245
21246pub fn styled_runs_for_code_label<'a>(
21247 label: &'a CodeLabel,
21248 syntax_theme: &'a theme::SyntaxTheme,
21249) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21250 let fade_out = HighlightStyle {
21251 fade_out: Some(0.35),
21252 ..Default::default()
21253 };
21254
21255 let mut prev_end = label.filter_range.end;
21256 label
21257 .runs
21258 .iter()
21259 .enumerate()
21260 .flat_map(move |(ix, (range, highlight_id))| {
21261 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21262 style
21263 } else {
21264 return Default::default();
21265 };
21266 let mut muted_style = style;
21267 muted_style.highlight(fade_out);
21268
21269 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21270 if range.start >= label.filter_range.end {
21271 if range.start > prev_end {
21272 runs.push((prev_end..range.start, fade_out));
21273 }
21274 runs.push((range.clone(), muted_style));
21275 } else if range.end <= label.filter_range.end {
21276 runs.push((range.clone(), style));
21277 } else {
21278 runs.push((range.start..label.filter_range.end, style));
21279 runs.push((label.filter_range.end..range.end, muted_style));
21280 }
21281 prev_end = cmp::max(prev_end, range.end);
21282
21283 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21284 runs.push((prev_end..label.text.len(), fade_out));
21285 }
21286
21287 runs
21288 })
21289}
21290
21291pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21292 let mut prev_index = 0;
21293 let mut prev_codepoint: Option<char> = None;
21294 text.char_indices()
21295 .chain([(text.len(), '\0')])
21296 .filter_map(move |(index, codepoint)| {
21297 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21298 let is_boundary = index == text.len()
21299 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21300 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21301 if is_boundary {
21302 let chunk = &text[prev_index..index];
21303 prev_index = index;
21304 Some(chunk)
21305 } else {
21306 None
21307 }
21308 })
21309}
21310
21311pub trait RangeToAnchorExt: Sized {
21312 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21313
21314 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21315 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21316 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21317 }
21318}
21319
21320impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21321 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21322 let start_offset = self.start.to_offset(snapshot);
21323 let end_offset = self.end.to_offset(snapshot);
21324 if start_offset == end_offset {
21325 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21326 } else {
21327 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21328 }
21329 }
21330}
21331
21332pub trait RowExt {
21333 fn as_f32(&self) -> f32;
21334
21335 fn next_row(&self) -> Self;
21336
21337 fn previous_row(&self) -> Self;
21338
21339 fn minus(&self, other: Self) -> u32;
21340}
21341
21342impl RowExt for DisplayRow {
21343 fn as_f32(&self) -> f32 {
21344 self.0 as f32
21345 }
21346
21347 fn next_row(&self) -> Self {
21348 Self(self.0 + 1)
21349 }
21350
21351 fn previous_row(&self) -> Self {
21352 Self(self.0.saturating_sub(1))
21353 }
21354
21355 fn minus(&self, other: Self) -> u32 {
21356 self.0 - other.0
21357 }
21358}
21359
21360impl RowExt for MultiBufferRow {
21361 fn as_f32(&self) -> f32 {
21362 self.0 as f32
21363 }
21364
21365 fn next_row(&self) -> Self {
21366 Self(self.0 + 1)
21367 }
21368
21369 fn previous_row(&self) -> Self {
21370 Self(self.0.saturating_sub(1))
21371 }
21372
21373 fn minus(&self, other: Self) -> u32 {
21374 self.0 - other.0
21375 }
21376}
21377
21378trait RowRangeExt {
21379 type Row;
21380
21381 fn len(&self) -> usize;
21382
21383 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21384}
21385
21386impl RowRangeExt for Range<MultiBufferRow> {
21387 type Row = MultiBufferRow;
21388
21389 fn len(&self) -> usize {
21390 (self.end.0 - self.start.0) as usize
21391 }
21392
21393 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21394 (self.start.0..self.end.0).map(MultiBufferRow)
21395 }
21396}
21397
21398impl RowRangeExt for Range<DisplayRow> {
21399 type Row = DisplayRow;
21400
21401 fn len(&self) -> usize {
21402 (self.end.0 - self.start.0) as usize
21403 }
21404
21405 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21406 (self.start.0..self.end.0).map(DisplayRow)
21407 }
21408}
21409
21410/// If select range has more than one line, we
21411/// just point the cursor to range.start.
21412fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21413 if range.start.row == range.end.row {
21414 range
21415 } else {
21416 range.start..range.start
21417 }
21418}
21419pub struct KillRing(ClipboardItem);
21420impl Global for KillRing {}
21421
21422const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21423
21424enum BreakpointPromptEditAction {
21425 Log,
21426 Condition,
21427 HitCondition,
21428}
21429
21430struct BreakpointPromptEditor {
21431 pub(crate) prompt: Entity<Editor>,
21432 editor: WeakEntity<Editor>,
21433 breakpoint_anchor: Anchor,
21434 breakpoint: Breakpoint,
21435 edit_action: BreakpointPromptEditAction,
21436 block_ids: HashSet<CustomBlockId>,
21437 editor_margins: Arc<Mutex<EditorMargins>>,
21438 _subscriptions: Vec<Subscription>,
21439}
21440
21441impl BreakpointPromptEditor {
21442 const MAX_LINES: u8 = 4;
21443
21444 fn new(
21445 editor: WeakEntity<Editor>,
21446 breakpoint_anchor: Anchor,
21447 breakpoint: Breakpoint,
21448 edit_action: BreakpointPromptEditAction,
21449 window: &mut Window,
21450 cx: &mut Context<Self>,
21451 ) -> Self {
21452 let base_text = match edit_action {
21453 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21454 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21455 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21456 }
21457 .map(|msg| msg.to_string())
21458 .unwrap_or_default();
21459
21460 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21461 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21462
21463 let prompt = cx.new(|cx| {
21464 let mut prompt = Editor::new(
21465 EditorMode::AutoHeight {
21466 max_lines: Self::MAX_LINES as usize,
21467 },
21468 buffer,
21469 None,
21470 window,
21471 cx,
21472 );
21473 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21474 prompt.set_show_cursor_when_unfocused(false, cx);
21475 prompt.set_placeholder_text(
21476 match edit_action {
21477 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21478 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21479 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21480 },
21481 cx,
21482 );
21483
21484 prompt
21485 });
21486
21487 Self {
21488 prompt,
21489 editor,
21490 breakpoint_anchor,
21491 breakpoint,
21492 edit_action,
21493 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21494 block_ids: Default::default(),
21495 _subscriptions: vec![],
21496 }
21497 }
21498
21499 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21500 self.block_ids.extend(block_ids)
21501 }
21502
21503 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21504 if let Some(editor) = self.editor.upgrade() {
21505 let message = self
21506 .prompt
21507 .read(cx)
21508 .buffer
21509 .read(cx)
21510 .as_singleton()
21511 .expect("A multi buffer in breakpoint prompt isn't possible")
21512 .read(cx)
21513 .as_rope()
21514 .to_string();
21515
21516 editor.update(cx, |editor, cx| {
21517 editor.edit_breakpoint_at_anchor(
21518 self.breakpoint_anchor,
21519 self.breakpoint.clone(),
21520 match self.edit_action {
21521 BreakpointPromptEditAction::Log => {
21522 BreakpointEditAction::EditLogMessage(message.into())
21523 }
21524 BreakpointPromptEditAction::Condition => {
21525 BreakpointEditAction::EditCondition(message.into())
21526 }
21527 BreakpointPromptEditAction::HitCondition => {
21528 BreakpointEditAction::EditHitCondition(message.into())
21529 }
21530 },
21531 cx,
21532 );
21533
21534 editor.remove_blocks(self.block_ids.clone(), None, cx);
21535 cx.focus_self(window);
21536 });
21537 }
21538 }
21539
21540 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21541 self.editor
21542 .update(cx, |editor, cx| {
21543 editor.remove_blocks(self.block_ids.clone(), None, cx);
21544 window.focus(&editor.focus_handle);
21545 })
21546 .log_err();
21547 }
21548
21549 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21550 let settings = ThemeSettings::get_global(cx);
21551 let text_style = TextStyle {
21552 color: if self.prompt.read(cx).read_only(cx) {
21553 cx.theme().colors().text_disabled
21554 } else {
21555 cx.theme().colors().text
21556 },
21557 font_family: settings.buffer_font.family.clone(),
21558 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21559 font_size: settings.buffer_font_size(cx).into(),
21560 font_weight: settings.buffer_font.weight,
21561 line_height: relative(settings.buffer_line_height.value()),
21562 ..Default::default()
21563 };
21564 EditorElement::new(
21565 &self.prompt,
21566 EditorStyle {
21567 background: cx.theme().colors().editor_background,
21568 local_player: cx.theme().players().local(),
21569 text: text_style,
21570 ..Default::default()
21571 },
21572 )
21573 }
21574}
21575
21576impl Render for BreakpointPromptEditor {
21577 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21578 let editor_margins = *self.editor_margins.lock();
21579 let gutter_dimensions = editor_margins.gutter;
21580 h_flex()
21581 .key_context("Editor")
21582 .bg(cx.theme().colors().editor_background)
21583 .border_y_1()
21584 .border_color(cx.theme().status().info_border)
21585 .size_full()
21586 .py(window.line_height() / 2.5)
21587 .on_action(cx.listener(Self::confirm))
21588 .on_action(cx.listener(Self::cancel))
21589 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21590 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21591 }
21592}
21593
21594impl Focusable for BreakpointPromptEditor {
21595 fn focus_handle(&self, cx: &App) -> FocusHandle {
21596 self.prompt.focus_handle(cx)
21597 }
21598}
21599
21600fn all_edits_insertions_or_deletions(
21601 edits: &Vec<(Range<Anchor>, String)>,
21602 snapshot: &MultiBufferSnapshot,
21603) -> bool {
21604 let mut all_insertions = true;
21605 let mut all_deletions = true;
21606
21607 for (range, new_text) in edits.iter() {
21608 let range_is_empty = range.to_offset(&snapshot).is_empty();
21609 let text_is_empty = new_text.is_empty();
21610
21611 if range_is_empty != text_is_empty {
21612 if range_is_empty {
21613 all_deletions = false;
21614 } else {
21615 all_insertions = false;
21616 }
21617 } else {
21618 return false;
21619 }
21620
21621 if !all_insertions && !all_deletions {
21622 return false;
21623 }
21624 }
21625 all_insertions || all_deletions
21626}
21627
21628struct MissingEditPredictionKeybindingTooltip;
21629
21630impl Render for MissingEditPredictionKeybindingTooltip {
21631 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21632 ui::tooltip_container(window, cx, |container, _, cx| {
21633 container
21634 .flex_shrink_0()
21635 .max_w_80()
21636 .min_h(rems_from_px(124.))
21637 .justify_between()
21638 .child(
21639 v_flex()
21640 .flex_1()
21641 .text_ui_sm(cx)
21642 .child(Label::new("Conflict with Accept Keybinding"))
21643 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21644 )
21645 .child(
21646 h_flex()
21647 .pb_1()
21648 .gap_1()
21649 .items_end()
21650 .w_full()
21651 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21652 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21653 }))
21654 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21655 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21656 })),
21657 )
21658 })
21659 }
21660}
21661
21662#[derive(Debug, Clone, Copy, PartialEq)]
21663pub struct LineHighlight {
21664 pub background: Background,
21665 pub border: Option<gpui::Hsla>,
21666 pub include_gutter: bool,
21667 pub type_id: Option<TypeId>,
21668}
21669
21670fn render_diff_hunk_controls(
21671 row: u32,
21672 status: &DiffHunkStatus,
21673 hunk_range: Range<Anchor>,
21674 is_created_file: bool,
21675 line_height: Pixels,
21676 editor: &Entity<Editor>,
21677 _window: &mut Window,
21678 cx: &mut App,
21679) -> AnyElement {
21680 h_flex()
21681 .h(line_height)
21682 .mr_1()
21683 .gap_1()
21684 .px_0p5()
21685 .pb_1()
21686 .border_x_1()
21687 .border_b_1()
21688 .border_color(cx.theme().colors().border_variant)
21689 .rounded_b_lg()
21690 .bg(cx.theme().colors().editor_background)
21691 .gap_1()
21692 .occlude()
21693 .shadow_md()
21694 .child(if status.has_secondary_hunk() {
21695 Button::new(("stage", row as u64), "Stage")
21696 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21697 .tooltip({
21698 let focus_handle = editor.focus_handle(cx);
21699 move |window, cx| {
21700 Tooltip::for_action_in(
21701 "Stage Hunk",
21702 &::git::ToggleStaged,
21703 &focus_handle,
21704 window,
21705 cx,
21706 )
21707 }
21708 })
21709 .on_click({
21710 let editor = editor.clone();
21711 move |_event, _window, cx| {
21712 editor.update(cx, |editor, cx| {
21713 editor.stage_or_unstage_diff_hunks(
21714 true,
21715 vec![hunk_range.start..hunk_range.start],
21716 cx,
21717 );
21718 });
21719 }
21720 })
21721 } else {
21722 Button::new(("unstage", row as u64), "Unstage")
21723 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21724 .tooltip({
21725 let focus_handle = editor.focus_handle(cx);
21726 move |window, cx| {
21727 Tooltip::for_action_in(
21728 "Unstage Hunk",
21729 &::git::ToggleStaged,
21730 &focus_handle,
21731 window,
21732 cx,
21733 )
21734 }
21735 })
21736 .on_click({
21737 let editor = editor.clone();
21738 move |_event, _window, cx| {
21739 editor.update(cx, |editor, cx| {
21740 editor.stage_or_unstage_diff_hunks(
21741 false,
21742 vec![hunk_range.start..hunk_range.start],
21743 cx,
21744 );
21745 });
21746 }
21747 })
21748 })
21749 .child(
21750 Button::new(("restore", row as u64), "Restore")
21751 .tooltip({
21752 let focus_handle = editor.focus_handle(cx);
21753 move |window, cx| {
21754 Tooltip::for_action_in(
21755 "Restore Hunk",
21756 &::git::Restore,
21757 &focus_handle,
21758 window,
21759 cx,
21760 )
21761 }
21762 })
21763 .on_click({
21764 let editor = editor.clone();
21765 move |_event, window, cx| {
21766 editor.update(cx, |editor, cx| {
21767 let snapshot = editor.snapshot(window, cx);
21768 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21769 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21770 });
21771 }
21772 })
21773 .disabled(is_created_file),
21774 )
21775 .when(
21776 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21777 |el| {
21778 el.child(
21779 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21780 .shape(IconButtonShape::Square)
21781 .icon_size(IconSize::Small)
21782 // .disabled(!has_multiple_hunks)
21783 .tooltip({
21784 let focus_handle = editor.focus_handle(cx);
21785 move |window, cx| {
21786 Tooltip::for_action_in(
21787 "Next Hunk",
21788 &GoToHunk,
21789 &focus_handle,
21790 window,
21791 cx,
21792 )
21793 }
21794 })
21795 .on_click({
21796 let editor = editor.clone();
21797 move |_event, window, cx| {
21798 editor.update(cx, |editor, cx| {
21799 let snapshot = editor.snapshot(window, cx);
21800 let position =
21801 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21802 editor.go_to_hunk_before_or_after_position(
21803 &snapshot,
21804 position,
21805 Direction::Next,
21806 window,
21807 cx,
21808 );
21809 editor.expand_selected_diff_hunks(cx);
21810 });
21811 }
21812 }),
21813 )
21814 .child(
21815 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21816 .shape(IconButtonShape::Square)
21817 .icon_size(IconSize::Small)
21818 // .disabled(!has_multiple_hunks)
21819 .tooltip({
21820 let focus_handle = editor.focus_handle(cx);
21821 move |window, cx| {
21822 Tooltip::for_action_in(
21823 "Previous Hunk",
21824 &GoToPreviousHunk,
21825 &focus_handle,
21826 window,
21827 cx,
21828 )
21829 }
21830 })
21831 .on_click({
21832 let editor = editor.clone();
21833 move |_event, window, cx| {
21834 editor.update(cx, |editor, cx| {
21835 let snapshot = editor.snapshot(window, cx);
21836 let point =
21837 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21838 editor.go_to_hunk_before_or_after_position(
21839 &snapshot,
21840 point,
21841 Direction::Prev,
21842 window,
21843 cx,
21844 );
21845 editor.expand_selected_diff_hunks(cx);
21846 });
21847 }
21848 }),
21849 )
21850 },
21851 )
21852 .into_any_element()
21853}