1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64pub use editor_settings::{
65 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
66 ShowScrollbar,
67};
68use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
111 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
112 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 BreakpointWithPosition, ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
129 BreakpointStoreEvent,
130 },
131 session::{Session, SessionEvent},
132 },
133 project_settings::DiagnosticSeverity,
134};
135
136pub use git::blame::BlameRenderer;
137pub use proposed_changes_editor::{
138 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
139};
140use smallvec::smallvec;
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::SmallVec;
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::hover_links::{find_url, find_url_from_range};
215use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
252 alt: true,
253 shift: true,
254 control: false,
255 platform: false,
256 function: false,
257};
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled(bool),
720}
721
722impl MinimapVisibility {
723 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
724 if mode.is_full() {
725 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
726 } else {
727 Self::Disabled
728 }
729 }
730
731 fn disabled(&self) -> bool {
732 match *self {
733 Self::Disabled => true,
734 _ => false,
735 }
736 }
737
738 fn visible(&self) -> bool {
739 match *self {
740 Self::Enabled(visible) => visible,
741 _ => false,
742 }
743 }
744
745 fn toggle_visibility(&self) -> Self {
746 match *self {
747 Self::Enabled(visible) => Self::Enabled(!visible),
748 Self::Disabled => Self::Disabled,
749 }
750 }
751}
752
753#[derive(Clone, Debug)]
754struct RunnableTasks {
755 templates: Vec<(TaskSourceKind, TaskTemplate)>,
756 offset: multi_buffer::Anchor,
757 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
758 column: u32,
759 // Values of all named captures, including those starting with '_'
760 extra_variables: HashMap<String, String>,
761 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
762 context_range: Range<BufferOffset>,
763}
764
765impl RunnableTasks {
766 fn resolve<'a>(
767 &'a self,
768 cx: &'a task::TaskContext,
769 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
770 self.templates.iter().filter_map(|(kind, template)| {
771 template
772 .resolve_task(&kind.to_id_base(), cx)
773 .map(|task| (kind.clone(), task))
774 })
775 }
776}
777
778#[derive(Clone)]
779struct ResolvedTasks {
780 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
781 position: Anchor,
782}
783
784#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
785struct BufferOffset(usize);
786
787// Addons allow storing per-editor state in other crates (e.g. Vim)
788pub trait Addon: 'static {
789 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
790
791 fn render_buffer_header_controls(
792 &self,
793 _: &ExcerptInfo,
794 _: &Window,
795 _: &App,
796 ) -> Option<AnyElement> {
797 None
798 }
799
800 fn to_any(&self) -> &dyn std::any::Any;
801
802 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
803 None
804 }
805}
806
807/// A set of caret positions, registered when the editor was edited.
808pub struct ChangeList {
809 changes: Vec<Vec<Anchor>>,
810 /// Currently "selected" change.
811 position: Option<usize>,
812}
813
814impl ChangeList {
815 pub fn new() -> Self {
816 Self {
817 changes: Vec::new(),
818 position: None,
819 }
820 }
821
822 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
823 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
824 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
825 if self.changes.is_empty() {
826 return None;
827 }
828
829 let prev = self.position.unwrap_or(self.changes.len());
830 let next = if direction == Direction::Prev {
831 prev.saturating_sub(count)
832 } else {
833 (prev + count).min(self.changes.len() - 1)
834 };
835 self.position = Some(next);
836 self.changes.get(next).map(|anchors| anchors.as_slice())
837 }
838
839 /// Adds a new change to the list, resetting the change list position.
840 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
841 self.position.take();
842 if pop_state {
843 self.changes.pop();
844 }
845 self.changes.push(new_positions.clone());
846 }
847
848 pub fn last(&self) -> Option<&[Anchor]> {
849 self.changes.last().map(|anchors| anchors.as_slice())
850 }
851}
852
853#[derive(Clone)]
854struct InlineBlamePopoverState {
855 scroll_handle: ScrollHandle,
856 commit_message: Option<ParsedCommitMessage>,
857 markdown: Entity<Markdown>,
858}
859
860struct InlineBlamePopover {
861 position: gpui::Point<Pixels>,
862 show_task: Option<Task<()>>,
863 hide_task: Option<Task<()>>,
864 popover_bounds: Option<Bounds<Pixels>>,
865 popover_state: InlineBlamePopoverState,
866}
867
868/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
869/// a breakpoint on them.
870#[derive(Clone, Copy, Debug)]
871struct PhantomBreakpointIndicator {
872 display_row: DisplayRow,
873 /// There's a small debounce between hovering over the line and showing the indicator.
874 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
875 is_active: bool,
876 collides_with_existing_breakpoint: bool,
877}
878/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
879///
880/// See the [module level documentation](self) for more information.
881pub struct Editor {
882 focus_handle: FocusHandle,
883 last_focused_descendant: Option<WeakFocusHandle>,
884 /// The text buffer being edited
885 buffer: Entity<MultiBuffer>,
886 /// Map of how text in the buffer should be displayed.
887 /// Handles soft wraps, folds, fake inlay text insertions, etc.
888 pub display_map: Entity<DisplayMap>,
889 pub selections: SelectionsCollection,
890 pub scroll_manager: ScrollManager,
891 /// When inline assist editors are linked, they all render cursors because
892 /// typing enters text into each of them, even the ones that aren't focused.
893 pub(crate) show_cursor_when_unfocused: bool,
894 columnar_selection_tail: Option<Anchor>,
895 add_selections_state: Option<AddSelectionsState>,
896 select_next_state: Option<SelectNextState>,
897 select_prev_state: Option<SelectNextState>,
898 selection_history: SelectionHistory,
899 autoclose_regions: Vec<AutocloseRegion>,
900 snippet_stack: InvalidationStack<SnippetState>,
901 select_syntax_node_history: SelectSyntaxNodeHistory,
902 ime_transaction: Option<TransactionId>,
903 pub diagnostics_max_severity: DiagnosticSeverity,
904 active_diagnostics: ActiveDiagnostic,
905 show_inline_diagnostics: bool,
906 inline_diagnostics_update: Task<()>,
907 inline_diagnostics_enabled: bool,
908 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
909 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
910 hard_wrap: Option<usize>,
911
912 // TODO: make this a access method
913 pub project: Option<Entity<Project>>,
914 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
915 completion_provider: Option<Box<dyn CompletionProvider>>,
916 collaboration_hub: Option<Box<dyn CollaborationHub>>,
917 blink_manager: Entity<BlinkManager>,
918 show_cursor_names: bool,
919 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
920 pub show_local_selections: bool,
921 mode: EditorMode,
922 show_breadcrumbs: bool,
923 show_gutter: bool,
924 show_scrollbars: bool,
925 minimap_visibility: MinimapVisibility,
926 disable_expand_excerpt_buttons: bool,
927 show_line_numbers: Option<bool>,
928 use_relative_line_numbers: Option<bool>,
929 show_git_diff_gutter: Option<bool>,
930 show_code_actions: Option<bool>,
931 show_runnables: Option<bool>,
932 show_breakpoints: Option<bool>,
933 show_wrap_guides: Option<bool>,
934 show_indent_guides: Option<bool>,
935 placeholder_text: Option<Arc<str>>,
936 highlight_order: usize,
937 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
938 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
939 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
940 scrollbar_marker_state: ScrollbarMarkerState,
941 active_indent_guides_state: ActiveIndentGuidesState,
942 nav_history: Option<ItemNavHistory>,
943 context_menu: RefCell<Option<CodeContextMenu>>,
944 context_menu_options: Option<ContextMenuOptions>,
945 mouse_context_menu: Option<MouseContextMenu>,
946 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
947 inline_blame_popover: Option<InlineBlamePopover>,
948 signature_help_state: SignatureHelpState,
949 auto_signature_help: Option<bool>,
950 find_all_references_task_sources: Vec<Anchor>,
951 next_completion_id: CompletionId,
952 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
953 code_actions_task: Option<Task<Result<()>>>,
954 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
955 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 document_highlights_task: Option<Task<()>>,
957 linked_editing_range_task: Option<Task<Option<()>>>,
958 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
959 pending_rename: Option<RenameState>,
960 searchable: bool,
961 cursor_shape: CursorShape,
962 current_line_highlight: Option<CurrentLineHighlight>,
963 collapse_matches: bool,
964 autoindent_mode: Option<AutoindentMode>,
965 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
966 input_enabled: bool,
967 use_modal_editing: bool,
968 read_only: bool,
969 leader_id: Option<CollaboratorId>,
970 remote_id: Option<ViewId>,
971 pub hover_state: HoverState,
972 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
973 gutter_hovered: bool,
974 hovered_link_state: Option<HoveredLinkState>,
975 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
976 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
977 active_inline_completion: Option<InlineCompletionState>,
978 /// Used to prevent flickering as the user types while the menu is open
979 stale_inline_completion_in_menu: Option<InlineCompletionState>,
980 edit_prediction_settings: EditPredictionSettings,
981 inline_completions_hidden_for_vim_mode: bool,
982 show_inline_completions_override: Option<bool>,
983 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
984 edit_prediction_preview: EditPredictionPreview,
985 edit_prediction_indent_conflict: bool,
986 edit_prediction_requires_modifier_in_indent_conflict: bool,
987 inlay_hint_cache: InlayHintCache,
988 next_inlay_id: usize,
989 _subscriptions: Vec<Subscription>,
990 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
991 gutter_dimensions: GutterDimensions,
992 style: Option<EditorStyle>,
993 text_style_refinement: Option<TextStyleRefinement>,
994 next_editor_action_id: EditorActionId,
995 editor_actions:
996 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
997 use_autoclose: bool,
998 use_auto_surround: bool,
999 auto_replace_emoji_shortcode: bool,
1000 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1001 show_git_blame_gutter: bool,
1002 show_git_blame_inline: bool,
1003 show_git_blame_inline_delay_task: Option<Task<()>>,
1004 git_blame_inline_enabled: bool,
1005 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1006 serialize_dirty_buffers: bool,
1007 show_selection_menu: Option<bool>,
1008 blame: Option<Entity<GitBlame>>,
1009 blame_subscription: Option<Subscription>,
1010 custom_context_menu: Option<
1011 Box<
1012 dyn 'static
1013 + Fn(
1014 &mut Self,
1015 DisplayPoint,
1016 &mut Window,
1017 &mut Context<Self>,
1018 ) -> Option<Entity<ui::ContextMenu>>,
1019 >,
1020 >,
1021 last_bounds: Option<Bounds<Pixels>>,
1022 last_position_map: Option<Rc<PositionMap>>,
1023 expect_bounds_change: Option<Bounds<Pixels>>,
1024 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1025 tasks_update_task: Option<Task<()>>,
1026 breakpoint_store: Option<Entity<BreakpointStore>>,
1027 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1028 in_project_search: bool,
1029 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1030 breadcrumb_header: Option<String>,
1031 focused_block: Option<FocusedBlock>,
1032 next_scroll_position: NextScrollCursorCenterTopBottom,
1033 addons: HashMap<TypeId, Box<dyn Addon>>,
1034 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1035 load_diff_task: Option<Shared<Task<()>>>,
1036 /// Whether we are temporarily displaying a diff other than git's
1037 temporary_diff_override: bool,
1038 selection_mark_mode: bool,
1039 toggle_fold_multiple_buffers: Task<()>,
1040 _scroll_cursor_center_top_bottom_task: Task<()>,
1041 serialize_selections: Task<()>,
1042 serialize_folds: Task<()>,
1043 mouse_cursor_hidden: bool,
1044 minimap: Option<Entity<Self>>,
1045 hide_mouse_mode: HideMouseMode,
1046 pub change_list: ChangeList,
1047 inline_value_cache: InlineValueCache,
1048}
1049
1050#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1051enum NextScrollCursorCenterTopBottom {
1052 #[default]
1053 Center,
1054 Top,
1055 Bottom,
1056}
1057
1058impl NextScrollCursorCenterTopBottom {
1059 fn next(&self) -> Self {
1060 match self {
1061 Self::Center => Self::Top,
1062 Self::Top => Self::Bottom,
1063 Self::Bottom => Self::Center,
1064 }
1065 }
1066}
1067
1068#[derive(Clone)]
1069pub struct EditorSnapshot {
1070 pub mode: EditorMode,
1071 show_gutter: bool,
1072 show_line_numbers: Option<bool>,
1073 show_git_diff_gutter: Option<bool>,
1074 show_runnables: Option<bool>,
1075 show_breakpoints: Option<bool>,
1076 git_blame_gutter_max_author_length: Option<usize>,
1077 pub display_snapshot: DisplaySnapshot,
1078 pub placeholder_text: Option<Arc<str>>,
1079 is_focused: bool,
1080 scroll_anchor: ScrollAnchor,
1081 ongoing_scroll: OngoingScroll,
1082 current_line_highlight: CurrentLineHighlight,
1083 gutter_hovered: bool,
1084}
1085
1086#[derive(Default, Debug, Clone, Copy)]
1087pub struct GutterDimensions {
1088 pub left_padding: Pixels,
1089 pub right_padding: Pixels,
1090 pub width: Pixels,
1091 pub margin: Pixels,
1092 pub git_blame_entries_width: Option<Pixels>,
1093}
1094
1095impl GutterDimensions {
1096 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1097 Self {
1098 margin: Self::default_gutter_margin(font_id, font_size, cx),
1099 ..Default::default()
1100 }
1101 }
1102
1103 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1104 -cx.text_system().descent(font_id, font_size)
1105 }
1106 /// The full width of the space taken up by the gutter.
1107 pub fn full_width(&self) -> Pixels {
1108 self.margin + self.width
1109 }
1110
1111 /// The width of the space reserved for the fold indicators,
1112 /// use alongside 'justify_end' and `gutter_width` to
1113 /// right align content with the line numbers
1114 pub fn fold_area_width(&self) -> Pixels {
1115 self.margin + self.right_padding
1116 }
1117}
1118
1119#[derive(Debug)]
1120pub struct RemoteSelection {
1121 pub replica_id: ReplicaId,
1122 pub selection: Selection<Anchor>,
1123 pub cursor_shape: CursorShape,
1124 pub collaborator_id: CollaboratorId,
1125 pub line_mode: bool,
1126 pub user_name: Option<SharedString>,
1127 pub color: PlayerColor,
1128}
1129
1130#[derive(Clone, Debug)]
1131struct SelectionHistoryEntry {
1132 selections: Arc<[Selection<Anchor>]>,
1133 select_next_state: Option<SelectNextState>,
1134 select_prev_state: Option<SelectNextState>,
1135 add_selections_state: Option<AddSelectionsState>,
1136}
1137
1138enum SelectionHistoryMode {
1139 Normal,
1140 Undoing,
1141 Redoing,
1142}
1143
1144#[derive(Clone, PartialEq, Eq, Hash)]
1145struct HoveredCursor {
1146 replica_id: u16,
1147 selection_id: usize,
1148}
1149
1150impl Default for SelectionHistoryMode {
1151 fn default() -> Self {
1152 Self::Normal
1153 }
1154}
1155
1156#[derive(Default)]
1157struct SelectionHistory {
1158 #[allow(clippy::type_complexity)]
1159 selections_by_transaction:
1160 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1161 mode: SelectionHistoryMode,
1162 undo_stack: VecDeque<SelectionHistoryEntry>,
1163 redo_stack: VecDeque<SelectionHistoryEntry>,
1164}
1165
1166impl SelectionHistory {
1167 fn insert_transaction(
1168 &mut self,
1169 transaction_id: TransactionId,
1170 selections: Arc<[Selection<Anchor>]>,
1171 ) {
1172 self.selections_by_transaction
1173 .insert(transaction_id, (selections, None));
1174 }
1175
1176 #[allow(clippy::type_complexity)]
1177 fn transaction(
1178 &self,
1179 transaction_id: TransactionId,
1180 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1181 self.selections_by_transaction.get(&transaction_id)
1182 }
1183
1184 #[allow(clippy::type_complexity)]
1185 fn transaction_mut(
1186 &mut self,
1187 transaction_id: TransactionId,
1188 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1189 self.selections_by_transaction.get_mut(&transaction_id)
1190 }
1191
1192 fn push(&mut self, entry: SelectionHistoryEntry) {
1193 if !entry.selections.is_empty() {
1194 match self.mode {
1195 SelectionHistoryMode::Normal => {
1196 self.push_undo(entry);
1197 self.redo_stack.clear();
1198 }
1199 SelectionHistoryMode::Undoing => self.push_redo(entry),
1200 SelectionHistoryMode::Redoing => self.push_undo(entry),
1201 }
1202 }
1203 }
1204
1205 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1206 if self
1207 .undo_stack
1208 .back()
1209 .map_or(true, |e| e.selections != entry.selections)
1210 {
1211 self.undo_stack.push_back(entry);
1212 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1213 self.undo_stack.pop_front();
1214 }
1215 }
1216 }
1217
1218 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1219 if self
1220 .redo_stack
1221 .back()
1222 .map_or(true, |e| e.selections != entry.selections)
1223 {
1224 self.redo_stack.push_back(entry);
1225 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1226 self.redo_stack.pop_front();
1227 }
1228 }
1229 }
1230}
1231
1232#[derive(Clone, Copy)]
1233pub struct RowHighlightOptions {
1234 pub autoscroll: bool,
1235 pub include_gutter: bool,
1236}
1237
1238impl Default for RowHighlightOptions {
1239 fn default() -> Self {
1240 Self {
1241 autoscroll: Default::default(),
1242 include_gutter: true,
1243 }
1244 }
1245}
1246
1247struct RowHighlight {
1248 index: usize,
1249 range: Range<Anchor>,
1250 color: Hsla,
1251 options: RowHighlightOptions,
1252 type_id: TypeId,
1253}
1254
1255#[derive(Clone, Debug)]
1256struct AddSelectionsState {
1257 above: bool,
1258 stack: Vec<usize>,
1259}
1260
1261#[derive(Clone)]
1262struct SelectNextState {
1263 query: AhoCorasick,
1264 wordwise: bool,
1265 done: bool,
1266}
1267
1268impl std::fmt::Debug for SelectNextState {
1269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270 f.debug_struct(std::any::type_name::<Self>())
1271 .field("wordwise", &self.wordwise)
1272 .field("done", &self.done)
1273 .finish()
1274 }
1275}
1276
1277#[derive(Debug)]
1278struct AutocloseRegion {
1279 selection_id: usize,
1280 range: Range<Anchor>,
1281 pair: BracketPair,
1282}
1283
1284#[derive(Debug)]
1285struct SnippetState {
1286 ranges: Vec<Vec<Range<Anchor>>>,
1287 active_index: usize,
1288 choices: Vec<Option<Vec<String>>>,
1289}
1290
1291#[doc(hidden)]
1292pub struct RenameState {
1293 pub range: Range<Anchor>,
1294 pub old_name: Arc<str>,
1295 pub editor: Entity<Editor>,
1296 block_id: CustomBlockId,
1297}
1298
1299struct InvalidationStack<T>(Vec<T>);
1300
1301struct RegisteredInlineCompletionProvider {
1302 provider: Arc<dyn InlineCompletionProviderHandle>,
1303 _subscription: Subscription,
1304}
1305
1306#[derive(Debug, PartialEq, Eq)]
1307pub struct ActiveDiagnosticGroup {
1308 pub active_range: Range<Anchor>,
1309 pub active_message: String,
1310 pub group_id: usize,
1311 pub blocks: HashSet<CustomBlockId>,
1312}
1313
1314#[derive(Debug, PartialEq, Eq)]
1315
1316pub(crate) enum ActiveDiagnostic {
1317 None,
1318 All,
1319 Group(ActiveDiagnosticGroup),
1320}
1321
1322#[derive(Serialize, Deserialize, Clone, Debug)]
1323pub struct ClipboardSelection {
1324 /// The number of bytes in this selection.
1325 pub len: usize,
1326 /// Whether this was a full-line selection.
1327 pub is_entire_line: bool,
1328 /// The indentation of the first line when this content was originally copied.
1329 pub first_line_indent: u32,
1330}
1331
1332// selections, scroll behavior, was newest selection reversed
1333type SelectSyntaxNodeHistoryState = (
1334 Box<[Selection<usize>]>,
1335 SelectSyntaxNodeScrollBehavior,
1336 bool,
1337);
1338
1339#[derive(Default)]
1340struct SelectSyntaxNodeHistory {
1341 stack: Vec<SelectSyntaxNodeHistoryState>,
1342 // disable temporarily to allow changing selections without losing the stack
1343 pub disable_clearing: bool,
1344}
1345
1346impl SelectSyntaxNodeHistory {
1347 pub fn try_clear(&mut self) {
1348 if !self.disable_clearing {
1349 self.stack.clear();
1350 }
1351 }
1352
1353 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1354 self.stack.push(selection);
1355 }
1356
1357 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1358 self.stack.pop()
1359 }
1360}
1361
1362enum SelectSyntaxNodeScrollBehavior {
1363 CursorTop,
1364 FitSelection,
1365 CursorBottom,
1366}
1367
1368#[derive(Debug)]
1369pub(crate) struct NavigationData {
1370 cursor_anchor: Anchor,
1371 cursor_position: Point,
1372 scroll_anchor: ScrollAnchor,
1373 scroll_top_row: u32,
1374}
1375
1376#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1377pub enum GotoDefinitionKind {
1378 Symbol,
1379 Declaration,
1380 Type,
1381 Implementation,
1382}
1383
1384#[derive(Debug, Clone)]
1385enum InlayHintRefreshReason {
1386 ModifiersChanged(bool),
1387 Toggle(bool),
1388 SettingsChange(InlayHintSettings),
1389 NewLinesShown,
1390 BufferEdited(HashSet<Arc<Language>>),
1391 RefreshRequested,
1392 ExcerptsRemoved(Vec<ExcerptId>),
1393}
1394
1395impl InlayHintRefreshReason {
1396 fn description(&self) -> &'static str {
1397 match self {
1398 Self::ModifiersChanged(_) => "modifiers changed",
1399 Self::Toggle(_) => "toggle",
1400 Self::SettingsChange(_) => "settings change",
1401 Self::NewLinesShown => "new lines shown",
1402 Self::BufferEdited(_) => "buffer edited",
1403 Self::RefreshRequested => "refresh requested",
1404 Self::ExcerptsRemoved(_) => "excerpts removed",
1405 }
1406 }
1407}
1408
1409pub enum FormatTarget {
1410 Buffers,
1411 Ranges(Vec<Range<MultiBufferPoint>>),
1412}
1413
1414pub(crate) struct FocusedBlock {
1415 id: BlockId,
1416 focus_handle: WeakFocusHandle,
1417}
1418
1419#[derive(Clone)]
1420enum JumpData {
1421 MultiBufferRow {
1422 row: MultiBufferRow,
1423 line_offset_from_top: u32,
1424 },
1425 MultiBufferPoint {
1426 excerpt_id: ExcerptId,
1427 position: Point,
1428 anchor: text::Anchor,
1429 line_offset_from_top: u32,
1430 },
1431}
1432
1433pub enum MultibufferSelectionMode {
1434 First,
1435 All,
1436}
1437
1438#[derive(Clone, Copy, Debug, Default)]
1439pub struct RewrapOptions {
1440 pub override_language_settings: bool,
1441 pub preserve_existing_whitespace: bool,
1442}
1443
1444impl Editor {
1445 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1446 let buffer = cx.new(|cx| Buffer::local("", cx));
1447 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1448 Self::new(
1449 EditorMode::SingleLine { auto_width: false },
1450 buffer,
1451 None,
1452 window,
1453 cx,
1454 )
1455 }
1456
1457 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1458 let buffer = cx.new(|cx| Buffer::local("", cx));
1459 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1460 Self::new(EditorMode::full(), buffer, None, window, cx)
1461 }
1462
1463 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1464 let buffer = cx.new(|cx| Buffer::local("", cx));
1465 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1466 Self::new(
1467 EditorMode::SingleLine { auto_width: true },
1468 buffer,
1469 None,
1470 window,
1471 cx,
1472 )
1473 }
1474
1475 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1476 let buffer = cx.new(|cx| Buffer::local("", cx));
1477 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1478 Self::new(
1479 EditorMode::AutoHeight { max_lines },
1480 buffer,
1481 None,
1482 window,
1483 cx,
1484 )
1485 }
1486
1487 pub fn for_buffer(
1488 buffer: Entity<Buffer>,
1489 project: Option<Entity<Project>>,
1490 window: &mut Window,
1491 cx: &mut Context<Self>,
1492 ) -> Self {
1493 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1494 Self::new(EditorMode::full(), buffer, project, window, cx)
1495 }
1496
1497 pub fn for_multibuffer(
1498 buffer: Entity<MultiBuffer>,
1499 project: Option<Entity<Project>>,
1500 window: &mut Window,
1501 cx: &mut Context<Self>,
1502 ) -> Self {
1503 Self::new(EditorMode::full(), buffer, project, window, cx)
1504 }
1505
1506 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1507 let mut clone = Self::new(
1508 self.mode.clone(),
1509 self.buffer.clone(),
1510 self.project.clone(),
1511 window,
1512 cx,
1513 );
1514 self.display_map.update(cx, |display_map, cx| {
1515 let snapshot = display_map.snapshot(cx);
1516 clone.display_map.update(cx, |display_map, cx| {
1517 display_map.set_state(&snapshot, cx);
1518 });
1519 });
1520 clone.folds_did_change(cx);
1521 clone.selections.clone_state(&self.selections);
1522 clone.scroll_manager.clone_state(&self.scroll_manager);
1523 clone.searchable = self.searchable;
1524 clone.read_only = self.read_only;
1525 clone
1526 }
1527
1528 pub fn new(
1529 mode: EditorMode,
1530 buffer: Entity<MultiBuffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 Editor::new_internal(mode, buffer, project, None, window, cx)
1536 }
1537
1538 fn new_internal(
1539 mode: EditorMode,
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 display_map: Option<Entity<DisplayMap>>,
1543 window: &mut Window,
1544 cx: &mut Context<Self>,
1545 ) -> Self {
1546 debug_assert!(
1547 display_map.is_none() || mode.is_minimap(),
1548 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1549 );
1550
1551 let full_mode = mode.is_full();
1552 let diagnostics_max_severity = if full_mode {
1553 EditorSettings::get_global(cx)
1554 .diagnostics_max_severity
1555 .unwrap_or(DiagnosticSeverity::Hint)
1556 } else {
1557 DiagnosticSeverity::Off
1558 };
1559 let style = window.text_style();
1560 let font_size = style.font_size.to_pixels(window.rem_size());
1561 let editor = cx.entity().downgrade();
1562 let fold_placeholder = FoldPlaceholder {
1563 constrain_width: true,
1564 render: Arc::new(move |fold_id, fold_range, cx| {
1565 let editor = editor.clone();
1566 div()
1567 .id(fold_id)
1568 .bg(cx.theme().colors().ghost_element_background)
1569 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1570 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1571 .rounded_xs()
1572 .size_full()
1573 .cursor_pointer()
1574 .child("⋯")
1575 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1576 .on_click(move |_, _window, cx| {
1577 editor
1578 .update(cx, |editor, cx| {
1579 editor.unfold_ranges(
1580 &[fold_range.start..fold_range.end],
1581 true,
1582 false,
1583 cx,
1584 );
1585 cx.stop_propagation();
1586 })
1587 .ok();
1588 })
1589 .into_any()
1590 }),
1591 merge_adjacent: true,
1592 ..FoldPlaceholder::default()
1593 };
1594 let display_map = display_map.unwrap_or_else(|| {
1595 cx.new(|cx| {
1596 DisplayMap::new(
1597 buffer.clone(),
1598 style.font(),
1599 font_size,
1600 None,
1601 FILE_HEADER_HEIGHT,
1602 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1603 fold_placeholder,
1604 diagnostics_max_severity,
1605 cx,
1606 )
1607 })
1608 });
1609
1610 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1611
1612 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1613
1614 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1615 .then(|| language_settings::SoftWrap::None);
1616
1617 let mut project_subscriptions = Vec::new();
1618 if mode.is_full() {
1619 if let Some(project) = project.as_ref() {
1620 project_subscriptions.push(cx.subscribe_in(
1621 project,
1622 window,
1623 |editor, _, event, window, cx| match event {
1624 project::Event::RefreshCodeLens => {
1625 // we always query lens with actions, without storing them, always refreshing them
1626 }
1627 project::Event::RefreshInlayHints => {
1628 editor
1629 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1630 }
1631 project::Event::SnippetEdit(id, snippet_edits) => {
1632 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1633 let focus_handle = editor.focus_handle(cx);
1634 if focus_handle.is_focused(window) {
1635 let snapshot = buffer.read(cx).snapshot();
1636 for (range, snippet) in snippet_edits {
1637 let editor_range =
1638 language::range_from_lsp(*range).to_offset(&snapshot);
1639 editor
1640 .insert_snippet(
1641 &[editor_range],
1642 snippet.clone(),
1643 window,
1644 cx,
1645 )
1646 .ok();
1647 }
1648 }
1649 }
1650 }
1651 _ => {}
1652 },
1653 ));
1654 if let Some(task_inventory) = project
1655 .read(cx)
1656 .task_store()
1657 .read(cx)
1658 .task_inventory()
1659 .cloned()
1660 {
1661 project_subscriptions.push(cx.observe_in(
1662 &task_inventory,
1663 window,
1664 |editor, _, window, cx| {
1665 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1666 },
1667 ));
1668 };
1669
1670 project_subscriptions.push(cx.subscribe_in(
1671 &project.read(cx).breakpoint_store(),
1672 window,
1673 |editor, _, event, window, cx| match event {
1674 BreakpointStoreEvent::ClearDebugLines => {
1675 editor.clear_row_highlights::<ActiveDebugLine>();
1676 editor.refresh_inline_values(cx);
1677 }
1678 BreakpointStoreEvent::SetDebugLine => {
1679 if editor.go_to_active_debug_line(window, cx) {
1680 cx.stop_propagation();
1681 }
1682
1683 editor.refresh_inline_values(cx);
1684 }
1685 _ => {}
1686 },
1687 ));
1688 }
1689 }
1690
1691 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1692
1693 let inlay_hint_settings =
1694 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1695 let focus_handle = cx.focus_handle();
1696 cx.on_focus(&focus_handle, window, Self::handle_focus)
1697 .detach();
1698 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1699 .detach();
1700 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1701 .detach();
1702 cx.on_blur(&focus_handle, window, Self::handle_blur)
1703 .detach();
1704
1705 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1706 Some(false)
1707 } else {
1708 None
1709 };
1710
1711 let breakpoint_store = match (&mode, project.as_ref()) {
1712 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1713 _ => None,
1714 };
1715
1716 let mut code_action_providers = Vec::new();
1717 let mut load_uncommitted_diff = None;
1718 if let Some(project) = project.clone() {
1719 load_uncommitted_diff = Some(
1720 update_uncommitted_diff_for_buffer(
1721 cx.entity(),
1722 &project,
1723 buffer.read(cx).all_buffers(),
1724 buffer.clone(),
1725 cx,
1726 )
1727 .shared(),
1728 );
1729 code_action_providers.push(Rc::new(project) as Rc<_>);
1730 }
1731
1732 let mut this = Self {
1733 focus_handle,
1734 show_cursor_when_unfocused: false,
1735 last_focused_descendant: None,
1736 buffer: buffer.clone(),
1737 display_map: display_map.clone(),
1738 selections,
1739 scroll_manager: ScrollManager::new(cx),
1740 columnar_selection_tail: None,
1741 add_selections_state: None,
1742 select_next_state: None,
1743 select_prev_state: None,
1744 selection_history: SelectionHistory::default(),
1745 autoclose_regions: Vec::new(),
1746 snippet_stack: InvalidationStack::default(),
1747 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1748 ime_transaction: None,
1749 active_diagnostics: ActiveDiagnostic::None,
1750 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1751 inline_diagnostics_update: Task::ready(()),
1752 inline_diagnostics: Vec::new(),
1753 soft_wrap_mode_override,
1754 diagnostics_max_severity,
1755 hard_wrap: None,
1756 completion_provider: project.clone().map(|project| Box::new(project) as _),
1757 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1758 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1759 project,
1760 blink_manager: blink_manager.clone(),
1761 show_local_selections: true,
1762 show_scrollbars: full_mode,
1763 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1764 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1765 show_gutter: mode.is_full(),
1766 show_line_numbers: None,
1767 use_relative_line_numbers: None,
1768 disable_expand_excerpt_buttons: false,
1769 show_git_diff_gutter: None,
1770 show_code_actions: None,
1771 show_runnables: None,
1772 show_breakpoints: None,
1773 show_wrap_guides: None,
1774 show_indent_guides,
1775 placeholder_text: None,
1776 highlight_order: 0,
1777 highlighted_rows: HashMap::default(),
1778 background_highlights: TreeMap::default(),
1779 gutter_highlights: TreeMap::default(),
1780 scrollbar_marker_state: ScrollbarMarkerState::default(),
1781 active_indent_guides_state: ActiveIndentGuidesState::default(),
1782 nav_history: None,
1783 context_menu: RefCell::new(None),
1784 context_menu_options: None,
1785 mouse_context_menu: None,
1786 completion_tasks: Vec::new(),
1787 inline_blame_popover: None,
1788 signature_help_state: SignatureHelpState::default(),
1789 auto_signature_help: None,
1790 find_all_references_task_sources: Vec::new(),
1791 next_completion_id: 0,
1792 next_inlay_id: 0,
1793 code_action_providers,
1794 available_code_actions: None,
1795 code_actions_task: None,
1796 quick_selection_highlight_task: None,
1797 debounced_selection_highlight_task: None,
1798 document_highlights_task: None,
1799 linked_editing_range_task: None,
1800 pending_rename: None,
1801 searchable: true,
1802 cursor_shape: EditorSettings::get_global(cx)
1803 .cursor_shape
1804 .unwrap_or_default(),
1805 current_line_highlight: None,
1806 autoindent_mode: Some(AutoindentMode::EachLine),
1807 collapse_matches: false,
1808 workspace: None,
1809 input_enabled: true,
1810 use_modal_editing: mode.is_full(),
1811 read_only: mode.is_minimap(),
1812 use_autoclose: true,
1813 use_auto_surround: true,
1814 auto_replace_emoji_shortcode: false,
1815 jsx_tag_auto_close_enabled_in_any_buffer: false,
1816 leader_id: None,
1817 remote_id: None,
1818 hover_state: HoverState::default(),
1819 pending_mouse_down: None,
1820 hovered_link_state: None,
1821 edit_prediction_provider: None,
1822 active_inline_completion: None,
1823 stale_inline_completion_in_menu: None,
1824 edit_prediction_preview: EditPredictionPreview::Inactive {
1825 released_too_fast: false,
1826 },
1827 inline_diagnostics_enabled: mode.is_full(),
1828 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1829 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1830
1831 gutter_hovered: false,
1832 pixel_position_of_newest_cursor: None,
1833 last_bounds: None,
1834 last_position_map: None,
1835 expect_bounds_change: None,
1836 gutter_dimensions: GutterDimensions::default(),
1837 style: None,
1838 show_cursor_names: false,
1839 hovered_cursors: HashMap::default(),
1840 next_editor_action_id: EditorActionId::default(),
1841 editor_actions: Rc::default(),
1842 inline_completions_hidden_for_vim_mode: false,
1843 show_inline_completions_override: None,
1844 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1845 edit_prediction_settings: EditPredictionSettings::Disabled,
1846 edit_prediction_indent_conflict: false,
1847 edit_prediction_requires_modifier_in_indent_conflict: true,
1848 custom_context_menu: None,
1849 show_git_blame_gutter: false,
1850 show_git_blame_inline: false,
1851 show_selection_menu: None,
1852 show_git_blame_inline_delay_task: None,
1853 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1854 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1855 serialize_dirty_buffers: !mode.is_minimap()
1856 && ProjectSettings::get_global(cx)
1857 .session
1858 .restore_unsaved_buffers,
1859 blame: None,
1860 blame_subscription: None,
1861 tasks: BTreeMap::default(),
1862
1863 breakpoint_store,
1864 gutter_breakpoint_indicator: (None, None),
1865 _subscriptions: vec![
1866 cx.observe(&buffer, Self::on_buffer_changed),
1867 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1868 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1869 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1870 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1871 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1872 cx.observe_window_activation(window, |editor, window, cx| {
1873 let active = window.is_window_active();
1874 editor.blink_manager.update(cx, |blink_manager, cx| {
1875 if active {
1876 blink_manager.enable(cx);
1877 } else {
1878 blink_manager.disable(cx);
1879 }
1880 });
1881 }),
1882 ],
1883 tasks_update_task: None,
1884 linked_edit_ranges: Default::default(),
1885 in_project_search: false,
1886 previous_search_ranges: None,
1887 breadcrumb_header: None,
1888 focused_block: None,
1889 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1890 addons: HashMap::default(),
1891 registered_buffers: HashMap::default(),
1892 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1893 selection_mark_mode: false,
1894 toggle_fold_multiple_buffers: Task::ready(()),
1895 serialize_selections: Task::ready(()),
1896 serialize_folds: Task::ready(()),
1897 text_style_refinement: None,
1898 load_diff_task: load_uncommitted_diff,
1899 temporary_diff_override: false,
1900 mouse_cursor_hidden: false,
1901 minimap: None,
1902 hide_mouse_mode: EditorSettings::get_global(cx)
1903 .hide_mouse
1904 .unwrap_or_default(),
1905 change_list: ChangeList::new(),
1906 mode,
1907 };
1908 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1909 this._subscriptions
1910 .push(cx.observe(breakpoints, |_, _, cx| {
1911 cx.notify();
1912 }));
1913 }
1914 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1915 this._subscriptions.extend(project_subscriptions);
1916
1917 this._subscriptions.push(cx.subscribe_in(
1918 &cx.entity(),
1919 window,
1920 |editor, _, e: &EditorEvent, window, cx| match e {
1921 EditorEvent::ScrollPositionChanged { local, .. } => {
1922 if *local {
1923 let new_anchor = editor.scroll_manager.anchor();
1924 let snapshot = editor.snapshot(window, cx);
1925 editor.update_restoration_data(cx, move |data| {
1926 data.scroll_position = (
1927 new_anchor.top_row(&snapshot.buffer_snapshot),
1928 new_anchor.offset,
1929 );
1930 });
1931 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1932 editor.inline_blame_popover.take();
1933 }
1934 }
1935 EditorEvent::Edited { .. } => {
1936 if !vim_enabled(cx) {
1937 let (map, selections) = editor.selections.all_adjusted_display(cx);
1938 let pop_state = editor
1939 .change_list
1940 .last()
1941 .map(|previous| {
1942 previous.len() == selections.len()
1943 && previous.iter().enumerate().all(|(ix, p)| {
1944 p.to_display_point(&map).row()
1945 == selections[ix].head().row()
1946 })
1947 })
1948 .unwrap_or(false);
1949 let new_positions = selections
1950 .into_iter()
1951 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1952 .collect();
1953 editor
1954 .change_list
1955 .push_to_change_list(pop_state, new_positions);
1956 }
1957 }
1958 _ => (),
1959 },
1960 ));
1961
1962 if let Some(dap_store) = this
1963 .project
1964 .as_ref()
1965 .map(|project| project.read(cx).dap_store())
1966 {
1967 let weak_editor = cx.weak_entity();
1968
1969 this._subscriptions
1970 .push(
1971 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1972 let session_entity = cx.entity();
1973 weak_editor
1974 .update(cx, |editor, cx| {
1975 editor._subscriptions.push(
1976 cx.subscribe(&session_entity, Self::on_debug_session_event),
1977 );
1978 })
1979 .ok();
1980 }),
1981 );
1982
1983 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1984 this._subscriptions
1985 .push(cx.subscribe(&session, Self::on_debug_session_event));
1986 }
1987 }
1988
1989 this.end_selection(window, cx);
1990 this.scroll_manager.show_scrollbars(window, cx);
1991 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1992
1993 if full_mode {
1994 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1995 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1996
1997 if this.git_blame_inline_enabled {
1998 this.start_git_blame_inline(false, window, cx);
1999 }
2000
2001 this.go_to_active_debug_line(window, cx);
2002
2003 if let Some(buffer) = buffer.read(cx).as_singleton() {
2004 if let Some(project) = this.project.as_ref() {
2005 let handle = project.update(cx, |project, cx| {
2006 project.register_buffer_with_language_servers(&buffer, cx)
2007 });
2008 this.registered_buffers
2009 .insert(buffer.read(cx).remote_id(), handle);
2010 }
2011 }
2012
2013 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2014 }
2015
2016 this.report_editor_event("Editor Opened", None, cx);
2017 this
2018 }
2019
2020 pub fn deploy_mouse_context_menu(
2021 &mut self,
2022 position: gpui::Point<Pixels>,
2023 context_menu: Entity<ContextMenu>,
2024 window: &mut Window,
2025 cx: &mut Context<Self>,
2026 ) {
2027 self.mouse_context_menu = Some(MouseContextMenu::new(
2028 self,
2029 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2030 context_menu,
2031 window,
2032 cx,
2033 ));
2034 }
2035
2036 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2037 self.mouse_context_menu
2038 .as_ref()
2039 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2040 }
2041
2042 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2043 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2044 }
2045
2046 fn key_context_internal(
2047 &self,
2048 has_active_edit_prediction: bool,
2049 window: &Window,
2050 cx: &App,
2051 ) -> KeyContext {
2052 let mut key_context = KeyContext::new_with_defaults();
2053 key_context.add("Editor");
2054 let mode = match self.mode {
2055 EditorMode::SingleLine { .. } => "single_line",
2056 EditorMode::AutoHeight { .. } => "auto_height",
2057 EditorMode::Minimap { .. } => "minimap",
2058 EditorMode::Full { .. } => "full",
2059 };
2060
2061 if EditorSettings::jupyter_enabled(cx) {
2062 key_context.add("jupyter");
2063 }
2064
2065 key_context.set("mode", mode);
2066 if self.pending_rename.is_some() {
2067 key_context.add("renaming");
2068 }
2069
2070 match self.context_menu.borrow().as_ref() {
2071 Some(CodeContextMenu::Completions(_)) => {
2072 key_context.add("menu");
2073 key_context.add("showing_completions");
2074 }
2075 Some(CodeContextMenu::CodeActions(_)) => {
2076 key_context.add("menu");
2077 key_context.add("showing_code_actions")
2078 }
2079 None => {}
2080 }
2081
2082 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2083 if !self.focus_handle(cx).contains_focused(window, cx)
2084 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2085 {
2086 for addon in self.addons.values() {
2087 addon.extend_key_context(&mut key_context, cx)
2088 }
2089 }
2090
2091 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2092 if let Some(extension) = singleton_buffer
2093 .read(cx)
2094 .file()
2095 .and_then(|file| file.path().extension()?.to_str())
2096 {
2097 key_context.set("extension", extension.to_string());
2098 }
2099 } else {
2100 key_context.add("multibuffer");
2101 }
2102
2103 if has_active_edit_prediction {
2104 if self.edit_prediction_in_conflict() {
2105 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2106 } else {
2107 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2108 key_context.add("copilot_suggestion");
2109 }
2110 }
2111
2112 if self.selection_mark_mode {
2113 key_context.add("selection_mode");
2114 }
2115
2116 key_context
2117 }
2118
2119 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2120 self.mouse_cursor_hidden = match origin {
2121 HideMouseCursorOrigin::TypingAction => {
2122 matches!(
2123 self.hide_mouse_mode,
2124 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2125 )
2126 }
2127 HideMouseCursorOrigin::MovementAction => {
2128 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2129 }
2130 };
2131 }
2132
2133 pub fn edit_prediction_in_conflict(&self) -> bool {
2134 if !self.show_edit_predictions_in_menu() {
2135 return false;
2136 }
2137
2138 let showing_completions = self
2139 .context_menu
2140 .borrow()
2141 .as_ref()
2142 .map_or(false, |context| {
2143 matches!(context, CodeContextMenu::Completions(_))
2144 });
2145
2146 showing_completions
2147 || self.edit_prediction_requires_modifier()
2148 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2149 // bindings to insert tab characters.
2150 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2151 }
2152
2153 pub fn accept_edit_prediction_keybind(
2154 &self,
2155 window: &Window,
2156 cx: &App,
2157 ) -> AcceptEditPredictionBinding {
2158 let key_context = self.key_context_internal(true, window, cx);
2159 let in_conflict = self.edit_prediction_in_conflict();
2160
2161 AcceptEditPredictionBinding(
2162 window
2163 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2164 .into_iter()
2165 .filter(|binding| {
2166 !in_conflict
2167 || binding
2168 .keystrokes()
2169 .first()
2170 .map_or(false, |keystroke| keystroke.modifiers.modified())
2171 })
2172 .rev()
2173 .min_by_key(|binding| {
2174 binding
2175 .keystrokes()
2176 .first()
2177 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2178 }),
2179 )
2180 }
2181
2182 pub fn new_file(
2183 workspace: &mut Workspace,
2184 _: &workspace::NewFile,
2185 window: &mut Window,
2186 cx: &mut Context<Workspace>,
2187 ) {
2188 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2189 "Failed to create buffer",
2190 window,
2191 cx,
2192 |e, _, _| match e.error_code() {
2193 ErrorCode::RemoteUpgradeRequired => Some(format!(
2194 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2195 e.error_tag("required").unwrap_or("the latest version")
2196 )),
2197 _ => None,
2198 },
2199 );
2200 }
2201
2202 pub fn new_in_workspace(
2203 workspace: &mut Workspace,
2204 window: &mut Window,
2205 cx: &mut Context<Workspace>,
2206 ) -> Task<Result<Entity<Editor>>> {
2207 let project = workspace.project().clone();
2208 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2209
2210 cx.spawn_in(window, async move |workspace, cx| {
2211 let buffer = create.await?;
2212 workspace.update_in(cx, |workspace, window, cx| {
2213 let editor =
2214 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2215 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2216 editor
2217 })
2218 })
2219 }
2220
2221 fn new_file_vertical(
2222 workspace: &mut Workspace,
2223 _: &workspace::NewFileSplitVertical,
2224 window: &mut Window,
2225 cx: &mut Context<Workspace>,
2226 ) {
2227 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2228 }
2229
2230 fn new_file_horizontal(
2231 workspace: &mut Workspace,
2232 _: &workspace::NewFileSplitHorizontal,
2233 window: &mut Window,
2234 cx: &mut Context<Workspace>,
2235 ) {
2236 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2237 }
2238
2239 fn new_file_in_direction(
2240 workspace: &mut Workspace,
2241 direction: SplitDirection,
2242 window: &mut Window,
2243 cx: &mut Context<Workspace>,
2244 ) {
2245 let project = workspace.project().clone();
2246 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2247
2248 cx.spawn_in(window, async move |workspace, cx| {
2249 let buffer = create.await?;
2250 workspace.update_in(cx, move |workspace, window, cx| {
2251 workspace.split_item(
2252 direction,
2253 Box::new(
2254 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2255 ),
2256 window,
2257 cx,
2258 )
2259 })?;
2260 anyhow::Ok(())
2261 })
2262 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2263 match e.error_code() {
2264 ErrorCode::RemoteUpgradeRequired => Some(format!(
2265 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2266 e.error_tag("required").unwrap_or("the latest version")
2267 )),
2268 _ => None,
2269 }
2270 });
2271 }
2272
2273 pub fn leader_id(&self) -> Option<CollaboratorId> {
2274 self.leader_id
2275 }
2276
2277 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2278 &self.buffer
2279 }
2280
2281 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2282 self.workspace.as_ref()?.0.upgrade()
2283 }
2284
2285 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2286 self.buffer().read(cx).title(cx)
2287 }
2288
2289 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2290 let git_blame_gutter_max_author_length = self
2291 .render_git_blame_gutter(cx)
2292 .then(|| {
2293 if let Some(blame) = self.blame.as_ref() {
2294 let max_author_length =
2295 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2296 Some(max_author_length)
2297 } else {
2298 None
2299 }
2300 })
2301 .flatten();
2302
2303 EditorSnapshot {
2304 mode: self.mode.clone(),
2305 show_gutter: self.show_gutter,
2306 show_line_numbers: self.show_line_numbers,
2307 show_git_diff_gutter: self.show_git_diff_gutter,
2308 show_runnables: self.show_runnables,
2309 show_breakpoints: self.show_breakpoints,
2310 git_blame_gutter_max_author_length,
2311 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2312 scroll_anchor: self.scroll_manager.anchor(),
2313 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2314 placeholder_text: self.placeholder_text.clone(),
2315 is_focused: self.focus_handle.is_focused(window),
2316 current_line_highlight: self
2317 .current_line_highlight
2318 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2319 gutter_hovered: self.gutter_hovered,
2320 }
2321 }
2322
2323 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2324 self.buffer.read(cx).language_at(point, cx)
2325 }
2326
2327 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2328 self.buffer.read(cx).read(cx).file_at(point).cloned()
2329 }
2330
2331 pub fn active_excerpt(
2332 &self,
2333 cx: &App,
2334 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2335 self.buffer
2336 .read(cx)
2337 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2338 }
2339
2340 pub fn mode(&self) -> &EditorMode {
2341 &self.mode
2342 }
2343
2344 pub fn set_mode(&mut self, mode: EditorMode) {
2345 self.mode = mode;
2346 }
2347
2348 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2349 self.collaboration_hub.as_deref()
2350 }
2351
2352 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2353 self.collaboration_hub = Some(hub);
2354 }
2355
2356 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2357 self.in_project_search = in_project_search;
2358 }
2359
2360 pub fn set_custom_context_menu(
2361 &mut self,
2362 f: impl 'static
2363 + Fn(
2364 &mut Self,
2365 DisplayPoint,
2366 &mut Window,
2367 &mut Context<Self>,
2368 ) -> Option<Entity<ui::ContextMenu>>,
2369 ) {
2370 self.custom_context_menu = Some(Box::new(f))
2371 }
2372
2373 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2374 self.completion_provider = provider;
2375 }
2376
2377 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2378 self.semantics_provider.clone()
2379 }
2380
2381 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2382 self.semantics_provider = provider;
2383 }
2384
2385 pub fn set_edit_prediction_provider<T>(
2386 &mut self,
2387 provider: Option<Entity<T>>,
2388 window: &mut Window,
2389 cx: &mut Context<Self>,
2390 ) where
2391 T: EditPredictionProvider,
2392 {
2393 self.edit_prediction_provider =
2394 provider.map(|provider| RegisteredInlineCompletionProvider {
2395 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2396 if this.focus_handle.is_focused(window) {
2397 this.update_visible_inline_completion(window, cx);
2398 }
2399 }),
2400 provider: Arc::new(provider),
2401 });
2402 self.update_edit_prediction_settings(cx);
2403 self.refresh_inline_completion(false, false, window, cx);
2404 }
2405
2406 pub fn placeholder_text(&self) -> Option<&str> {
2407 self.placeholder_text.as_deref()
2408 }
2409
2410 pub fn set_placeholder_text(
2411 &mut self,
2412 placeholder_text: impl Into<Arc<str>>,
2413 cx: &mut Context<Self>,
2414 ) {
2415 let placeholder_text = Some(placeholder_text.into());
2416 if self.placeholder_text != placeholder_text {
2417 self.placeholder_text = placeholder_text;
2418 cx.notify();
2419 }
2420 }
2421
2422 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2423 self.cursor_shape = cursor_shape;
2424
2425 // Disrupt blink for immediate user feedback that the cursor shape has changed
2426 self.blink_manager.update(cx, BlinkManager::show_cursor);
2427
2428 cx.notify();
2429 }
2430
2431 pub fn set_current_line_highlight(
2432 &mut self,
2433 current_line_highlight: Option<CurrentLineHighlight>,
2434 ) {
2435 self.current_line_highlight = current_line_highlight;
2436 }
2437
2438 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2439 self.collapse_matches = collapse_matches;
2440 }
2441
2442 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2443 let buffers = self.buffer.read(cx).all_buffers();
2444 let Some(project) = self.project.as_ref() else {
2445 return;
2446 };
2447 project.update(cx, |project, cx| {
2448 for buffer in buffers {
2449 self.registered_buffers
2450 .entry(buffer.read(cx).remote_id())
2451 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2452 }
2453 })
2454 }
2455
2456 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2457 if self.collapse_matches {
2458 return range.start..range.start;
2459 }
2460 range.clone()
2461 }
2462
2463 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2464 if self.display_map.read(cx).clip_at_line_ends != clip {
2465 self.display_map
2466 .update(cx, |map, _| map.clip_at_line_ends = clip);
2467 }
2468 }
2469
2470 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2471 self.input_enabled = input_enabled;
2472 }
2473
2474 pub fn set_inline_completions_hidden_for_vim_mode(
2475 &mut self,
2476 hidden: bool,
2477 window: &mut Window,
2478 cx: &mut Context<Self>,
2479 ) {
2480 if hidden != self.inline_completions_hidden_for_vim_mode {
2481 self.inline_completions_hidden_for_vim_mode = hidden;
2482 if hidden {
2483 self.update_visible_inline_completion(window, cx);
2484 } else {
2485 self.refresh_inline_completion(true, false, window, cx);
2486 }
2487 }
2488 }
2489
2490 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2491 self.menu_inline_completions_policy = value;
2492 }
2493
2494 pub fn set_autoindent(&mut self, autoindent: bool) {
2495 if autoindent {
2496 self.autoindent_mode = Some(AutoindentMode::EachLine);
2497 } else {
2498 self.autoindent_mode = None;
2499 }
2500 }
2501
2502 pub fn read_only(&self, cx: &App) -> bool {
2503 self.read_only || self.buffer.read(cx).read_only()
2504 }
2505
2506 pub fn set_read_only(&mut self, read_only: bool) {
2507 self.read_only = read_only;
2508 }
2509
2510 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2511 self.use_autoclose = autoclose;
2512 }
2513
2514 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2515 self.use_auto_surround = auto_surround;
2516 }
2517
2518 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2519 self.auto_replace_emoji_shortcode = auto_replace;
2520 }
2521
2522 pub fn toggle_edit_predictions(
2523 &mut self,
2524 _: &ToggleEditPrediction,
2525 window: &mut Window,
2526 cx: &mut Context<Self>,
2527 ) {
2528 if self.show_inline_completions_override.is_some() {
2529 self.set_show_edit_predictions(None, window, cx);
2530 } else {
2531 let show_edit_predictions = !self.edit_predictions_enabled();
2532 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2533 }
2534 }
2535
2536 pub fn set_show_edit_predictions(
2537 &mut self,
2538 show_edit_predictions: Option<bool>,
2539 window: &mut Window,
2540 cx: &mut Context<Self>,
2541 ) {
2542 self.show_inline_completions_override = show_edit_predictions;
2543 self.update_edit_prediction_settings(cx);
2544
2545 if let Some(false) = show_edit_predictions {
2546 self.discard_inline_completion(false, cx);
2547 } else {
2548 self.refresh_inline_completion(false, true, window, cx);
2549 }
2550 }
2551
2552 fn inline_completions_disabled_in_scope(
2553 &self,
2554 buffer: &Entity<Buffer>,
2555 buffer_position: language::Anchor,
2556 cx: &App,
2557 ) -> bool {
2558 let snapshot = buffer.read(cx).snapshot();
2559 let settings = snapshot.settings_at(buffer_position, cx);
2560
2561 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2562 return false;
2563 };
2564
2565 scope.override_name().map_or(false, |scope_name| {
2566 settings
2567 .edit_predictions_disabled_in
2568 .iter()
2569 .any(|s| s == scope_name)
2570 })
2571 }
2572
2573 pub fn set_use_modal_editing(&mut self, to: bool) {
2574 self.use_modal_editing = to;
2575 }
2576
2577 pub fn use_modal_editing(&self) -> bool {
2578 self.use_modal_editing
2579 }
2580
2581 fn selections_did_change(
2582 &mut self,
2583 local: bool,
2584 old_cursor_position: &Anchor,
2585 show_completions: bool,
2586 window: &mut Window,
2587 cx: &mut Context<Self>,
2588 ) {
2589 window.invalidate_character_coordinates();
2590
2591 // Copy selections to primary selection buffer
2592 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2593 if local {
2594 let selections = self.selections.all::<usize>(cx);
2595 let buffer_handle = self.buffer.read(cx).read(cx);
2596
2597 let mut text = String::new();
2598 for (index, selection) in selections.iter().enumerate() {
2599 let text_for_selection = buffer_handle
2600 .text_for_range(selection.start..selection.end)
2601 .collect::<String>();
2602
2603 text.push_str(&text_for_selection);
2604 if index != selections.len() - 1 {
2605 text.push('\n');
2606 }
2607 }
2608
2609 if !text.is_empty() {
2610 cx.write_to_primary(ClipboardItem::new_string(text));
2611 }
2612 }
2613
2614 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2615 self.buffer.update(cx, |buffer, cx| {
2616 buffer.set_active_selections(
2617 &self.selections.disjoint_anchors(),
2618 self.selections.line_mode,
2619 self.cursor_shape,
2620 cx,
2621 )
2622 });
2623 }
2624 let display_map = self
2625 .display_map
2626 .update(cx, |display_map, cx| display_map.snapshot(cx));
2627 let buffer = &display_map.buffer_snapshot;
2628 self.add_selections_state = None;
2629 self.select_next_state = None;
2630 self.select_prev_state = None;
2631 self.select_syntax_node_history.try_clear();
2632 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2633 self.snippet_stack
2634 .invalidate(&self.selections.disjoint_anchors(), buffer);
2635 self.take_rename(false, window, cx);
2636
2637 let new_cursor_position = self.selections.newest_anchor().head();
2638
2639 self.push_to_nav_history(
2640 *old_cursor_position,
2641 Some(new_cursor_position.to_point(buffer)),
2642 false,
2643 cx,
2644 );
2645
2646 if local {
2647 let new_cursor_position = self.selections.newest_anchor().head();
2648 let mut context_menu = self.context_menu.borrow_mut();
2649 let completion_menu = match context_menu.as_ref() {
2650 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2651 _ => {
2652 *context_menu = None;
2653 None
2654 }
2655 };
2656 if let Some(buffer_id) = new_cursor_position.buffer_id {
2657 if !self.registered_buffers.contains_key(&buffer_id) {
2658 if let Some(project) = self.project.as_ref() {
2659 project.update(cx, |project, cx| {
2660 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2661 return;
2662 };
2663 self.registered_buffers.insert(
2664 buffer_id,
2665 project.register_buffer_with_language_servers(&buffer, cx),
2666 );
2667 })
2668 }
2669 }
2670 }
2671
2672 if let Some(completion_menu) = completion_menu {
2673 let cursor_position = new_cursor_position.to_offset(buffer);
2674 let (word_range, kind) =
2675 buffer.surrounding_word(completion_menu.initial_position, true);
2676 if kind == Some(CharKind::Word)
2677 && word_range.to_inclusive().contains(&cursor_position)
2678 {
2679 let mut completion_menu = completion_menu.clone();
2680 drop(context_menu);
2681
2682 let query = Self::completion_query(buffer, cursor_position);
2683 cx.spawn(async move |this, cx| {
2684 completion_menu
2685 .filter(query.as_deref(), cx.background_executor().clone())
2686 .await;
2687
2688 this.update(cx, |this, cx| {
2689 let mut context_menu = this.context_menu.borrow_mut();
2690 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2691 else {
2692 return;
2693 };
2694
2695 if menu.id > completion_menu.id {
2696 return;
2697 }
2698
2699 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2700 drop(context_menu);
2701 cx.notify();
2702 })
2703 })
2704 .detach();
2705
2706 if show_completions {
2707 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2708 }
2709 } else {
2710 drop(context_menu);
2711 self.hide_context_menu(window, cx);
2712 }
2713 } else {
2714 drop(context_menu);
2715 }
2716
2717 hide_hover(self, cx);
2718
2719 if old_cursor_position.to_display_point(&display_map).row()
2720 != new_cursor_position.to_display_point(&display_map).row()
2721 {
2722 self.available_code_actions.take();
2723 }
2724 self.refresh_code_actions(window, cx);
2725 self.refresh_document_highlights(cx);
2726 self.refresh_selected_text_highlights(false, window, cx);
2727 refresh_matching_bracket_highlights(self, window, cx);
2728 self.update_visible_inline_completion(window, cx);
2729 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2730 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2731 self.inline_blame_popover.take();
2732 if self.git_blame_inline_enabled {
2733 self.start_inline_blame_timer(window, cx);
2734 }
2735 }
2736
2737 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2738 cx.emit(EditorEvent::SelectionsChanged { local });
2739
2740 let selections = &self.selections.disjoint;
2741 if selections.len() == 1 {
2742 cx.emit(SearchEvent::ActiveMatchChanged)
2743 }
2744 if local {
2745 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2746 let inmemory_selections = selections
2747 .iter()
2748 .map(|s| {
2749 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2750 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2751 })
2752 .collect();
2753 self.update_restoration_data(cx, |data| {
2754 data.selections = inmemory_selections;
2755 });
2756
2757 if WorkspaceSettings::get(None, cx).restore_on_startup
2758 != RestoreOnStartupBehavior::None
2759 {
2760 if let Some(workspace_id) =
2761 self.workspace.as_ref().and_then(|workspace| workspace.1)
2762 {
2763 let snapshot = self.buffer().read(cx).snapshot(cx);
2764 let selections = selections.clone();
2765 let background_executor = cx.background_executor().clone();
2766 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2767 self.serialize_selections = cx.background_spawn(async move {
2768 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2769 let db_selections = selections
2770 .iter()
2771 .map(|selection| {
2772 (
2773 selection.start.to_offset(&snapshot),
2774 selection.end.to_offset(&snapshot),
2775 )
2776 })
2777 .collect();
2778
2779 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2780 .await
2781 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2782 .log_err();
2783 });
2784 }
2785 }
2786 }
2787 }
2788
2789 cx.notify();
2790 }
2791
2792 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2793 use text::ToOffset as _;
2794 use text::ToPoint as _;
2795
2796 if self.mode.is_minimap()
2797 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2798 {
2799 return;
2800 }
2801
2802 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2803 return;
2804 };
2805
2806 let snapshot = singleton.read(cx).snapshot();
2807 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2808 let display_snapshot = display_map.snapshot(cx);
2809
2810 display_snapshot
2811 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2812 .map(|fold| {
2813 fold.range.start.text_anchor.to_point(&snapshot)
2814 ..fold.range.end.text_anchor.to_point(&snapshot)
2815 })
2816 .collect()
2817 });
2818 self.update_restoration_data(cx, |data| {
2819 data.folds = inmemory_folds;
2820 });
2821
2822 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2823 return;
2824 };
2825 let background_executor = cx.background_executor().clone();
2826 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2827 let db_folds = self.display_map.update(cx, |display_map, cx| {
2828 display_map
2829 .snapshot(cx)
2830 .folds_in_range(0..snapshot.len())
2831 .map(|fold| {
2832 (
2833 fold.range.start.text_anchor.to_offset(&snapshot),
2834 fold.range.end.text_anchor.to_offset(&snapshot),
2835 )
2836 })
2837 .collect()
2838 });
2839 self.serialize_folds = cx.background_spawn(async move {
2840 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2841 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2842 .await
2843 .with_context(|| {
2844 format!(
2845 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2846 )
2847 })
2848 .log_err();
2849 });
2850 }
2851
2852 pub fn sync_selections(
2853 &mut self,
2854 other: Entity<Editor>,
2855 cx: &mut Context<Self>,
2856 ) -> gpui::Subscription {
2857 let other_selections = other.read(cx).selections.disjoint.to_vec();
2858 self.selections.change_with(cx, |selections| {
2859 selections.select_anchors(other_selections);
2860 });
2861
2862 let other_subscription =
2863 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2864 EditorEvent::SelectionsChanged { local: true } => {
2865 let other_selections = other.read(cx).selections.disjoint.to_vec();
2866 if other_selections.is_empty() {
2867 return;
2868 }
2869 this.selections.change_with(cx, |selections| {
2870 selections.select_anchors(other_selections);
2871 });
2872 }
2873 _ => {}
2874 });
2875
2876 let this_subscription =
2877 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2878 EditorEvent::SelectionsChanged { local: true } => {
2879 let these_selections = this.selections.disjoint.to_vec();
2880 if these_selections.is_empty() {
2881 return;
2882 }
2883 other.update(cx, |other_editor, cx| {
2884 other_editor.selections.change_with(cx, |selections| {
2885 selections.select_anchors(these_selections);
2886 })
2887 });
2888 }
2889 _ => {}
2890 });
2891
2892 Subscription::join(other_subscription, this_subscription)
2893 }
2894
2895 pub fn change_selections<R>(
2896 &mut self,
2897 autoscroll: Option<Autoscroll>,
2898 window: &mut Window,
2899 cx: &mut Context<Self>,
2900 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2901 ) -> R {
2902 self.change_selections_inner(autoscroll, true, window, cx, change)
2903 }
2904
2905 fn change_selections_inner<R>(
2906 &mut self,
2907 autoscroll: Option<Autoscroll>,
2908 request_completions: bool,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2912 ) -> R {
2913 let old_cursor_position = self.selections.newest_anchor().head();
2914 self.push_to_selection_history();
2915
2916 let (changed, result) = self.selections.change_with(cx, change);
2917
2918 if changed {
2919 if let Some(autoscroll) = autoscroll {
2920 self.request_autoscroll(autoscroll, cx);
2921 }
2922 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2923
2924 if self.should_open_signature_help_automatically(
2925 &old_cursor_position,
2926 self.signature_help_state.backspace_pressed(),
2927 cx,
2928 ) {
2929 self.show_signature_help(&ShowSignatureHelp, window, cx);
2930 }
2931 self.signature_help_state.set_backspace_pressed(false);
2932 }
2933
2934 result
2935 }
2936
2937 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2938 where
2939 I: IntoIterator<Item = (Range<S>, T)>,
2940 S: ToOffset,
2941 T: Into<Arc<str>>,
2942 {
2943 if self.read_only(cx) {
2944 return;
2945 }
2946
2947 self.buffer
2948 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2949 }
2950
2951 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2952 where
2953 I: IntoIterator<Item = (Range<S>, T)>,
2954 S: ToOffset,
2955 T: Into<Arc<str>>,
2956 {
2957 if self.read_only(cx) {
2958 return;
2959 }
2960
2961 self.buffer.update(cx, |buffer, cx| {
2962 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2963 });
2964 }
2965
2966 pub fn edit_with_block_indent<I, S, T>(
2967 &mut self,
2968 edits: I,
2969 original_indent_columns: Vec<Option<u32>>,
2970 cx: &mut Context<Self>,
2971 ) where
2972 I: IntoIterator<Item = (Range<S>, T)>,
2973 S: ToOffset,
2974 T: Into<Arc<str>>,
2975 {
2976 if self.read_only(cx) {
2977 return;
2978 }
2979
2980 self.buffer.update(cx, |buffer, cx| {
2981 buffer.edit(
2982 edits,
2983 Some(AutoindentMode::Block {
2984 original_indent_columns,
2985 }),
2986 cx,
2987 )
2988 });
2989 }
2990
2991 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2992 self.hide_context_menu(window, cx);
2993
2994 match phase {
2995 SelectPhase::Begin {
2996 position,
2997 add,
2998 click_count,
2999 } => self.begin_selection(position, add, click_count, window, cx),
3000 SelectPhase::BeginColumnar {
3001 position,
3002 goal_column,
3003 reset,
3004 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3005 SelectPhase::Extend {
3006 position,
3007 click_count,
3008 } => self.extend_selection(position, click_count, window, cx),
3009 SelectPhase::Update {
3010 position,
3011 goal_column,
3012 scroll_delta,
3013 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3014 SelectPhase::End => self.end_selection(window, cx),
3015 }
3016 }
3017
3018 fn extend_selection(
3019 &mut self,
3020 position: DisplayPoint,
3021 click_count: usize,
3022 window: &mut Window,
3023 cx: &mut Context<Self>,
3024 ) {
3025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3026 let tail = self.selections.newest::<usize>(cx).tail();
3027 self.begin_selection(position, false, click_count, window, cx);
3028
3029 let position = position.to_offset(&display_map, Bias::Left);
3030 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3031
3032 let mut pending_selection = self
3033 .selections
3034 .pending_anchor()
3035 .expect("extend_selection not called with pending selection");
3036 if position >= tail {
3037 pending_selection.start = tail_anchor;
3038 } else {
3039 pending_selection.end = tail_anchor;
3040 pending_selection.reversed = true;
3041 }
3042
3043 let mut pending_mode = self.selections.pending_mode().unwrap();
3044 match &mut pending_mode {
3045 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3046 _ => {}
3047 }
3048
3049 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3050
3051 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3052 s.set_pending(pending_selection, pending_mode)
3053 });
3054 }
3055
3056 fn begin_selection(
3057 &mut self,
3058 position: DisplayPoint,
3059 add: bool,
3060 click_count: usize,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 ) {
3064 if !self.focus_handle.is_focused(window) {
3065 self.last_focused_descendant = None;
3066 window.focus(&self.focus_handle);
3067 }
3068
3069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3070 let buffer = &display_map.buffer_snapshot;
3071 let position = display_map.clip_point(position, Bias::Left);
3072
3073 let start;
3074 let end;
3075 let mode;
3076 let mut auto_scroll;
3077 match click_count {
3078 1 => {
3079 start = buffer.anchor_before(position.to_point(&display_map));
3080 end = start;
3081 mode = SelectMode::Character;
3082 auto_scroll = true;
3083 }
3084 2 => {
3085 let range = movement::surrounding_word(&display_map, position);
3086 start = buffer.anchor_before(range.start.to_point(&display_map));
3087 end = buffer.anchor_before(range.end.to_point(&display_map));
3088 mode = SelectMode::Word(start..end);
3089 auto_scroll = true;
3090 }
3091 3 => {
3092 let position = display_map
3093 .clip_point(position, Bias::Left)
3094 .to_point(&display_map);
3095 let line_start = display_map.prev_line_boundary(position).0;
3096 let next_line_start = buffer.clip_point(
3097 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3098 Bias::Left,
3099 );
3100 start = buffer.anchor_before(line_start);
3101 end = buffer.anchor_before(next_line_start);
3102 mode = SelectMode::Line(start..end);
3103 auto_scroll = true;
3104 }
3105 _ => {
3106 start = buffer.anchor_before(0);
3107 end = buffer.anchor_before(buffer.len());
3108 mode = SelectMode::All;
3109 auto_scroll = false;
3110 }
3111 }
3112 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3113
3114 let point_to_delete: Option<usize> = {
3115 let selected_points: Vec<Selection<Point>> =
3116 self.selections.disjoint_in_range(start..end, cx);
3117
3118 if !add || click_count > 1 {
3119 None
3120 } else if !selected_points.is_empty() {
3121 Some(selected_points[0].id)
3122 } else {
3123 let clicked_point_already_selected =
3124 self.selections.disjoint.iter().find(|selection| {
3125 selection.start.to_point(buffer) == start.to_point(buffer)
3126 || selection.end.to_point(buffer) == end.to_point(buffer)
3127 });
3128
3129 clicked_point_already_selected.map(|selection| selection.id)
3130 }
3131 };
3132
3133 let selections_count = self.selections.count();
3134
3135 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3136 if let Some(point_to_delete) = point_to_delete {
3137 s.delete(point_to_delete);
3138
3139 if selections_count == 1 {
3140 s.set_pending_anchor_range(start..end, mode);
3141 }
3142 } else {
3143 if !add {
3144 s.clear_disjoint();
3145 }
3146
3147 s.set_pending_anchor_range(start..end, mode);
3148 }
3149 });
3150 }
3151
3152 fn begin_columnar_selection(
3153 &mut self,
3154 position: DisplayPoint,
3155 goal_column: u32,
3156 reset: bool,
3157 window: &mut Window,
3158 cx: &mut Context<Self>,
3159 ) {
3160 if !self.focus_handle.is_focused(window) {
3161 self.last_focused_descendant = None;
3162 window.focus(&self.focus_handle);
3163 }
3164
3165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3166
3167 if reset {
3168 let pointer_position = display_map
3169 .buffer_snapshot
3170 .anchor_before(position.to_point(&display_map));
3171
3172 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3173 s.clear_disjoint();
3174 s.set_pending_anchor_range(
3175 pointer_position..pointer_position,
3176 SelectMode::Character,
3177 );
3178 });
3179 }
3180
3181 let tail = self.selections.newest::<Point>(cx).tail();
3182 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3183
3184 if !reset {
3185 self.select_columns(
3186 tail.to_display_point(&display_map),
3187 position,
3188 goal_column,
3189 &display_map,
3190 window,
3191 cx,
3192 );
3193 }
3194 }
3195
3196 fn update_selection(
3197 &mut self,
3198 position: DisplayPoint,
3199 goal_column: u32,
3200 scroll_delta: gpui::Point<f32>,
3201 window: &mut Window,
3202 cx: &mut Context<Self>,
3203 ) {
3204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3205
3206 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3207 let tail = tail.to_display_point(&display_map);
3208 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3209 } else if let Some(mut pending) = self.selections.pending_anchor() {
3210 let buffer = self.buffer.read(cx).snapshot(cx);
3211 let head;
3212 let tail;
3213 let mode = self.selections.pending_mode().unwrap();
3214 match &mode {
3215 SelectMode::Character => {
3216 head = position.to_point(&display_map);
3217 tail = pending.tail().to_point(&buffer);
3218 }
3219 SelectMode::Word(original_range) => {
3220 let original_display_range = original_range.start.to_display_point(&display_map)
3221 ..original_range.end.to_display_point(&display_map);
3222 let original_buffer_range = original_display_range.start.to_point(&display_map)
3223 ..original_display_range.end.to_point(&display_map);
3224 if movement::is_inside_word(&display_map, position)
3225 || original_display_range.contains(&position)
3226 {
3227 let word_range = movement::surrounding_word(&display_map, position);
3228 if word_range.start < original_display_range.start {
3229 head = word_range.start.to_point(&display_map);
3230 } else {
3231 head = word_range.end.to_point(&display_map);
3232 }
3233 } else {
3234 head = position.to_point(&display_map);
3235 }
3236
3237 if head <= original_buffer_range.start {
3238 tail = original_buffer_range.end;
3239 } else {
3240 tail = original_buffer_range.start;
3241 }
3242 }
3243 SelectMode::Line(original_range) => {
3244 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3245
3246 let position = display_map
3247 .clip_point(position, Bias::Left)
3248 .to_point(&display_map);
3249 let line_start = display_map.prev_line_boundary(position).0;
3250 let next_line_start = buffer.clip_point(
3251 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3252 Bias::Left,
3253 );
3254
3255 if line_start < original_range.start {
3256 head = line_start
3257 } else {
3258 head = next_line_start
3259 }
3260
3261 if head <= original_range.start {
3262 tail = original_range.end;
3263 } else {
3264 tail = original_range.start;
3265 }
3266 }
3267 SelectMode::All => {
3268 return;
3269 }
3270 };
3271
3272 if head < tail {
3273 pending.start = buffer.anchor_before(head);
3274 pending.end = buffer.anchor_before(tail);
3275 pending.reversed = true;
3276 } else {
3277 pending.start = buffer.anchor_before(tail);
3278 pending.end = buffer.anchor_before(head);
3279 pending.reversed = false;
3280 }
3281
3282 self.change_selections(None, window, cx, |s| {
3283 s.set_pending(pending, mode);
3284 });
3285 } else {
3286 log::error!("update_selection dispatched with no pending selection");
3287 return;
3288 }
3289
3290 self.apply_scroll_delta(scroll_delta, window, cx);
3291 cx.notify();
3292 }
3293
3294 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3295 self.columnar_selection_tail.take();
3296 if self.selections.pending_anchor().is_some() {
3297 let selections = self.selections.all::<usize>(cx);
3298 self.change_selections(None, window, cx, |s| {
3299 s.select(selections);
3300 s.clear_pending();
3301 });
3302 }
3303 }
3304
3305 fn select_columns(
3306 &mut self,
3307 tail: DisplayPoint,
3308 head: DisplayPoint,
3309 goal_column: u32,
3310 display_map: &DisplaySnapshot,
3311 window: &mut Window,
3312 cx: &mut Context<Self>,
3313 ) {
3314 let start_row = cmp::min(tail.row(), head.row());
3315 let end_row = cmp::max(tail.row(), head.row());
3316 let start_column = cmp::min(tail.column(), goal_column);
3317 let end_column = cmp::max(tail.column(), goal_column);
3318 let reversed = start_column < tail.column();
3319
3320 let selection_ranges = (start_row.0..=end_row.0)
3321 .map(DisplayRow)
3322 .filter_map(|row| {
3323 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3324 let start = display_map
3325 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3326 .to_point(display_map);
3327 let end = display_map
3328 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3329 .to_point(display_map);
3330 if reversed {
3331 Some(end..start)
3332 } else {
3333 Some(start..end)
3334 }
3335 } else {
3336 None
3337 }
3338 })
3339 .collect::<Vec<_>>();
3340
3341 self.change_selections(None, window, cx, |s| {
3342 s.select_ranges(selection_ranges);
3343 });
3344 cx.notify();
3345 }
3346
3347 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3348 self.selections
3349 .all_adjusted(cx)
3350 .iter()
3351 .any(|selection| !selection.is_empty())
3352 }
3353
3354 pub fn has_pending_nonempty_selection(&self) -> bool {
3355 let pending_nonempty_selection = match self.selections.pending_anchor() {
3356 Some(Selection { start, end, .. }) => start != end,
3357 None => false,
3358 };
3359
3360 pending_nonempty_selection
3361 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3362 }
3363
3364 pub fn has_pending_selection(&self) -> bool {
3365 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3366 }
3367
3368 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3369 self.selection_mark_mode = false;
3370
3371 if self.clear_expanded_diff_hunks(cx) {
3372 cx.notify();
3373 return;
3374 }
3375 if self.dismiss_menus_and_popups(true, window, cx) {
3376 return;
3377 }
3378
3379 if self.mode.is_full()
3380 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3381 {
3382 return;
3383 }
3384
3385 cx.propagate();
3386 }
3387
3388 pub fn dismiss_menus_and_popups(
3389 &mut self,
3390 is_user_requested: bool,
3391 window: &mut Window,
3392 cx: &mut Context<Self>,
3393 ) -> bool {
3394 if self.take_rename(false, window, cx).is_some() {
3395 return true;
3396 }
3397
3398 if hide_hover(self, cx) {
3399 return true;
3400 }
3401
3402 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3403 return true;
3404 }
3405
3406 if self.hide_context_menu(window, cx).is_some() {
3407 return true;
3408 }
3409
3410 if self.mouse_context_menu.take().is_some() {
3411 return true;
3412 }
3413
3414 if is_user_requested && self.discard_inline_completion(true, cx) {
3415 return true;
3416 }
3417
3418 if self.snippet_stack.pop().is_some() {
3419 return true;
3420 }
3421
3422 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3423 self.dismiss_diagnostics(cx);
3424 return true;
3425 }
3426
3427 false
3428 }
3429
3430 fn linked_editing_ranges_for(
3431 &self,
3432 selection: Range<text::Anchor>,
3433 cx: &App,
3434 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3435 if self.linked_edit_ranges.is_empty() {
3436 return None;
3437 }
3438 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3439 selection.end.buffer_id.and_then(|end_buffer_id| {
3440 if selection.start.buffer_id != Some(end_buffer_id) {
3441 return None;
3442 }
3443 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3444 let snapshot = buffer.read(cx).snapshot();
3445 self.linked_edit_ranges
3446 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3447 .map(|ranges| (ranges, snapshot, buffer))
3448 })?;
3449 use text::ToOffset as TO;
3450 // find offset from the start of current range to current cursor position
3451 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3452
3453 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3454 let start_difference = start_offset - start_byte_offset;
3455 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3456 let end_difference = end_offset - start_byte_offset;
3457 // Current range has associated linked ranges.
3458 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3459 for range in linked_ranges.iter() {
3460 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3461 let end_offset = start_offset + end_difference;
3462 let start_offset = start_offset + start_difference;
3463 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3464 continue;
3465 }
3466 if self.selections.disjoint_anchor_ranges().any(|s| {
3467 if s.start.buffer_id != selection.start.buffer_id
3468 || s.end.buffer_id != selection.end.buffer_id
3469 {
3470 return false;
3471 }
3472 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3473 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3474 }) {
3475 continue;
3476 }
3477 let start = buffer_snapshot.anchor_after(start_offset);
3478 let end = buffer_snapshot.anchor_after(end_offset);
3479 linked_edits
3480 .entry(buffer.clone())
3481 .or_default()
3482 .push(start..end);
3483 }
3484 Some(linked_edits)
3485 }
3486
3487 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3488 let text: Arc<str> = text.into();
3489
3490 if self.read_only(cx) {
3491 return;
3492 }
3493
3494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3495
3496 let selections = self.selections.all_adjusted(cx);
3497 let mut bracket_inserted = false;
3498 let mut edits = Vec::new();
3499 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3500 let mut new_selections = Vec::with_capacity(selections.len());
3501 let mut new_autoclose_regions = Vec::new();
3502 let snapshot = self.buffer.read(cx).read(cx);
3503 let mut clear_linked_edit_ranges = false;
3504
3505 for (selection, autoclose_region) in
3506 self.selections_with_autoclose_regions(selections, &snapshot)
3507 {
3508 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3509 // Determine if the inserted text matches the opening or closing
3510 // bracket of any of this language's bracket pairs.
3511 let mut bracket_pair = None;
3512 let mut is_bracket_pair_start = false;
3513 let mut is_bracket_pair_end = false;
3514 if !text.is_empty() {
3515 let mut bracket_pair_matching_end = None;
3516 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3517 // and they are removing the character that triggered IME popup.
3518 for (pair, enabled) in scope.brackets() {
3519 if !pair.close && !pair.surround {
3520 continue;
3521 }
3522
3523 if enabled && pair.start.ends_with(text.as_ref()) {
3524 let prefix_len = pair.start.len() - text.len();
3525 let preceding_text_matches_prefix = prefix_len == 0
3526 || (selection.start.column >= (prefix_len as u32)
3527 && snapshot.contains_str_at(
3528 Point::new(
3529 selection.start.row,
3530 selection.start.column - (prefix_len as u32),
3531 ),
3532 &pair.start[..prefix_len],
3533 ));
3534 if preceding_text_matches_prefix {
3535 bracket_pair = Some(pair.clone());
3536 is_bracket_pair_start = true;
3537 break;
3538 }
3539 }
3540 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3541 {
3542 // take first bracket pair matching end, but don't break in case a later bracket
3543 // pair matches start
3544 bracket_pair_matching_end = Some(pair.clone());
3545 }
3546 }
3547 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3548 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3549 is_bracket_pair_end = true;
3550 }
3551 }
3552
3553 if let Some(bracket_pair) = bracket_pair {
3554 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3555 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3556 let auto_surround =
3557 self.use_auto_surround && snapshot_settings.use_auto_surround;
3558 if selection.is_empty() {
3559 if is_bracket_pair_start {
3560 // If the inserted text is a suffix of an opening bracket and the
3561 // selection is preceded by the rest of the opening bracket, then
3562 // insert the closing bracket.
3563 let following_text_allows_autoclose = snapshot
3564 .chars_at(selection.start)
3565 .next()
3566 .map_or(true, |c| scope.should_autoclose_before(c));
3567
3568 let preceding_text_allows_autoclose = selection.start.column == 0
3569 || snapshot.reversed_chars_at(selection.start).next().map_or(
3570 true,
3571 |c| {
3572 bracket_pair.start != bracket_pair.end
3573 || !snapshot
3574 .char_classifier_at(selection.start)
3575 .is_word(c)
3576 },
3577 );
3578
3579 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3580 && bracket_pair.start.len() == 1
3581 {
3582 let target = bracket_pair.start.chars().next().unwrap();
3583 let current_line_count = snapshot
3584 .reversed_chars_at(selection.start)
3585 .take_while(|&c| c != '\n')
3586 .filter(|&c| c == target)
3587 .count();
3588 current_line_count % 2 == 1
3589 } else {
3590 false
3591 };
3592
3593 if autoclose
3594 && bracket_pair.close
3595 && following_text_allows_autoclose
3596 && preceding_text_allows_autoclose
3597 && !is_closing_quote
3598 {
3599 let anchor = snapshot.anchor_before(selection.end);
3600 new_selections.push((selection.map(|_| anchor), text.len()));
3601 new_autoclose_regions.push((
3602 anchor,
3603 text.len(),
3604 selection.id,
3605 bracket_pair.clone(),
3606 ));
3607 edits.push((
3608 selection.range(),
3609 format!("{}{}", text, bracket_pair.end).into(),
3610 ));
3611 bracket_inserted = true;
3612 continue;
3613 }
3614 }
3615
3616 if let Some(region) = autoclose_region {
3617 // If the selection is followed by an auto-inserted closing bracket,
3618 // then don't insert that closing bracket again; just move the selection
3619 // past the closing bracket.
3620 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3621 && text.as_ref() == region.pair.end.as_str();
3622 if should_skip {
3623 let anchor = snapshot.anchor_after(selection.end);
3624 new_selections
3625 .push((selection.map(|_| anchor), region.pair.end.len()));
3626 continue;
3627 }
3628 }
3629
3630 let always_treat_brackets_as_autoclosed = snapshot
3631 .language_settings_at(selection.start, cx)
3632 .always_treat_brackets_as_autoclosed;
3633 if always_treat_brackets_as_autoclosed
3634 && is_bracket_pair_end
3635 && snapshot.contains_str_at(selection.end, text.as_ref())
3636 {
3637 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3638 // and the inserted text is a closing bracket and the selection is followed
3639 // by the closing bracket then move the selection past the closing bracket.
3640 let anchor = snapshot.anchor_after(selection.end);
3641 new_selections.push((selection.map(|_| anchor), text.len()));
3642 continue;
3643 }
3644 }
3645 // If an opening bracket is 1 character long and is typed while
3646 // text is selected, then surround that text with the bracket pair.
3647 else if auto_surround
3648 && bracket_pair.surround
3649 && is_bracket_pair_start
3650 && bracket_pair.start.chars().count() == 1
3651 {
3652 edits.push((selection.start..selection.start, text.clone()));
3653 edits.push((
3654 selection.end..selection.end,
3655 bracket_pair.end.as_str().into(),
3656 ));
3657 bracket_inserted = true;
3658 new_selections.push((
3659 Selection {
3660 id: selection.id,
3661 start: snapshot.anchor_after(selection.start),
3662 end: snapshot.anchor_before(selection.end),
3663 reversed: selection.reversed,
3664 goal: selection.goal,
3665 },
3666 0,
3667 ));
3668 continue;
3669 }
3670 }
3671 }
3672
3673 if self.auto_replace_emoji_shortcode
3674 && selection.is_empty()
3675 && text.as_ref().ends_with(':')
3676 {
3677 if let Some(possible_emoji_short_code) =
3678 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3679 {
3680 if !possible_emoji_short_code.is_empty() {
3681 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3682 let emoji_shortcode_start = Point::new(
3683 selection.start.row,
3684 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3685 );
3686
3687 // Remove shortcode from buffer
3688 edits.push((
3689 emoji_shortcode_start..selection.start,
3690 "".to_string().into(),
3691 ));
3692 new_selections.push((
3693 Selection {
3694 id: selection.id,
3695 start: snapshot.anchor_after(emoji_shortcode_start),
3696 end: snapshot.anchor_before(selection.start),
3697 reversed: selection.reversed,
3698 goal: selection.goal,
3699 },
3700 0,
3701 ));
3702
3703 // Insert emoji
3704 let selection_start_anchor = snapshot.anchor_after(selection.start);
3705 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3706 edits.push((selection.start..selection.end, emoji.to_string().into()));
3707
3708 continue;
3709 }
3710 }
3711 }
3712 }
3713
3714 // If not handling any auto-close operation, then just replace the selected
3715 // text with the given input and move the selection to the end of the
3716 // newly inserted text.
3717 let anchor = snapshot.anchor_after(selection.end);
3718 if !self.linked_edit_ranges.is_empty() {
3719 let start_anchor = snapshot.anchor_before(selection.start);
3720
3721 let is_word_char = text.chars().next().map_or(true, |char| {
3722 let classifier = snapshot
3723 .char_classifier_at(start_anchor.to_offset(&snapshot))
3724 .ignore_punctuation(true);
3725 classifier.is_word(char)
3726 });
3727
3728 if is_word_char {
3729 if let Some(ranges) = self
3730 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3731 {
3732 for (buffer, edits) in ranges {
3733 linked_edits
3734 .entry(buffer.clone())
3735 .or_default()
3736 .extend(edits.into_iter().map(|range| (range, text.clone())));
3737 }
3738 }
3739 } else {
3740 clear_linked_edit_ranges = true;
3741 }
3742 }
3743
3744 new_selections.push((selection.map(|_| anchor), 0));
3745 edits.push((selection.start..selection.end, text.clone()));
3746 }
3747
3748 drop(snapshot);
3749
3750 self.transact(window, cx, |this, window, cx| {
3751 if clear_linked_edit_ranges {
3752 this.linked_edit_ranges.clear();
3753 }
3754 let initial_buffer_versions =
3755 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3756
3757 this.buffer.update(cx, |buffer, cx| {
3758 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3759 });
3760 for (buffer, edits) in linked_edits {
3761 buffer.update(cx, |buffer, cx| {
3762 let snapshot = buffer.snapshot();
3763 let edits = edits
3764 .into_iter()
3765 .map(|(range, text)| {
3766 use text::ToPoint as TP;
3767 let end_point = TP::to_point(&range.end, &snapshot);
3768 let start_point = TP::to_point(&range.start, &snapshot);
3769 (start_point..end_point, text)
3770 })
3771 .sorted_by_key(|(range, _)| range.start);
3772 buffer.edit(edits, None, cx);
3773 })
3774 }
3775 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3776 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3777 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3778 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3779 .zip(new_selection_deltas)
3780 .map(|(selection, delta)| Selection {
3781 id: selection.id,
3782 start: selection.start + delta,
3783 end: selection.end + delta,
3784 reversed: selection.reversed,
3785 goal: SelectionGoal::None,
3786 })
3787 .collect::<Vec<_>>();
3788
3789 let mut i = 0;
3790 for (position, delta, selection_id, pair) in new_autoclose_regions {
3791 let position = position.to_offset(&map.buffer_snapshot) + delta;
3792 let start = map.buffer_snapshot.anchor_before(position);
3793 let end = map.buffer_snapshot.anchor_after(position);
3794 while let Some(existing_state) = this.autoclose_regions.get(i) {
3795 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3796 Ordering::Less => i += 1,
3797 Ordering::Greater => break,
3798 Ordering::Equal => {
3799 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3800 Ordering::Less => i += 1,
3801 Ordering::Equal => break,
3802 Ordering::Greater => break,
3803 }
3804 }
3805 }
3806 }
3807 this.autoclose_regions.insert(
3808 i,
3809 AutocloseRegion {
3810 selection_id,
3811 range: start..end,
3812 pair,
3813 },
3814 );
3815 }
3816
3817 let had_active_inline_completion = this.has_active_inline_completion();
3818 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3819 s.select(new_selections)
3820 });
3821
3822 if !bracket_inserted {
3823 if let Some(on_type_format_task) =
3824 this.trigger_on_type_formatting(text.to_string(), window, cx)
3825 {
3826 on_type_format_task.detach_and_log_err(cx);
3827 }
3828 }
3829
3830 let editor_settings = EditorSettings::get_global(cx);
3831 if bracket_inserted
3832 && (editor_settings.auto_signature_help
3833 || editor_settings.show_signature_help_after_edits)
3834 {
3835 this.show_signature_help(&ShowSignatureHelp, window, cx);
3836 }
3837
3838 let trigger_in_words =
3839 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3840 if this.hard_wrap.is_some() {
3841 let latest: Range<Point> = this.selections.newest(cx).range();
3842 if latest.is_empty()
3843 && this
3844 .buffer()
3845 .read(cx)
3846 .snapshot(cx)
3847 .line_len(MultiBufferRow(latest.start.row))
3848 == latest.start.column
3849 {
3850 this.rewrap_impl(
3851 RewrapOptions {
3852 override_language_settings: true,
3853 preserve_existing_whitespace: true,
3854 },
3855 cx,
3856 )
3857 }
3858 }
3859 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3860 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3861 this.refresh_inline_completion(true, false, window, cx);
3862 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3863 });
3864 }
3865
3866 fn find_possible_emoji_shortcode_at_position(
3867 snapshot: &MultiBufferSnapshot,
3868 position: Point,
3869 ) -> Option<String> {
3870 let mut chars = Vec::new();
3871 let mut found_colon = false;
3872 for char in snapshot.reversed_chars_at(position).take(100) {
3873 // Found a possible emoji shortcode in the middle of the buffer
3874 if found_colon {
3875 if char.is_whitespace() {
3876 chars.reverse();
3877 return Some(chars.iter().collect());
3878 }
3879 // If the previous character is not a whitespace, we are in the middle of a word
3880 // and we only want to complete the shortcode if the word is made up of other emojis
3881 let mut containing_word = String::new();
3882 for ch in snapshot
3883 .reversed_chars_at(position)
3884 .skip(chars.len() + 1)
3885 .take(100)
3886 {
3887 if ch.is_whitespace() {
3888 break;
3889 }
3890 containing_word.push(ch);
3891 }
3892 let containing_word = containing_word.chars().rev().collect::<String>();
3893 if util::word_consists_of_emojis(containing_word.as_str()) {
3894 chars.reverse();
3895 return Some(chars.iter().collect());
3896 }
3897 }
3898
3899 if char.is_whitespace() || !char.is_ascii() {
3900 return None;
3901 }
3902 if char == ':' {
3903 found_colon = true;
3904 } else {
3905 chars.push(char);
3906 }
3907 }
3908 // Found a possible emoji shortcode at the beginning of the buffer
3909 chars.reverse();
3910 Some(chars.iter().collect())
3911 }
3912
3913 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3915 self.transact(window, cx, |this, window, cx| {
3916 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3917 let selections = this.selections.all::<usize>(cx);
3918 let multi_buffer = this.buffer.read(cx);
3919 let buffer = multi_buffer.snapshot(cx);
3920 selections
3921 .iter()
3922 .map(|selection| {
3923 let start_point = selection.start.to_point(&buffer);
3924 let mut existing_indent =
3925 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3926 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3927 let start = selection.start;
3928 let end = selection.end;
3929 let selection_is_empty = start == end;
3930 let language_scope = buffer.language_scope_at(start);
3931 let (
3932 comment_delimiter,
3933 doc_delimiter,
3934 insert_extra_newline,
3935 indent_on_newline,
3936 indent_on_extra_newline,
3937 ) = if let Some(language) = &language_scope {
3938 let mut insert_extra_newline =
3939 insert_extra_newline_brackets(&buffer, start..end, language)
3940 || insert_extra_newline_tree_sitter(&buffer, start..end);
3941
3942 // Comment extension on newline is allowed only for cursor selections
3943 let comment_delimiter = maybe!({
3944 if !selection_is_empty {
3945 return None;
3946 }
3947
3948 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3949 return None;
3950 }
3951
3952 let delimiters = language.line_comment_prefixes();
3953 let max_len_of_delimiter =
3954 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3955 let (snapshot, range) =
3956 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3957
3958 let num_of_whitespaces = snapshot
3959 .chars_for_range(range.clone())
3960 .take_while(|c| c.is_whitespace())
3961 .count();
3962 let comment_candidate = snapshot
3963 .chars_for_range(range)
3964 .skip(num_of_whitespaces)
3965 .take(max_len_of_delimiter)
3966 .collect::<String>();
3967 let (delimiter, trimmed_len) =
3968 delimiters.iter().find_map(|delimiter| {
3969 let trimmed = delimiter.trim_end();
3970 if comment_candidate.starts_with(trimmed) {
3971 Some((delimiter, trimmed.len()))
3972 } else {
3973 None
3974 }
3975 })?;
3976 let cursor_is_placed_after_comment_marker =
3977 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3978 if cursor_is_placed_after_comment_marker {
3979 Some(delimiter.clone())
3980 } else {
3981 None
3982 }
3983 });
3984
3985 let mut indent_on_newline = IndentSize::spaces(0);
3986 let mut indent_on_extra_newline = IndentSize::spaces(0);
3987
3988 let doc_delimiter = maybe!({
3989 if !selection_is_empty {
3990 return None;
3991 }
3992
3993 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3994 return None;
3995 }
3996
3997 let DocumentationConfig {
3998 start: start_tag,
3999 end: end_tag,
4000 prefix: delimiter,
4001 tab_size: len,
4002 } = language.documentation()?;
4003
4004 let (snapshot, range) =
4005 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4006
4007 let num_of_whitespaces = snapshot
4008 .chars_for_range(range.clone())
4009 .take_while(|c| c.is_whitespace())
4010 .count();
4011
4012 let cursor_is_after_start_tag = {
4013 let start_tag_len = start_tag.len();
4014 let start_tag_line = snapshot
4015 .chars_for_range(range.clone())
4016 .skip(num_of_whitespaces)
4017 .take(start_tag_len)
4018 .collect::<String>();
4019 if start_tag_line.starts_with(start_tag.as_ref()) {
4020 num_of_whitespaces + start_tag_len
4021 <= start_point.column as usize
4022 } else {
4023 false
4024 }
4025 };
4026
4027 let cursor_is_after_delimiter = {
4028 let delimiter_trim = delimiter.trim_end();
4029 let delimiter_line = snapshot
4030 .chars_for_range(range.clone())
4031 .skip(num_of_whitespaces)
4032 .take(delimiter_trim.len())
4033 .collect::<String>();
4034 if delimiter_line.starts_with(delimiter_trim) {
4035 num_of_whitespaces + delimiter_trim.len()
4036 <= start_point.column as usize
4037 } else {
4038 false
4039 }
4040 };
4041
4042 let cursor_is_before_end_tag_if_exists = {
4043 let num_of_whitespaces_rev = snapshot
4044 .reversed_chars_for_range(range.clone())
4045 .take_while(|c| c.is_whitespace())
4046 .count();
4047 let mut line_iter = snapshot
4048 .reversed_chars_for_range(range)
4049 .skip(num_of_whitespaces_rev);
4050 let end_tag_exists = end_tag
4051 .chars()
4052 .rev()
4053 .all(|char| line_iter.next() == Some(char));
4054 if end_tag_exists {
4055 let max_point = snapshot.line_len(start_point.row) as usize;
4056 let ordering = (num_of_whitespaces_rev
4057 + end_tag.len()
4058 + start_point.column as usize)
4059 .cmp(&max_point);
4060 let cursor_is_before_end_tag =
4061 ordering != Ordering::Greater;
4062 if cursor_is_after_start_tag {
4063 if cursor_is_before_end_tag {
4064 insert_extra_newline = true;
4065 }
4066 let cursor_is_at_start_of_end_tag =
4067 ordering == Ordering::Equal;
4068 if cursor_is_at_start_of_end_tag {
4069 indent_on_extra_newline.len = (*len).into();
4070 }
4071 }
4072 cursor_is_before_end_tag
4073 } else {
4074 true
4075 }
4076 };
4077
4078 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4079 && cursor_is_before_end_tag_if_exists
4080 {
4081 if cursor_is_after_start_tag {
4082 indent_on_newline.len = (*len).into();
4083 }
4084 Some(delimiter.clone())
4085 } else {
4086 None
4087 }
4088 });
4089
4090 (
4091 comment_delimiter,
4092 doc_delimiter,
4093 insert_extra_newline,
4094 indent_on_newline,
4095 indent_on_extra_newline,
4096 )
4097 } else {
4098 (
4099 None,
4100 None,
4101 false,
4102 IndentSize::default(),
4103 IndentSize::default(),
4104 )
4105 };
4106
4107 let prevent_auto_indent = doc_delimiter.is_some();
4108 let delimiter = comment_delimiter.or(doc_delimiter);
4109
4110 let capacity_for_delimiter =
4111 delimiter.as_deref().map(str::len).unwrap_or_default();
4112 let mut new_text = String::with_capacity(
4113 1 + capacity_for_delimiter
4114 + existing_indent.len as usize
4115 + indent_on_newline.len as usize
4116 + indent_on_extra_newline.len as usize,
4117 );
4118 new_text.push('\n');
4119 new_text.extend(existing_indent.chars());
4120 new_text.extend(indent_on_newline.chars());
4121
4122 if let Some(delimiter) = &delimiter {
4123 new_text.push_str(delimiter);
4124 }
4125
4126 if insert_extra_newline {
4127 new_text.push('\n');
4128 new_text.extend(existing_indent.chars());
4129 new_text.extend(indent_on_extra_newline.chars());
4130 }
4131
4132 let anchor = buffer.anchor_after(end);
4133 let new_selection = selection.map(|_| anchor);
4134 (
4135 ((start..end, new_text), prevent_auto_indent),
4136 (insert_extra_newline, new_selection),
4137 )
4138 })
4139 .unzip()
4140 };
4141
4142 let mut auto_indent_edits = Vec::new();
4143 let mut edits = Vec::new();
4144 for (edit, prevent_auto_indent) in edits_with_flags {
4145 if prevent_auto_indent {
4146 edits.push(edit);
4147 } else {
4148 auto_indent_edits.push(edit);
4149 }
4150 }
4151 if !edits.is_empty() {
4152 this.edit(edits, cx);
4153 }
4154 if !auto_indent_edits.is_empty() {
4155 this.edit_with_autoindent(auto_indent_edits, cx);
4156 }
4157
4158 let buffer = this.buffer.read(cx).snapshot(cx);
4159 let new_selections = selection_info
4160 .into_iter()
4161 .map(|(extra_newline_inserted, new_selection)| {
4162 let mut cursor = new_selection.end.to_point(&buffer);
4163 if extra_newline_inserted {
4164 cursor.row -= 1;
4165 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4166 }
4167 new_selection.map(|_| cursor)
4168 })
4169 .collect();
4170
4171 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4172 s.select(new_selections)
4173 });
4174 this.refresh_inline_completion(true, false, window, cx);
4175 });
4176 }
4177
4178 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4179 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4180
4181 let buffer = self.buffer.read(cx);
4182 let snapshot = buffer.snapshot(cx);
4183
4184 let mut edits = Vec::new();
4185 let mut rows = Vec::new();
4186
4187 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4188 let cursor = selection.head();
4189 let row = cursor.row;
4190
4191 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4192
4193 let newline = "\n".to_string();
4194 edits.push((start_of_line..start_of_line, newline));
4195
4196 rows.push(row + rows_inserted as u32);
4197 }
4198
4199 self.transact(window, cx, |editor, window, cx| {
4200 editor.edit(edits, cx);
4201
4202 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4203 let mut index = 0;
4204 s.move_cursors_with(|map, _, _| {
4205 let row = rows[index];
4206 index += 1;
4207
4208 let point = Point::new(row, 0);
4209 let boundary = map.next_line_boundary(point).1;
4210 let clipped = map.clip_point(boundary, Bias::Left);
4211
4212 (clipped, SelectionGoal::None)
4213 });
4214 });
4215
4216 let mut indent_edits = Vec::new();
4217 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4218 for row in rows {
4219 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4220 for (row, indent) in indents {
4221 if indent.len == 0 {
4222 continue;
4223 }
4224
4225 let text = match indent.kind {
4226 IndentKind::Space => " ".repeat(indent.len as usize),
4227 IndentKind::Tab => "\t".repeat(indent.len as usize),
4228 };
4229 let point = Point::new(row.0, 0);
4230 indent_edits.push((point..point, text));
4231 }
4232 }
4233 editor.edit(indent_edits, cx);
4234 });
4235 }
4236
4237 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4238 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4239
4240 let buffer = self.buffer.read(cx);
4241 let snapshot = buffer.snapshot(cx);
4242
4243 let mut edits = Vec::new();
4244 let mut rows = Vec::new();
4245 let mut rows_inserted = 0;
4246
4247 for selection in self.selections.all_adjusted(cx) {
4248 let cursor = selection.head();
4249 let row = cursor.row;
4250
4251 let point = Point::new(row + 1, 0);
4252 let start_of_line = snapshot.clip_point(point, Bias::Left);
4253
4254 let newline = "\n".to_string();
4255 edits.push((start_of_line..start_of_line, newline));
4256
4257 rows_inserted += 1;
4258 rows.push(row + rows_inserted);
4259 }
4260
4261 self.transact(window, cx, |editor, window, cx| {
4262 editor.edit(edits, cx);
4263
4264 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4265 let mut index = 0;
4266 s.move_cursors_with(|map, _, _| {
4267 let row = rows[index];
4268 index += 1;
4269
4270 let point = Point::new(row, 0);
4271 let boundary = map.next_line_boundary(point).1;
4272 let clipped = map.clip_point(boundary, Bias::Left);
4273
4274 (clipped, SelectionGoal::None)
4275 });
4276 });
4277
4278 let mut indent_edits = Vec::new();
4279 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4280 for row in rows {
4281 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4282 for (row, indent) in indents {
4283 if indent.len == 0 {
4284 continue;
4285 }
4286
4287 let text = match indent.kind {
4288 IndentKind::Space => " ".repeat(indent.len as usize),
4289 IndentKind::Tab => "\t".repeat(indent.len as usize),
4290 };
4291 let point = Point::new(row.0, 0);
4292 indent_edits.push((point..point, text));
4293 }
4294 }
4295 editor.edit(indent_edits, cx);
4296 });
4297 }
4298
4299 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4300 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4301 original_indent_columns: Vec::new(),
4302 });
4303 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4304 }
4305
4306 fn insert_with_autoindent_mode(
4307 &mut self,
4308 text: &str,
4309 autoindent_mode: Option<AutoindentMode>,
4310 window: &mut Window,
4311 cx: &mut Context<Self>,
4312 ) {
4313 if self.read_only(cx) {
4314 return;
4315 }
4316
4317 let text: Arc<str> = text.into();
4318 self.transact(window, cx, |this, window, cx| {
4319 let old_selections = this.selections.all_adjusted(cx);
4320 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4321 let anchors = {
4322 let snapshot = buffer.read(cx);
4323 old_selections
4324 .iter()
4325 .map(|s| {
4326 let anchor = snapshot.anchor_after(s.head());
4327 s.map(|_| anchor)
4328 })
4329 .collect::<Vec<_>>()
4330 };
4331 buffer.edit(
4332 old_selections
4333 .iter()
4334 .map(|s| (s.start..s.end, text.clone())),
4335 autoindent_mode,
4336 cx,
4337 );
4338 anchors
4339 });
4340
4341 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4342 s.select_anchors(selection_anchors);
4343 });
4344
4345 cx.notify();
4346 });
4347 }
4348
4349 fn trigger_completion_on_input(
4350 &mut self,
4351 text: &str,
4352 trigger_in_words: bool,
4353 window: &mut Window,
4354 cx: &mut Context<Self>,
4355 ) {
4356 let ignore_completion_provider = self
4357 .context_menu
4358 .borrow()
4359 .as_ref()
4360 .map(|menu| match menu {
4361 CodeContextMenu::Completions(completions_menu) => {
4362 completions_menu.ignore_completion_provider
4363 }
4364 CodeContextMenu::CodeActions(_) => false,
4365 })
4366 .unwrap_or(false);
4367
4368 if ignore_completion_provider {
4369 self.show_word_completions(&ShowWordCompletions, window, cx);
4370 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4371 self.show_completions(
4372 &ShowCompletions {
4373 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4374 },
4375 window,
4376 cx,
4377 );
4378 } else {
4379 self.hide_context_menu(window, cx);
4380 }
4381 }
4382
4383 fn is_completion_trigger(
4384 &self,
4385 text: &str,
4386 trigger_in_words: bool,
4387 cx: &mut Context<Self>,
4388 ) -> bool {
4389 let position = self.selections.newest_anchor().head();
4390 let multibuffer = self.buffer.read(cx);
4391 let Some(buffer) = position
4392 .buffer_id
4393 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4394 else {
4395 return false;
4396 };
4397
4398 if let Some(completion_provider) = &self.completion_provider {
4399 completion_provider.is_completion_trigger(
4400 &buffer,
4401 position.text_anchor,
4402 text,
4403 trigger_in_words,
4404 cx,
4405 )
4406 } else {
4407 false
4408 }
4409 }
4410
4411 /// If any empty selections is touching the start of its innermost containing autoclose
4412 /// region, expand it to select the brackets.
4413 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4414 let selections = self.selections.all::<usize>(cx);
4415 let buffer = self.buffer.read(cx).read(cx);
4416 let new_selections = self
4417 .selections_with_autoclose_regions(selections, &buffer)
4418 .map(|(mut selection, region)| {
4419 if !selection.is_empty() {
4420 return selection;
4421 }
4422
4423 if let Some(region) = region {
4424 let mut range = region.range.to_offset(&buffer);
4425 if selection.start == range.start && range.start >= region.pair.start.len() {
4426 range.start -= region.pair.start.len();
4427 if buffer.contains_str_at(range.start, ®ion.pair.start)
4428 && buffer.contains_str_at(range.end, ®ion.pair.end)
4429 {
4430 range.end += region.pair.end.len();
4431 selection.start = range.start;
4432 selection.end = range.end;
4433
4434 return selection;
4435 }
4436 }
4437 }
4438
4439 let always_treat_brackets_as_autoclosed = buffer
4440 .language_settings_at(selection.start, cx)
4441 .always_treat_brackets_as_autoclosed;
4442
4443 if !always_treat_brackets_as_autoclosed {
4444 return selection;
4445 }
4446
4447 if let Some(scope) = buffer.language_scope_at(selection.start) {
4448 for (pair, enabled) in scope.brackets() {
4449 if !enabled || !pair.close {
4450 continue;
4451 }
4452
4453 if buffer.contains_str_at(selection.start, &pair.end) {
4454 let pair_start_len = pair.start.len();
4455 if buffer.contains_str_at(
4456 selection.start.saturating_sub(pair_start_len),
4457 &pair.start,
4458 ) {
4459 selection.start -= pair_start_len;
4460 selection.end += pair.end.len();
4461
4462 return selection;
4463 }
4464 }
4465 }
4466 }
4467
4468 selection
4469 })
4470 .collect();
4471
4472 drop(buffer);
4473 self.change_selections(None, window, cx, |selections| {
4474 selections.select(new_selections)
4475 });
4476 }
4477
4478 /// Iterate the given selections, and for each one, find the smallest surrounding
4479 /// autoclose region. This uses the ordering of the selections and the autoclose
4480 /// regions to avoid repeated comparisons.
4481 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4482 &'a self,
4483 selections: impl IntoIterator<Item = Selection<D>>,
4484 buffer: &'a MultiBufferSnapshot,
4485 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4486 let mut i = 0;
4487 let mut regions = self.autoclose_regions.as_slice();
4488 selections.into_iter().map(move |selection| {
4489 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4490
4491 let mut enclosing = None;
4492 while let Some(pair_state) = regions.get(i) {
4493 if pair_state.range.end.to_offset(buffer) < range.start {
4494 regions = ®ions[i + 1..];
4495 i = 0;
4496 } else if pair_state.range.start.to_offset(buffer) > range.end {
4497 break;
4498 } else {
4499 if pair_state.selection_id == selection.id {
4500 enclosing = Some(pair_state);
4501 }
4502 i += 1;
4503 }
4504 }
4505
4506 (selection, enclosing)
4507 })
4508 }
4509
4510 /// Remove any autoclose regions that no longer contain their selection.
4511 fn invalidate_autoclose_regions(
4512 &mut self,
4513 mut selections: &[Selection<Anchor>],
4514 buffer: &MultiBufferSnapshot,
4515 ) {
4516 self.autoclose_regions.retain(|state| {
4517 let mut i = 0;
4518 while let Some(selection) = selections.get(i) {
4519 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4520 selections = &selections[1..];
4521 continue;
4522 }
4523 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4524 break;
4525 }
4526 if selection.id == state.selection_id {
4527 return true;
4528 } else {
4529 i += 1;
4530 }
4531 }
4532 false
4533 });
4534 }
4535
4536 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4537 let offset = position.to_offset(buffer);
4538 let (word_range, kind) = buffer.surrounding_word(offset, true);
4539 if offset > word_range.start && kind == Some(CharKind::Word) {
4540 Some(
4541 buffer
4542 .text_for_range(word_range.start..offset)
4543 .collect::<String>(),
4544 )
4545 } else {
4546 None
4547 }
4548 }
4549
4550 pub fn toggle_inline_values(
4551 &mut self,
4552 _: &ToggleInlineValues,
4553 _: &mut Window,
4554 cx: &mut Context<Self>,
4555 ) {
4556 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4557
4558 self.refresh_inline_values(cx);
4559 }
4560
4561 pub fn toggle_inlay_hints(
4562 &mut self,
4563 _: &ToggleInlayHints,
4564 _: &mut Window,
4565 cx: &mut Context<Self>,
4566 ) {
4567 self.refresh_inlay_hints(
4568 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4569 cx,
4570 );
4571 }
4572
4573 pub fn inlay_hints_enabled(&self) -> bool {
4574 self.inlay_hint_cache.enabled
4575 }
4576
4577 pub fn inline_values_enabled(&self) -> bool {
4578 self.inline_value_cache.enabled
4579 }
4580
4581 #[cfg(any(test, feature = "test-support"))]
4582 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4583 self.display_map
4584 .read(cx)
4585 .current_inlays()
4586 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4587 .cloned()
4588 .collect()
4589 }
4590
4591 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4592 if self.semantics_provider.is_none() || !self.mode.is_full() {
4593 return;
4594 }
4595
4596 let reason_description = reason.description();
4597 let ignore_debounce = matches!(
4598 reason,
4599 InlayHintRefreshReason::SettingsChange(_)
4600 | InlayHintRefreshReason::Toggle(_)
4601 | InlayHintRefreshReason::ExcerptsRemoved(_)
4602 | InlayHintRefreshReason::ModifiersChanged(_)
4603 );
4604 let (invalidate_cache, required_languages) = match reason {
4605 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4606 match self.inlay_hint_cache.modifiers_override(enabled) {
4607 Some(enabled) => {
4608 if enabled {
4609 (InvalidationStrategy::RefreshRequested, None)
4610 } else {
4611 self.splice_inlays(
4612 &self
4613 .visible_inlay_hints(cx)
4614 .iter()
4615 .map(|inlay| inlay.id)
4616 .collect::<Vec<InlayId>>(),
4617 Vec::new(),
4618 cx,
4619 );
4620 return;
4621 }
4622 }
4623 None => return,
4624 }
4625 }
4626 InlayHintRefreshReason::Toggle(enabled) => {
4627 if self.inlay_hint_cache.toggle(enabled) {
4628 if enabled {
4629 (InvalidationStrategy::RefreshRequested, None)
4630 } else {
4631 self.splice_inlays(
4632 &self
4633 .visible_inlay_hints(cx)
4634 .iter()
4635 .map(|inlay| inlay.id)
4636 .collect::<Vec<InlayId>>(),
4637 Vec::new(),
4638 cx,
4639 );
4640 return;
4641 }
4642 } else {
4643 return;
4644 }
4645 }
4646 InlayHintRefreshReason::SettingsChange(new_settings) => {
4647 match self.inlay_hint_cache.update_settings(
4648 &self.buffer,
4649 new_settings,
4650 self.visible_inlay_hints(cx),
4651 cx,
4652 ) {
4653 ControlFlow::Break(Some(InlaySplice {
4654 to_remove,
4655 to_insert,
4656 })) => {
4657 self.splice_inlays(&to_remove, to_insert, cx);
4658 return;
4659 }
4660 ControlFlow::Break(None) => return,
4661 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4662 }
4663 }
4664 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4665 if let Some(InlaySplice {
4666 to_remove,
4667 to_insert,
4668 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4669 {
4670 self.splice_inlays(&to_remove, to_insert, cx);
4671 }
4672 self.display_map.update(cx, |display_map, _| {
4673 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4674 });
4675 return;
4676 }
4677 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4678 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4679 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4680 }
4681 InlayHintRefreshReason::RefreshRequested => {
4682 (InvalidationStrategy::RefreshRequested, None)
4683 }
4684 };
4685
4686 if let Some(InlaySplice {
4687 to_remove,
4688 to_insert,
4689 }) = self.inlay_hint_cache.spawn_hint_refresh(
4690 reason_description,
4691 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4692 invalidate_cache,
4693 ignore_debounce,
4694 cx,
4695 ) {
4696 self.splice_inlays(&to_remove, to_insert, cx);
4697 }
4698 }
4699
4700 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4701 self.display_map
4702 .read(cx)
4703 .current_inlays()
4704 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4705 .cloned()
4706 .collect()
4707 }
4708
4709 pub fn excerpts_for_inlay_hints_query(
4710 &self,
4711 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4712 cx: &mut Context<Editor>,
4713 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4714 let Some(project) = self.project.as_ref() else {
4715 return HashMap::default();
4716 };
4717 let project = project.read(cx);
4718 let multi_buffer = self.buffer().read(cx);
4719 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4720 let multi_buffer_visible_start = self
4721 .scroll_manager
4722 .anchor()
4723 .anchor
4724 .to_point(&multi_buffer_snapshot);
4725 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4726 multi_buffer_visible_start
4727 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4728 Bias::Left,
4729 );
4730 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4731 multi_buffer_snapshot
4732 .range_to_buffer_ranges(multi_buffer_visible_range)
4733 .into_iter()
4734 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4735 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4736 let buffer_file = project::File::from_dyn(buffer.file())?;
4737 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4738 let worktree_entry = buffer_worktree
4739 .read(cx)
4740 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4741 if worktree_entry.is_ignored {
4742 return None;
4743 }
4744
4745 let language = buffer.language()?;
4746 if let Some(restrict_to_languages) = restrict_to_languages {
4747 if !restrict_to_languages.contains(language) {
4748 return None;
4749 }
4750 }
4751 Some((
4752 excerpt_id,
4753 (
4754 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4755 buffer.version().clone(),
4756 excerpt_visible_range,
4757 ),
4758 ))
4759 })
4760 .collect()
4761 }
4762
4763 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4764 TextLayoutDetails {
4765 text_system: window.text_system().clone(),
4766 editor_style: self.style.clone().unwrap(),
4767 rem_size: window.rem_size(),
4768 scroll_anchor: self.scroll_manager.anchor(),
4769 visible_rows: self.visible_line_count(),
4770 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4771 }
4772 }
4773
4774 pub fn splice_inlays(
4775 &self,
4776 to_remove: &[InlayId],
4777 to_insert: Vec<Inlay>,
4778 cx: &mut Context<Self>,
4779 ) {
4780 self.display_map.update(cx, |display_map, cx| {
4781 display_map.splice_inlays(to_remove, to_insert, cx)
4782 });
4783 cx.notify();
4784 }
4785
4786 fn trigger_on_type_formatting(
4787 &self,
4788 input: String,
4789 window: &mut Window,
4790 cx: &mut Context<Self>,
4791 ) -> Option<Task<Result<()>>> {
4792 if input.len() != 1 {
4793 return None;
4794 }
4795
4796 let project = self.project.as_ref()?;
4797 let position = self.selections.newest_anchor().head();
4798 let (buffer, buffer_position) = self
4799 .buffer
4800 .read(cx)
4801 .text_anchor_for_position(position, cx)?;
4802
4803 let settings = language_settings::language_settings(
4804 buffer
4805 .read(cx)
4806 .language_at(buffer_position)
4807 .map(|l| l.name()),
4808 buffer.read(cx).file(),
4809 cx,
4810 );
4811 if !settings.use_on_type_format {
4812 return None;
4813 }
4814
4815 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4816 // hence we do LSP request & edit on host side only — add formats to host's history.
4817 let push_to_lsp_host_history = true;
4818 // If this is not the host, append its history with new edits.
4819 let push_to_client_history = project.read(cx).is_via_collab();
4820
4821 let on_type_formatting = project.update(cx, |project, cx| {
4822 project.on_type_format(
4823 buffer.clone(),
4824 buffer_position,
4825 input,
4826 push_to_lsp_host_history,
4827 cx,
4828 )
4829 });
4830 Some(cx.spawn_in(window, async move |editor, cx| {
4831 if let Some(transaction) = on_type_formatting.await? {
4832 if push_to_client_history {
4833 buffer
4834 .update(cx, |buffer, _| {
4835 buffer.push_transaction(transaction, Instant::now());
4836 buffer.finalize_last_transaction();
4837 })
4838 .ok();
4839 }
4840 editor.update(cx, |editor, cx| {
4841 editor.refresh_document_highlights(cx);
4842 })?;
4843 }
4844 Ok(())
4845 }))
4846 }
4847
4848 pub fn show_word_completions(
4849 &mut self,
4850 _: &ShowWordCompletions,
4851 window: &mut Window,
4852 cx: &mut Context<Self>,
4853 ) {
4854 self.open_completions_menu(true, None, window, cx);
4855 }
4856
4857 pub fn show_completions(
4858 &mut self,
4859 options: &ShowCompletions,
4860 window: &mut Window,
4861 cx: &mut Context<Self>,
4862 ) {
4863 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4864 }
4865
4866 fn open_completions_menu(
4867 &mut self,
4868 ignore_completion_provider: bool,
4869 trigger: Option<&str>,
4870 window: &mut Window,
4871 cx: &mut Context<Self>,
4872 ) {
4873 if self.pending_rename.is_some() {
4874 return;
4875 }
4876 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4877 return;
4878 }
4879
4880 let position = self.selections.newest_anchor().head();
4881 if position.diff_base_anchor.is_some() {
4882 return;
4883 }
4884 let (buffer, buffer_position) =
4885 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4886 output
4887 } else {
4888 return;
4889 };
4890 let buffer_snapshot = buffer.read(cx).snapshot();
4891 let show_completion_documentation = buffer_snapshot
4892 .settings_at(buffer_position, cx)
4893 .show_completion_documentation;
4894
4895 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4896
4897 let trigger_kind = match trigger {
4898 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4899 CompletionTriggerKind::TRIGGER_CHARACTER
4900 }
4901 _ => CompletionTriggerKind::INVOKED,
4902 };
4903 let completion_context = CompletionContext {
4904 trigger_character: trigger.and_then(|trigger| {
4905 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4906 Some(String::from(trigger))
4907 } else {
4908 None
4909 }
4910 }),
4911 trigger_kind,
4912 };
4913
4914 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4915 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4916 let word_to_exclude = buffer_snapshot
4917 .text_for_range(old_range.clone())
4918 .collect::<String>();
4919 (
4920 buffer_snapshot.anchor_before(old_range.start)
4921 ..buffer_snapshot.anchor_after(old_range.end),
4922 Some(word_to_exclude),
4923 )
4924 } else {
4925 (buffer_position..buffer_position, None)
4926 };
4927
4928 let completion_settings = language_settings(
4929 buffer_snapshot
4930 .language_at(buffer_position)
4931 .map(|language| language.name()),
4932 buffer_snapshot.file(),
4933 cx,
4934 )
4935 .completions;
4936
4937 // The document can be large, so stay in reasonable bounds when searching for words,
4938 // otherwise completion pop-up might be slow to appear.
4939 const WORD_LOOKUP_ROWS: u32 = 5_000;
4940 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4941 let min_word_search = buffer_snapshot.clip_point(
4942 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4943 Bias::Left,
4944 );
4945 let max_word_search = buffer_snapshot.clip_point(
4946 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4947 Bias::Right,
4948 );
4949 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4950 ..buffer_snapshot.point_to_offset(max_word_search);
4951
4952 let provider = self
4953 .completion_provider
4954 .as_ref()
4955 .filter(|_| !ignore_completion_provider);
4956 let skip_digits = query
4957 .as_ref()
4958 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4959
4960 let (mut words, provided_completions) = match provider {
4961 Some(provider) => {
4962 let completions = provider.completions(
4963 position.excerpt_id,
4964 &buffer,
4965 buffer_position,
4966 completion_context,
4967 window,
4968 cx,
4969 );
4970
4971 let words = match completion_settings.words {
4972 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4973 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4974 .background_spawn(async move {
4975 buffer_snapshot.words_in_range(WordsQuery {
4976 fuzzy_contents: None,
4977 range: word_search_range,
4978 skip_digits,
4979 })
4980 }),
4981 };
4982
4983 (words, completions)
4984 }
4985 None => (
4986 cx.background_spawn(async move {
4987 buffer_snapshot.words_in_range(WordsQuery {
4988 fuzzy_contents: None,
4989 range: word_search_range,
4990 skip_digits,
4991 })
4992 }),
4993 Task::ready(Ok(None)),
4994 ),
4995 };
4996
4997 let sort_completions = provider
4998 .as_ref()
4999 .map_or(false, |provider| provider.sort_completions());
5000
5001 let filter_completions = provider
5002 .as_ref()
5003 .map_or(true, |provider| provider.filter_completions());
5004
5005 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5006
5007 let id = post_inc(&mut self.next_completion_id);
5008 let task = cx.spawn_in(window, async move |editor, cx| {
5009 async move {
5010 editor.update(cx, |this, _| {
5011 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5012 })?;
5013
5014 let mut completions = Vec::new();
5015 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5016 completions.extend(provided_completions);
5017 if completion_settings.words == WordsCompletionMode::Fallback {
5018 words = Task::ready(BTreeMap::default());
5019 }
5020 }
5021
5022 let mut words = words.await;
5023 if let Some(word_to_exclude) = &word_to_exclude {
5024 words.remove(word_to_exclude);
5025 }
5026 for lsp_completion in &completions {
5027 words.remove(&lsp_completion.new_text);
5028 }
5029 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5030 replace_range: old_range.clone(),
5031 new_text: word.clone(),
5032 label: CodeLabel::plain(word, None),
5033 icon_path: None,
5034 documentation: None,
5035 source: CompletionSource::BufferWord {
5036 word_range,
5037 resolved: false,
5038 },
5039 insert_text_mode: Some(InsertTextMode::AS_IS),
5040 confirm: None,
5041 }));
5042
5043 let menu = if completions.is_empty() {
5044 None
5045 } else {
5046 let mut menu = CompletionsMenu::new(
5047 id,
5048 sort_completions,
5049 show_completion_documentation,
5050 ignore_completion_provider,
5051 position,
5052 buffer.clone(),
5053 completions.into(),
5054 snippet_sort_order,
5055 );
5056
5057 menu.filter(
5058 if filter_completions {
5059 query.as_deref()
5060 } else {
5061 None
5062 },
5063 cx.background_executor().clone(),
5064 )
5065 .await;
5066
5067 menu.visible().then_some(menu)
5068 };
5069
5070 editor.update_in(cx, |editor, window, cx| {
5071 match editor.context_menu.borrow().as_ref() {
5072 None => {}
5073 Some(CodeContextMenu::Completions(prev_menu)) => {
5074 if prev_menu.id > id {
5075 return;
5076 }
5077 }
5078 _ => return,
5079 }
5080
5081 if editor.focus_handle.is_focused(window) && menu.is_some() {
5082 let mut menu = menu.unwrap();
5083 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5084 crate::hover_popover::hide_hover(editor, cx);
5085 *editor.context_menu.borrow_mut() =
5086 Some(CodeContextMenu::Completions(menu));
5087
5088 if editor.show_edit_predictions_in_menu() {
5089 editor.update_visible_inline_completion(window, cx);
5090 } else {
5091 editor.discard_inline_completion(false, cx);
5092 }
5093
5094 cx.notify();
5095 } else if editor.completion_tasks.len() <= 1 {
5096 // If there are no more completion tasks and the last menu was
5097 // empty, we should hide it.
5098 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5099 // If it was already hidden and we don't show inline
5100 // completions in the menu, we should also show the
5101 // inline-completion when available.
5102 if was_hidden && editor.show_edit_predictions_in_menu() {
5103 editor.update_visible_inline_completion(window, cx);
5104 }
5105 }
5106 })?;
5107
5108 anyhow::Ok(())
5109 }
5110 .log_err()
5111 .await
5112 });
5113
5114 self.completion_tasks.push((id, task));
5115 }
5116
5117 #[cfg(feature = "test-support")]
5118 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5119 let menu = self.context_menu.borrow();
5120 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5121 let completions = menu.completions.borrow();
5122 Some(completions.to_vec())
5123 } else {
5124 None
5125 }
5126 }
5127
5128 pub fn confirm_completion(
5129 &mut self,
5130 action: &ConfirmCompletion,
5131 window: &mut Window,
5132 cx: &mut Context<Self>,
5133 ) -> Option<Task<Result<()>>> {
5134 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5135 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5136 }
5137
5138 pub fn confirm_completion_insert(
5139 &mut self,
5140 _: &ConfirmCompletionInsert,
5141 window: &mut Window,
5142 cx: &mut Context<Self>,
5143 ) -> Option<Task<Result<()>>> {
5144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5145 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5146 }
5147
5148 pub fn confirm_completion_replace(
5149 &mut self,
5150 _: &ConfirmCompletionReplace,
5151 window: &mut Window,
5152 cx: &mut Context<Self>,
5153 ) -> Option<Task<Result<()>>> {
5154 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5155 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5156 }
5157
5158 pub fn compose_completion(
5159 &mut self,
5160 action: &ComposeCompletion,
5161 window: &mut Window,
5162 cx: &mut Context<Self>,
5163 ) -> Option<Task<Result<()>>> {
5164 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5165 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5166 }
5167
5168 fn do_completion(
5169 &mut self,
5170 item_ix: Option<usize>,
5171 intent: CompletionIntent,
5172 window: &mut Window,
5173 cx: &mut Context<Editor>,
5174 ) -> Option<Task<Result<()>>> {
5175 use language::ToOffset as _;
5176
5177 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5178 else {
5179 return None;
5180 };
5181
5182 let candidate_id = {
5183 let entries = completions_menu.entries.borrow();
5184 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5185 if self.show_edit_predictions_in_menu() {
5186 self.discard_inline_completion(true, cx);
5187 }
5188 mat.candidate_id
5189 };
5190
5191 let buffer_handle = completions_menu.buffer;
5192 let completion = completions_menu
5193 .completions
5194 .borrow()
5195 .get(candidate_id)?
5196 .clone();
5197 cx.stop_propagation();
5198
5199 let snapshot = self.buffer.read(cx).snapshot(cx);
5200 let newest_anchor = self.selections.newest_anchor();
5201
5202 let snippet;
5203 let new_text;
5204 if completion.is_snippet() {
5205 let mut snippet_source = completion.new_text.clone();
5206 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5207 if scope.prefers_label_for_snippet_in_completion() {
5208 if let Some(label) = completion.label() {
5209 if matches!(
5210 completion.kind(),
5211 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5212 ) {
5213 snippet_source = label;
5214 }
5215 }
5216 }
5217 }
5218 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5219 new_text = snippet.as_ref().unwrap().text.clone();
5220 } else {
5221 snippet = None;
5222 new_text = completion.new_text.clone();
5223 };
5224
5225 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5226 let buffer = buffer_handle.read(cx);
5227 let replace_range_multibuffer = {
5228 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5229 let multibuffer_anchor = snapshot
5230 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5231 .unwrap()
5232 ..snapshot
5233 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5234 .unwrap();
5235 multibuffer_anchor.start.to_offset(&snapshot)
5236 ..multibuffer_anchor.end.to_offset(&snapshot)
5237 };
5238 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5239 return None;
5240 }
5241
5242 let old_text = buffer
5243 .text_for_range(replace_range.clone())
5244 .collect::<String>();
5245 let lookbehind = newest_anchor
5246 .start
5247 .text_anchor
5248 .to_offset(buffer)
5249 .saturating_sub(replace_range.start);
5250 let lookahead = replace_range
5251 .end
5252 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5253 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5254 let suffix = &old_text[lookbehind.min(old_text.len())..];
5255
5256 let selections = self.selections.all::<usize>(cx);
5257 let mut ranges = Vec::new();
5258 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5259
5260 for selection in &selections {
5261 let range = if selection.id == newest_anchor.id {
5262 replace_range_multibuffer.clone()
5263 } else {
5264 let mut range = selection.range();
5265
5266 // if prefix is present, don't duplicate it
5267 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5268 range.start = range.start.saturating_sub(lookbehind);
5269
5270 // if suffix is also present, mimic the newest cursor and replace it
5271 if selection.id != newest_anchor.id
5272 && snapshot.contains_str_at(range.end, suffix)
5273 {
5274 range.end += lookahead;
5275 }
5276 }
5277 range
5278 };
5279
5280 ranges.push(range.clone());
5281
5282 if !self.linked_edit_ranges.is_empty() {
5283 let start_anchor = snapshot.anchor_before(range.start);
5284 let end_anchor = snapshot.anchor_after(range.end);
5285 if let Some(ranges) = self
5286 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5287 {
5288 for (buffer, edits) in ranges {
5289 linked_edits
5290 .entry(buffer.clone())
5291 .or_default()
5292 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5293 }
5294 }
5295 }
5296 }
5297
5298 cx.emit(EditorEvent::InputHandled {
5299 utf16_range_to_replace: None,
5300 text: new_text.clone().into(),
5301 });
5302
5303 self.transact(window, cx, |this, window, cx| {
5304 if let Some(mut snippet) = snippet {
5305 snippet.text = new_text.to_string();
5306 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5307 } else {
5308 this.buffer.update(cx, |buffer, cx| {
5309 let auto_indent = match completion.insert_text_mode {
5310 Some(InsertTextMode::AS_IS) => None,
5311 _ => this.autoindent_mode.clone(),
5312 };
5313 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5314 buffer.edit(edits, auto_indent, cx);
5315 });
5316 }
5317 for (buffer, edits) in linked_edits {
5318 buffer.update(cx, |buffer, cx| {
5319 let snapshot = buffer.snapshot();
5320 let edits = edits
5321 .into_iter()
5322 .map(|(range, text)| {
5323 use text::ToPoint as TP;
5324 let end_point = TP::to_point(&range.end, &snapshot);
5325 let start_point = TP::to_point(&range.start, &snapshot);
5326 (start_point..end_point, text)
5327 })
5328 .sorted_by_key(|(range, _)| range.start);
5329 buffer.edit(edits, None, cx);
5330 })
5331 }
5332
5333 this.refresh_inline_completion(true, false, window, cx);
5334 });
5335
5336 let show_new_completions_on_confirm = completion
5337 .confirm
5338 .as_ref()
5339 .map_or(false, |confirm| confirm(intent, window, cx));
5340 if show_new_completions_on_confirm {
5341 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5342 }
5343
5344 let provider = self.completion_provider.as_ref()?;
5345 drop(completion);
5346 let apply_edits = provider.apply_additional_edits_for_completion(
5347 buffer_handle,
5348 completions_menu.completions.clone(),
5349 candidate_id,
5350 true,
5351 cx,
5352 );
5353
5354 let editor_settings = EditorSettings::get_global(cx);
5355 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5356 // After the code completion is finished, users often want to know what signatures are needed.
5357 // so we should automatically call signature_help
5358 self.show_signature_help(&ShowSignatureHelp, window, cx);
5359 }
5360
5361 Some(cx.foreground_executor().spawn(async move {
5362 apply_edits.await?;
5363 Ok(())
5364 }))
5365 }
5366
5367 pub fn toggle_code_actions(
5368 &mut self,
5369 action: &ToggleCodeActions,
5370 window: &mut Window,
5371 cx: &mut Context<Self>,
5372 ) {
5373 let quick_launch = action.quick_launch;
5374 let mut context_menu = self.context_menu.borrow_mut();
5375 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5376 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5377 // Toggle if we're selecting the same one
5378 *context_menu = None;
5379 cx.notify();
5380 return;
5381 } else {
5382 // Otherwise, clear it and start a new one
5383 *context_menu = None;
5384 cx.notify();
5385 }
5386 }
5387 drop(context_menu);
5388 let snapshot = self.snapshot(window, cx);
5389 let deployed_from_indicator = action.deployed_from_indicator;
5390 let mut task = self.code_actions_task.take();
5391 let action = action.clone();
5392 cx.spawn_in(window, async move |editor, cx| {
5393 while let Some(prev_task) = task {
5394 prev_task.await.log_err();
5395 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5396 }
5397
5398 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5399 if editor.focus_handle.is_focused(window) {
5400 let multibuffer_point = action
5401 .deployed_from_indicator
5402 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5403 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5404 let (buffer, buffer_row) = snapshot
5405 .buffer_snapshot
5406 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5407 .and_then(|(buffer_snapshot, range)| {
5408 editor
5409 .buffer
5410 .read(cx)
5411 .buffer(buffer_snapshot.remote_id())
5412 .map(|buffer| (buffer, range.start.row))
5413 })?;
5414 let (_, code_actions) = editor
5415 .available_code_actions
5416 .clone()
5417 .and_then(|(location, code_actions)| {
5418 let snapshot = location.buffer.read(cx).snapshot();
5419 let point_range = location.range.to_point(&snapshot);
5420 let point_range = point_range.start.row..=point_range.end.row;
5421 if point_range.contains(&buffer_row) {
5422 Some((location, code_actions))
5423 } else {
5424 None
5425 }
5426 })
5427 .unzip();
5428 let buffer_id = buffer.read(cx).remote_id();
5429 let tasks = editor
5430 .tasks
5431 .get(&(buffer_id, buffer_row))
5432 .map(|t| Arc::new(t.to_owned()));
5433 if tasks.is_none() && code_actions.is_none() {
5434 return None;
5435 }
5436
5437 editor.completion_tasks.clear();
5438 editor.discard_inline_completion(false, cx);
5439 let task_context =
5440 tasks
5441 .as_ref()
5442 .zip(editor.project.clone())
5443 .map(|(tasks, project)| {
5444 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5445 });
5446
5447 Some(cx.spawn_in(window, async move |editor, cx| {
5448 let task_context = match task_context {
5449 Some(task_context) => task_context.await,
5450 None => None,
5451 };
5452 let resolved_tasks =
5453 tasks
5454 .zip(task_context.clone())
5455 .map(|(tasks, task_context)| ResolvedTasks {
5456 templates: tasks.resolve(&task_context).collect(),
5457 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5458 multibuffer_point.row,
5459 tasks.column,
5460 )),
5461 });
5462 let debug_scenarios = editor.update(cx, |editor, cx| {
5463 if cx.has_flag::<DebuggerFeatureFlag>() {
5464 maybe!({
5465 let project = editor.project.as_ref()?;
5466 let dap_store = project.read(cx).dap_store();
5467 let mut scenarios = vec![];
5468 let resolved_tasks = resolved_tasks.as_ref()?;
5469 let buffer = buffer.read(cx);
5470 let language = buffer.language()?;
5471 let file = buffer.file();
5472 let debug_adapter =
5473 language_settings(language.name().into(), file, cx)
5474 .debuggers
5475 .first()
5476 .map(SharedString::from)
5477 .or_else(|| {
5478 language
5479 .config()
5480 .debuggers
5481 .first()
5482 .map(SharedString::from)
5483 })?;
5484
5485 dap_store.update(cx, |dap_store, cx| {
5486 for (_, task) in &resolved_tasks.templates {
5487 if let Some(scenario) = dap_store
5488 .debug_scenario_for_build_task(
5489 task.original_task().clone(),
5490 debug_adapter.clone().into(),
5491 task.display_label().to_owned().into(),
5492 cx,
5493 )
5494 {
5495 scenarios.push(scenario);
5496 }
5497 }
5498 });
5499 Some(scenarios)
5500 })
5501 .unwrap_or_default()
5502 } else {
5503 vec![]
5504 }
5505 })?;
5506 let spawn_straight_away = quick_launch
5507 && resolved_tasks
5508 .as_ref()
5509 .map_or(false, |tasks| tasks.templates.len() == 1)
5510 && code_actions
5511 .as_ref()
5512 .map_or(true, |actions| actions.is_empty())
5513 && debug_scenarios.is_empty();
5514 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5515 crate::hover_popover::hide_hover(editor, cx);
5516 *editor.context_menu.borrow_mut() =
5517 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5518 buffer,
5519 actions: CodeActionContents::new(
5520 resolved_tasks,
5521 code_actions,
5522 debug_scenarios,
5523 task_context.unwrap_or_default(),
5524 ),
5525 selected_item: Default::default(),
5526 scroll_handle: UniformListScrollHandle::default(),
5527 deployed_from_indicator,
5528 }));
5529 if spawn_straight_away {
5530 if let Some(task) = editor.confirm_code_action(
5531 &ConfirmCodeAction { item_ix: Some(0) },
5532 window,
5533 cx,
5534 ) {
5535 cx.notify();
5536 return task;
5537 }
5538 }
5539 cx.notify();
5540 Task::ready(Ok(()))
5541 }) {
5542 task.await
5543 } else {
5544 Ok(())
5545 }
5546 }))
5547 } else {
5548 Some(Task::ready(Ok(())))
5549 }
5550 })?;
5551 if let Some(task) = spawned_test_task {
5552 task.await?;
5553 }
5554
5555 anyhow::Ok(())
5556 })
5557 .detach_and_log_err(cx);
5558 }
5559
5560 pub fn confirm_code_action(
5561 &mut self,
5562 action: &ConfirmCodeAction,
5563 window: &mut Window,
5564 cx: &mut Context<Self>,
5565 ) -> Option<Task<Result<()>>> {
5566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5567
5568 let actions_menu =
5569 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5570 menu
5571 } else {
5572 return None;
5573 };
5574
5575 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5576 let action = actions_menu.actions.get(action_ix)?;
5577 let title = action.label();
5578 let buffer = actions_menu.buffer;
5579 let workspace = self.workspace()?;
5580
5581 match action {
5582 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5583 workspace.update(cx, |workspace, cx| {
5584 workspace.schedule_resolved_task(
5585 task_source_kind,
5586 resolved_task,
5587 false,
5588 window,
5589 cx,
5590 );
5591
5592 Some(Task::ready(Ok(())))
5593 })
5594 }
5595 CodeActionsItem::CodeAction {
5596 excerpt_id,
5597 action,
5598 provider,
5599 } => {
5600 let apply_code_action =
5601 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5602 let workspace = workspace.downgrade();
5603 Some(cx.spawn_in(window, async move |editor, cx| {
5604 let project_transaction = apply_code_action.await?;
5605 Self::open_project_transaction(
5606 &editor,
5607 workspace,
5608 project_transaction,
5609 title,
5610 cx,
5611 )
5612 .await
5613 }))
5614 }
5615 CodeActionsItem::DebugScenario(scenario) => {
5616 let context = actions_menu.actions.context.clone();
5617
5618 workspace.update(cx, |workspace, cx| {
5619 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5620 });
5621 Some(Task::ready(Ok(())))
5622 }
5623 }
5624 }
5625
5626 pub async fn open_project_transaction(
5627 this: &WeakEntity<Editor>,
5628 workspace: WeakEntity<Workspace>,
5629 transaction: ProjectTransaction,
5630 title: String,
5631 cx: &mut AsyncWindowContext,
5632 ) -> Result<()> {
5633 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5634 cx.update(|_, cx| {
5635 entries.sort_unstable_by_key(|(buffer, _)| {
5636 buffer.read(cx).file().map(|f| f.path().clone())
5637 });
5638 })?;
5639
5640 // If the project transaction's edits are all contained within this editor, then
5641 // avoid opening a new editor to display them.
5642
5643 if let Some((buffer, transaction)) = entries.first() {
5644 if entries.len() == 1 {
5645 let excerpt = this.update(cx, |editor, cx| {
5646 editor
5647 .buffer()
5648 .read(cx)
5649 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5650 })?;
5651 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5652 if excerpted_buffer == *buffer {
5653 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5654 let excerpt_range = excerpt_range.to_offset(buffer);
5655 buffer
5656 .edited_ranges_for_transaction::<usize>(transaction)
5657 .all(|range| {
5658 excerpt_range.start <= range.start
5659 && excerpt_range.end >= range.end
5660 })
5661 })?;
5662
5663 if all_edits_within_excerpt {
5664 return Ok(());
5665 }
5666 }
5667 }
5668 }
5669 } else {
5670 return Ok(());
5671 }
5672
5673 let mut ranges_to_highlight = Vec::new();
5674 let excerpt_buffer = cx.new(|cx| {
5675 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5676 for (buffer_handle, transaction) in &entries {
5677 let edited_ranges = buffer_handle
5678 .read(cx)
5679 .edited_ranges_for_transaction::<Point>(transaction)
5680 .collect::<Vec<_>>();
5681 let (ranges, _) = multibuffer.set_excerpts_for_path(
5682 PathKey::for_buffer(buffer_handle, cx),
5683 buffer_handle.clone(),
5684 edited_ranges,
5685 DEFAULT_MULTIBUFFER_CONTEXT,
5686 cx,
5687 );
5688
5689 ranges_to_highlight.extend(ranges);
5690 }
5691 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5692 multibuffer
5693 })?;
5694
5695 workspace.update_in(cx, |workspace, window, cx| {
5696 let project = workspace.project().clone();
5697 let editor =
5698 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5699 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5700 editor.update(cx, |editor, cx| {
5701 editor.highlight_background::<Self>(
5702 &ranges_to_highlight,
5703 |theme| theme.editor_highlighted_line_background,
5704 cx,
5705 );
5706 });
5707 })?;
5708
5709 Ok(())
5710 }
5711
5712 pub fn clear_code_action_providers(&mut self) {
5713 self.code_action_providers.clear();
5714 self.available_code_actions.take();
5715 }
5716
5717 pub fn add_code_action_provider(
5718 &mut self,
5719 provider: Rc<dyn CodeActionProvider>,
5720 window: &mut Window,
5721 cx: &mut Context<Self>,
5722 ) {
5723 if self
5724 .code_action_providers
5725 .iter()
5726 .any(|existing_provider| existing_provider.id() == provider.id())
5727 {
5728 return;
5729 }
5730
5731 self.code_action_providers.push(provider);
5732 self.refresh_code_actions(window, cx);
5733 }
5734
5735 pub fn remove_code_action_provider(
5736 &mut self,
5737 id: Arc<str>,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) {
5741 self.code_action_providers
5742 .retain(|provider| provider.id() != id);
5743 self.refresh_code_actions(window, cx);
5744 }
5745
5746 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5747 let newest_selection = self.selections.newest_anchor().clone();
5748 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5749 let buffer = self.buffer.read(cx);
5750 if newest_selection.head().diff_base_anchor.is_some() {
5751 return None;
5752 }
5753 let (start_buffer, start) =
5754 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5755 let (end_buffer, end) =
5756 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5757 if start_buffer != end_buffer {
5758 return None;
5759 }
5760
5761 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5762 cx.background_executor()
5763 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5764 .await;
5765
5766 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5767 let providers = this.code_action_providers.clone();
5768 let tasks = this
5769 .code_action_providers
5770 .iter()
5771 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5772 .collect::<Vec<_>>();
5773 (providers, tasks)
5774 })?;
5775
5776 let mut actions = Vec::new();
5777 for (provider, provider_actions) in
5778 providers.into_iter().zip(future::join_all(tasks).await)
5779 {
5780 if let Some(provider_actions) = provider_actions.log_err() {
5781 actions.extend(provider_actions.into_iter().map(|action| {
5782 AvailableCodeAction {
5783 excerpt_id: newest_selection.start.excerpt_id,
5784 action,
5785 provider: provider.clone(),
5786 }
5787 }));
5788 }
5789 }
5790
5791 this.update(cx, |this, cx| {
5792 this.available_code_actions = if actions.is_empty() {
5793 None
5794 } else {
5795 Some((
5796 Location {
5797 buffer: start_buffer,
5798 range: start..end,
5799 },
5800 actions.into(),
5801 ))
5802 };
5803 cx.notify();
5804 })
5805 }));
5806 None
5807 }
5808
5809 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5810 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5811 self.show_git_blame_inline = false;
5812
5813 self.show_git_blame_inline_delay_task =
5814 Some(cx.spawn_in(window, async move |this, cx| {
5815 cx.background_executor().timer(delay).await;
5816
5817 this.update(cx, |this, cx| {
5818 this.show_git_blame_inline = true;
5819 cx.notify();
5820 })
5821 .log_err();
5822 }));
5823 }
5824 }
5825
5826 fn show_blame_popover(
5827 &mut self,
5828 blame_entry: &BlameEntry,
5829 position: gpui::Point<Pixels>,
5830 cx: &mut Context<Self>,
5831 ) {
5832 if let Some(state) = &mut self.inline_blame_popover {
5833 state.hide_task.take();
5834 cx.notify();
5835 } else {
5836 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5837 let show_task = cx.spawn(async move |editor, cx| {
5838 cx.background_executor()
5839 .timer(std::time::Duration::from_millis(delay))
5840 .await;
5841 editor
5842 .update(cx, |editor, cx| {
5843 if let Some(state) = &mut editor.inline_blame_popover {
5844 state.show_task = None;
5845 cx.notify();
5846 }
5847 })
5848 .ok();
5849 });
5850 let Some(blame) = self.blame.as_ref() else {
5851 return;
5852 };
5853 let blame = blame.read(cx);
5854 let details = blame.details_for_entry(&blame_entry);
5855 let markdown = cx.new(|cx| {
5856 Markdown::new(
5857 details
5858 .as_ref()
5859 .map(|message| message.message.clone())
5860 .unwrap_or_default(),
5861 None,
5862 None,
5863 cx,
5864 )
5865 });
5866 self.inline_blame_popover = Some(InlineBlamePopover {
5867 position,
5868 show_task: Some(show_task),
5869 hide_task: None,
5870 popover_bounds: None,
5871 popover_state: InlineBlamePopoverState {
5872 scroll_handle: ScrollHandle::new(),
5873 commit_message: details,
5874 markdown,
5875 },
5876 });
5877 }
5878 }
5879
5880 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5881 if let Some(state) = &mut self.inline_blame_popover {
5882 if state.show_task.is_some() {
5883 self.inline_blame_popover.take();
5884 cx.notify();
5885 } else {
5886 let hide_task = cx.spawn(async move |editor, cx| {
5887 cx.background_executor()
5888 .timer(std::time::Duration::from_millis(100))
5889 .await;
5890 editor
5891 .update(cx, |editor, cx| {
5892 editor.inline_blame_popover.take();
5893 cx.notify();
5894 })
5895 .ok();
5896 });
5897 state.hide_task = Some(hide_task);
5898 }
5899 }
5900 }
5901
5902 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5903 if self.pending_rename.is_some() {
5904 return None;
5905 }
5906
5907 let provider = self.semantics_provider.clone()?;
5908 let buffer = self.buffer.read(cx);
5909 let newest_selection = self.selections.newest_anchor().clone();
5910 let cursor_position = newest_selection.head();
5911 let (cursor_buffer, cursor_buffer_position) =
5912 buffer.text_anchor_for_position(cursor_position, cx)?;
5913 let (tail_buffer, tail_buffer_position) =
5914 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5915 if cursor_buffer != tail_buffer {
5916 return None;
5917 }
5918
5919 let snapshot = cursor_buffer.read(cx).snapshot();
5920 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5921 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5922 if start_word_range != end_word_range {
5923 self.document_highlights_task.take();
5924 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5925 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5926 return None;
5927 }
5928
5929 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5930 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5931 cx.background_executor()
5932 .timer(Duration::from_millis(debounce))
5933 .await;
5934
5935 let highlights = if let Some(highlights) = cx
5936 .update(|cx| {
5937 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5938 })
5939 .ok()
5940 .flatten()
5941 {
5942 highlights.await.log_err()
5943 } else {
5944 None
5945 };
5946
5947 if let Some(highlights) = highlights {
5948 this.update(cx, |this, cx| {
5949 if this.pending_rename.is_some() {
5950 return;
5951 }
5952
5953 let buffer_id = cursor_position.buffer_id;
5954 let buffer = this.buffer.read(cx);
5955 if !buffer
5956 .text_anchor_for_position(cursor_position, cx)
5957 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5958 {
5959 return;
5960 }
5961
5962 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5963 let mut write_ranges = Vec::new();
5964 let mut read_ranges = Vec::new();
5965 for highlight in highlights {
5966 for (excerpt_id, excerpt_range) in
5967 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5968 {
5969 let start = highlight
5970 .range
5971 .start
5972 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5973 let end = highlight
5974 .range
5975 .end
5976 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5977 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5978 continue;
5979 }
5980
5981 let range = Anchor {
5982 buffer_id,
5983 excerpt_id,
5984 text_anchor: start,
5985 diff_base_anchor: None,
5986 }..Anchor {
5987 buffer_id,
5988 excerpt_id,
5989 text_anchor: end,
5990 diff_base_anchor: None,
5991 };
5992 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5993 write_ranges.push(range);
5994 } else {
5995 read_ranges.push(range);
5996 }
5997 }
5998 }
5999
6000 this.highlight_background::<DocumentHighlightRead>(
6001 &read_ranges,
6002 |theme| theme.editor_document_highlight_read_background,
6003 cx,
6004 );
6005 this.highlight_background::<DocumentHighlightWrite>(
6006 &write_ranges,
6007 |theme| theme.editor_document_highlight_write_background,
6008 cx,
6009 );
6010 cx.notify();
6011 })
6012 .log_err();
6013 }
6014 }));
6015 None
6016 }
6017
6018 fn prepare_highlight_query_from_selection(
6019 &mut self,
6020 cx: &mut Context<Editor>,
6021 ) -> Option<(String, Range<Anchor>)> {
6022 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6023 return None;
6024 }
6025 if !EditorSettings::get_global(cx).selection_highlight {
6026 return None;
6027 }
6028 if self.selections.count() != 1 || self.selections.line_mode {
6029 return None;
6030 }
6031 let selection = self.selections.newest::<Point>(cx);
6032 if selection.is_empty() || selection.start.row != selection.end.row {
6033 return None;
6034 }
6035 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6036 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6037 let query = multi_buffer_snapshot
6038 .text_for_range(selection_anchor_range.clone())
6039 .collect::<String>();
6040 if query.trim().is_empty() {
6041 return None;
6042 }
6043 Some((query, selection_anchor_range))
6044 }
6045
6046 fn update_selection_occurrence_highlights(
6047 &mut self,
6048 query_text: String,
6049 query_range: Range<Anchor>,
6050 multi_buffer_range_to_query: Range<Point>,
6051 use_debounce: bool,
6052 window: &mut Window,
6053 cx: &mut Context<Editor>,
6054 ) -> Task<()> {
6055 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6056 cx.spawn_in(window, async move |editor, cx| {
6057 if use_debounce {
6058 cx.background_executor()
6059 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6060 .await;
6061 }
6062 let match_task = cx.background_spawn(async move {
6063 let buffer_ranges = multi_buffer_snapshot
6064 .range_to_buffer_ranges(multi_buffer_range_to_query)
6065 .into_iter()
6066 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6067 let mut match_ranges = Vec::new();
6068 let Ok(regex) = project::search::SearchQuery::text(
6069 query_text.clone(),
6070 false,
6071 false,
6072 false,
6073 Default::default(),
6074 Default::default(),
6075 false,
6076 None,
6077 ) else {
6078 return Vec::default();
6079 };
6080 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6081 match_ranges.extend(
6082 regex
6083 .search(&buffer_snapshot, Some(search_range.clone()))
6084 .await
6085 .into_iter()
6086 .filter_map(|match_range| {
6087 let match_start = buffer_snapshot
6088 .anchor_after(search_range.start + match_range.start);
6089 let match_end = buffer_snapshot
6090 .anchor_before(search_range.start + match_range.end);
6091 let match_anchor_range = Anchor::range_in_buffer(
6092 excerpt_id,
6093 buffer_snapshot.remote_id(),
6094 match_start..match_end,
6095 );
6096 (match_anchor_range != query_range).then_some(match_anchor_range)
6097 }),
6098 );
6099 }
6100 match_ranges
6101 });
6102 let match_ranges = match_task.await;
6103 editor
6104 .update_in(cx, |editor, _, cx| {
6105 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6106 if !match_ranges.is_empty() {
6107 editor.highlight_background::<SelectedTextHighlight>(
6108 &match_ranges,
6109 |theme| theme.editor_document_highlight_bracket_background,
6110 cx,
6111 )
6112 }
6113 })
6114 .log_err();
6115 })
6116 }
6117
6118 fn refresh_selected_text_highlights(
6119 &mut self,
6120 on_buffer_edit: bool,
6121 window: &mut Window,
6122 cx: &mut Context<Editor>,
6123 ) {
6124 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6125 else {
6126 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6127 self.quick_selection_highlight_task.take();
6128 self.debounced_selection_highlight_task.take();
6129 return;
6130 };
6131 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6132 if on_buffer_edit
6133 || self
6134 .quick_selection_highlight_task
6135 .as_ref()
6136 .map_or(true, |(prev_anchor_range, _)| {
6137 prev_anchor_range != &query_range
6138 })
6139 {
6140 let multi_buffer_visible_start = self
6141 .scroll_manager
6142 .anchor()
6143 .anchor
6144 .to_point(&multi_buffer_snapshot);
6145 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6146 multi_buffer_visible_start
6147 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6148 Bias::Left,
6149 );
6150 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6151 self.quick_selection_highlight_task = Some((
6152 query_range.clone(),
6153 self.update_selection_occurrence_highlights(
6154 query_text.clone(),
6155 query_range.clone(),
6156 multi_buffer_visible_range,
6157 false,
6158 window,
6159 cx,
6160 ),
6161 ));
6162 }
6163 if on_buffer_edit
6164 || self
6165 .debounced_selection_highlight_task
6166 .as_ref()
6167 .map_or(true, |(prev_anchor_range, _)| {
6168 prev_anchor_range != &query_range
6169 })
6170 {
6171 let multi_buffer_start = multi_buffer_snapshot
6172 .anchor_before(0)
6173 .to_point(&multi_buffer_snapshot);
6174 let multi_buffer_end = multi_buffer_snapshot
6175 .anchor_after(multi_buffer_snapshot.len())
6176 .to_point(&multi_buffer_snapshot);
6177 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6178 self.debounced_selection_highlight_task = Some((
6179 query_range.clone(),
6180 self.update_selection_occurrence_highlights(
6181 query_text,
6182 query_range,
6183 multi_buffer_full_range,
6184 true,
6185 window,
6186 cx,
6187 ),
6188 ));
6189 }
6190 }
6191
6192 pub fn refresh_inline_completion(
6193 &mut self,
6194 debounce: bool,
6195 user_requested: bool,
6196 window: &mut Window,
6197 cx: &mut Context<Self>,
6198 ) -> Option<()> {
6199 let provider = self.edit_prediction_provider()?;
6200 let cursor = self.selections.newest_anchor().head();
6201 let (buffer, cursor_buffer_position) =
6202 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6203
6204 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6205 self.discard_inline_completion(false, cx);
6206 return None;
6207 }
6208
6209 if !user_requested
6210 && (!self.should_show_edit_predictions()
6211 || !self.is_focused(window)
6212 || buffer.read(cx).is_empty())
6213 {
6214 self.discard_inline_completion(false, cx);
6215 return None;
6216 }
6217
6218 self.update_visible_inline_completion(window, cx);
6219 provider.refresh(
6220 self.project.clone(),
6221 buffer,
6222 cursor_buffer_position,
6223 debounce,
6224 cx,
6225 );
6226 Some(())
6227 }
6228
6229 fn show_edit_predictions_in_menu(&self) -> bool {
6230 match self.edit_prediction_settings {
6231 EditPredictionSettings::Disabled => false,
6232 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6233 }
6234 }
6235
6236 pub fn edit_predictions_enabled(&self) -> bool {
6237 match self.edit_prediction_settings {
6238 EditPredictionSettings::Disabled => false,
6239 EditPredictionSettings::Enabled { .. } => true,
6240 }
6241 }
6242
6243 fn edit_prediction_requires_modifier(&self) -> bool {
6244 match self.edit_prediction_settings {
6245 EditPredictionSettings::Disabled => false,
6246 EditPredictionSettings::Enabled {
6247 preview_requires_modifier,
6248 ..
6249 } => preview_requires_modifier,
6250 }
6251 }
6252
6253 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6254 if self.edit_prediction_provider.is_none() {
6255 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6256 } else {
6257 let selection = self.selections.newest_anchor();
6258 let cursor = selection.head();
6259
6260 if let Some((buffer, cursor_buffer_position)) =
6261 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6262 {
6263 self.edit_prediction_settings =
6264 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6265 }
6266 }
6267 }
6268
6269 fn edit_prediction_settings_at_position(
6270 &self,
6271 buffer: &Entity<Buffer>,
6272 buffer_position: language::Anchor,
6273 cx: &App,
6274 ) -> EditPredictionSettings {
6275 if !self.mode.is_full()
6276 || !self.show_inline_completions_override.unwrap_or(true)
6277 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6278 {
6279 return EditPredictionSettings::Disabled;
6280 }
6281
6282 let buffer = buffer.read(cx);
6283
6284 let file = buffer.file();
6285
6286 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6287 return EditPredictionSettings::Disabled;
6288 };
6289
6290 let by_provider = matches!(
6291 self.menu_inline_completions_policy,
6292 MenuInlineCompletionsPolicy::ByProvider
6293 );
6294
6295 let show_in_menu = by_provider
6296 && self
6297 .edit_prediction_provider
6298 .as_ref()
6299 .map_or(false, |provider| {
6300 provider.provider.show_completions_in_menu()
6301 });
6302
6303 let preview_requires_modifier =
6304 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6305
6306 EditPredictionSettings::Enabled {
6307 show_in_menu,
6308 preview_requires_modifier,
6309 }
6310 }
6311
6312 fn should_show_edit_predictions(&self) -> bool {
6313 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6314 }
6315
6316 pub fn edit_prediction_preview_is_active(&self) -> bool {
6317 matches!(
6318 self.edit_prediction_preview,
6319 EditPredictionPreview::Active { .. }
6320 )
6321 }
6322
6323 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6324 let cursor = self.selections.newest_anchor().head();
6325 if let Some((buffer, cursor_position)) =
6326 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6327 {
6328 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6329 } else {
6330 false
6331 }
6332 }
6333
6334 pub fn supports_minimap(&self, cx: &App) -> bool {
6335 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6336 }
6337
6338 fn edit_predictions_enabled_in_buffer(
6339 &self,
6340 buffer: &Entity<Buffer>,
6341 buffer_position: language::Anchor,
6342 cx: &App,
6343 ) -> bool {
6344 maybe!({
6345 if self.read_only(cx) {
6346 return Some(false);
6347 }
6348 let provider = self.edit_prediction_provider()?;
6349 if !provider.is_enabled(&buffer, buffer_position, cx) {
6350 return Some(false);
6351 }
6352 let buffer = buffer.read(cx);
6353 let Some(file) = buffer.file() else {
6354 return Some(true);
6355 };
6356 let settings = all_language_settings(Some(file), cx);
6357 Some(settings.edit_predictions_enabled_for_file(file, cx))
6358 })
6359 .unwrap_or(false)
6360 }
6361
6362 fn cycle_inline_completion(
6363 &mut self,
6364 direction: Direction,
6365 window: &mut Window,
6366 cx: &mut Context<Self>,
6367 ) -> Option<()> {
6368 let provider = self.edit_prediction_provider()?;
6369 let cursor = self.selections.newest_anchor().head();
6370 let (buffer, cursor_buffer_position) =
6371 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6372 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6373 return None;
6374 }
6375
6376 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6377 self.update_visible_inline_completion(window, cx);
6378
6379 Some(())
6380 }
6381
6382 pub fn show_inline_completion(
6383 &mut self,
6384 _: &ShowEditPrediction,
6385 window: &mut Window,
6386 cx: &mut Context<Self>,
6387 ) {
6388 if !self.has_active_inline_completion() {
6389 self.refresh_inline_completion(false, true, window, cx);
6390 return;
6391 }
6392
6393 self.update_visible_inline_completion(window, cx);
6394 }
6395
6396 pub fn display_cursor_names(
6397 &mut self,
6398 _: &DisplayCursorNames,
6399 window: &mut Window,
6400 cx: &mut Context<Self>,
6401 ) {
6402 self.show_cursor_names(window, cx);
6403 }
6404
6405 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6406 self.show_cursor_names = true;
6407 cx.notify();
6408 cx.spawn_in(window, async move |this, cx| {
6409 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6410 this.update(cx, |this, cx| {
6411 this.show_cursor_names = false;
6412 cx.notify()
6413 })
6414 .ok()
6415 })
6416 .detach();
6417 }
6418
6419 pub fn next_edit_prediction(
6420 &mut self,
6421 _: &NextEditPrediction,
6422 window: &mut Window,
6423 cx: &mut Context<Self>,
6424 ) {
6425 if self.has_active_inline_completion() {
6426 self.cycle_inline_completion(Direction::Next, window, cx);
6427 } else {
6428 let is_copilot_disabled = self
6429 .refresh_inline_completion(false, true, window, cx)
6430 .is_none();
6431 if is_copilot_disabled {
6432 cx.propagate();
6433 }
6434 }
6435 }
6436
6437 pub fn previous_edit_prediction(
6438 &mut self,
6439 _: &PreviousEditPrediction,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) {
6443 if self.has_active_inline_completion() {
6444 self.cycle_inline_completion(Direction::Prev, window, cx);
6445 } else {
6446 let is_copilot_disabled = self
6447 .refresh_inline_completion(false, true, window, cx)
6448 .is_none();
6449 if is_copilot_disabled {
6450 cx.propagate();
6451 }
6452 }
6453 }
6454
6455 pub fn accept_edit_prediction(
6456 &mut self,
6457 _: &AcceptEditPrediction,
6458 window: &mut Window,
6459 cx: &mut Context<Self>,
6460 ) {
6461 if self.show_edit_predictions_in_menu() {
6462 self.hide_context_menu(window, cx);
6463 }
6464
6465 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6466 return;
6467 };
6468
6469 self.report_inline_completion_event(
6470 active_inline_completion.completion_id.clone(),
6471 true,
6472 cx,
6473 );
6474
6475 match &active_inline_completion.completion {
6476 InlineCompletion::Move { target, .. } => {
6477 let target = *target;
6478
6479 if let Some(position_map) = &self.last_position_map {
6480 if position_map
6481 .visible_row_range
6482 .contains(&target.to_display_point(&position_map.snapshot).row())
6483 || !self.edit_prediction_requires_modifier()
6484 {
6485 self.unfold_ranges(&[target..target], true, false, cx);
6486 // Note that this is also done in vim's handler of the Tab action.
6487 self.change_selections(
6488 Some(Autoscroll::newest()),
6489 window,
6490 cx,
6491 |selections| {
6492 selections.select_anchor_ranges([target..target]);
6493 },
6494 );
6495 self.clear_row_highlights::<EditPredictionPreview>();
6496
6497 self.edit_prediction_preview
6498 .set_previous_scroll_position(None);
6499 } else {
6500 self.edit_prediction_preview
6501 .set_previous_scroll_position(Some(
6502 position_map.snapshot.scroll_anchor,
6503 ));
6504
6505 self.highlight_rows::<EditPredictionPreview>(
6506 target..target,
6507 cx.theme().colors().editor_highlighted_line_background,
6508 RowHighlightOptions {
6509 autoscroll: true,
6510 ..Default::default()
6511 },
6512 cx,
6513 );
6514 self.request_autoscroll(Autoscroll::fit(), cx);
6515 }
6516 }
6517 }
6518 InlineCompletion::Edit { edits, .. } => {
6519 if let Some(provider) = self.edit_prediction_provider() {
6520 provider.accept(cx);
6521 }
6522
6523 let snapshot = self.buffer.read(cx).snapshot(cx);
6524 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6525
6526 self.buffer.update(cx, |buffer, cx| {
6527 buffer.edit(edits.iter().cloned(), None, cx)
6528 });
6529
6530 self.change_selections(None, window, cx, |s| {
6531 s.select_anchor_ranges([last_edit_end..last_edit_end])
6532 });
6533
6534 self.update_visible_inline_completion(window, cx);
6535 if self.active_inline_completion.is_none() {
6536 self.refresh_inline_completion(true, true, window, cx);
6537 }
6538
6539 cx.notify();
6540 }
6541 }
6542
6543 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6544 }
6545
6546 pub fn accept_partial_inline_completion(
6547 &mut self,
6548 _: &AcceptPartialEditPrediction,
6549 window: &mut Window,
6550 cx: &mut Context<Self>,
6551 ) {
6552 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6553 return;
6554 };
6555 if self.selections.count() != 1 {
6556 return;
6557 }
6558
6559 self.report_inline_completion_event(
6560 active_inline_completion.completion_id.clone(),
6561 true,
6562 cx,
6563 );
6564
6565 match &active_inline_completion.completion {
6566 InlineCompletion::Move { target, .. } => {
6567 let target = *target;
6568 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6569 selections.select_anchor_ranges([target..target]);
6570 });
6571 }
6572 InlineCompletion::Edit { edits, .. } => {
6573 // Find an insertion that starts at the cursor position.
6574 let snapshot = self.buffer.read(cx).snapshot(cx);
6575 let cursor_offset = self.selections.newest::<usize>(cx).head();
6576 let insertion = edits.iter().find_map(|(range, text)| {
6577 let range = range.to_offset(&snapshot);
6578 if range.is_empty() && range.start == cursor_offset {
6579 Some(text)
6580 } else {
6581 None
6582 }
6583 });
6584
6585 if let Some(text) = insertion {
6586 let mut partial_completion = text
6587 .chars()
6588 .by_ref()
6589 .take_while(|c| c.is_alphabetic())
6590 .collect::<String>();
6591 if partial_completion.is_empty() {
6592 partial_completion = text
6593 .chars()
6594 .by_ref()
6595 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6596 .collect::<String>();
6597 }
6598
6599 cx.emit(EditorEvent::InputHandled {
6600 utf16_range_to_replace: None,
6601 text: partial_completion.clone().into(),
6602 });
6603
6604 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6605
6606 self.refresh_inline_completion(true, true, window, cx);
6607 cx.notify();
6608 } else {
6609 self.accept_edit_prediction(&Default::default(), window, cx);
6610 }
6611 }
6612 }
6613 }
6614
6615 fn discard_inline_completion(
6616 &mut self,
6617 should_report_inline_completion_event: bool,
6618 cx: &mut Context<Self>,
6619 ) -> bool {
6620 if should_report_inline_completion_event {
6621 let completion_id = self
6622 .active_inline_completion
6623 .as_ref()
6624 .and_then(|active_completion| active_completion.completion_id.clone());
6625
6626 self.report_inline_completion_event(completion_id, false, cx);
6627 }
6628
6629 if let Some(provider) = self.edit_prediction_provider() {
6630 provider.discard(cx);
6631 }
6632
6633 self.take_active_inline_completion(cx)
6634 }
6635
6636 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6637 let Some(provider) = self.edit_prediction_provider() else {
6638 return;
6639 };
6640
6641 let Some((_, buffer, _)) = self
6642 .buffer
6643 .read(cx)
6644 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6645 else {
6646 return;
6647 };
6648
6649 let extension = buffer
6650 .read(cx)
6651 .file()
6652 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6653
6654 let event_type = match accepted {
6655 true => "Edit Prediction Accepted",
6656 false => "Edit Prediction Discarded",
6657 };
6658 telemetry::event!(
6659 event_type,
6660 provider = provider.name(),
6661 prediction_id = id,
6662 suggestion_accepted = accepted,
6663 file_extension = extension,
6664 );
6665 }
6666
6667 pub fn has_active_inline_completion(&self) -> bool {
6668 self.active_inline_completion.is_some()
6669 }
6670
6671 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6672 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6673 return false;
6674 };
6675
6676 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6677 self.clear_highlights::<InlineCompletionHighlight>(cx);
6678 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6679 true
6680 }
6681
6682 /// Returns true when we're displaying the edit prediction popover below the cursor
6683 /// like we are not previewing and the LSP autocomplete menu is visible
6684 /// or we are in `when_holding_modifier` mode.
6685 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6686 if self.edit_prediction_preview_is_active()
6687 || !self.show_edit_predictions_in_menu()
6688 || !self.edit_predictions_enabled()
6689 {
6690 return false;
6691 }
6692
6693 if self.has_visible_completions_menu() {
6694 return true;
6695 }
6696
6697 has_completion && self.edit_prediction_requires_modifier()
6698 }
6699
6700 fn handle_modifiers_changed(
6701 &mut self,
6702 modifiers: Modifiers,
6703 position_map: &PositionMap,
6704 window: &mut Window,
6705 cx: &mut Context<Self>,
6706 ) {
6707 if self.show_edit_predictions_in_menu() {
6708 self.update_edit_prediction_preview(&modifiers, window, cx);
6709 }
6710
6711 self.update_selection_mode(&modifiers, position_map, window, cx);
6712
6713 let mouse_position = window.mouse_position();
6714 if !position_map.text_hitbox.is_hovered(window) {
6715 return;
6716 }
6717
6718 self.update_hovered_link(
6719 position_map.point_for_position(mouse_position),
6720 &position_map.snapshot,
6721 modifiers,
6722 window,
6723 cx,
6724 )
6725 }
6726
6727 fn update_selection_mode(
6728 &mut self,
6729 modifiers: &Modifiers,
6730 position_map: &PositionMap,
6731 window: &mut Window,
6732 cx: &mut Context<Self>,
6733 ) {
6734 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6735 return;
6736 }
6737
6738 let mouse_position = window.mouse_position();
6739 let point_for_position = position_map.point_for_position(mouse_position);
6740 let position = point_for_position.previous_valid;
6741
6742 self.select(
6743 SelectPhase::BeginColumnar {
6744 position,
6745 reset: false,
6746 goal_column: point_for_position.exact_unclipped.column(),
6747 },
6748 window,
6749 cx,
6750 );
6751 }
6752
6753 fn update_edit_prediction_preview(
6754 &mut self,
6755 modifiers: &Modifiers,
6756 window: &mut Window,
6757 cx: &mut Context<Self>,
6758 ) {
6759 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6760 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6761 return;
6762 };
6763
6764 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6765 if matches!(
6766 self.edit_prediction_preview,
6767 EditPredictionPreview::Inactive { .. }
6768 ) {
6769 self.edit_prediction_preview = EditPredictionPreview::Active {
6770 previous_scroll_position: None,
6771 since: Instant::now(),
6772 };
6773
6774 self.update_visible_inline_completion(window, cx);
6775 cx.notify();
6776 }
6777 } else if let EditPredictionPreview::Active {
6778 previous_scroll_position,
6779 since,
6780 } = self.edit_prediction_preview
6781 {
6782 if let (Some(previous_scroll_position), Some(position_map)) =
6783 (previous_scroll_position, self.last_position_map.as_ref())
6784 {
6785 self.set_scroll_position(
6786 previous_scroll_position
6787 .scroll_position(&position_map.snapshot.display_snapshot),
6788 window,
6789 cx,
6790 );
6791 }
6792
6793 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6794 released_too_fast: since.elapsed() < Duration::from_millis(200),
6795 };
6796 self.clear_row_highlights::<EditPredictionPreview>();
6797 self.update_visible_inline_completion(window, cx);
6798 cx.notify();
6799 }
6800 }
6801
6802 fn update_visible_inline_completion(
6803 &mut self,
6804 _window: &mut Window,
6805 cx: &mut Context<Self>,
6806 ) -> Option<()> {
6807 let selection = self.selections.newest_anchor();
6808 let cursor = selection.head();
6809 let multibuffer = self.buffer.read(cx).snapshot(cx);
6810 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6811 let excerpt_id = cursor.excerpt_id;
6812
6813 let show_in_menu = self.show_edit_predictions_in_menu();
6814 let completions_menu_has_precedence = !show_in_menu
6815 && (self.context_menu.borrow().is_some()
6816 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6817
6818 if completions_menu_has_precedence
6819 || !offset_selection.is_empty()
6820 || self
6821 .active_inline_completion
6822 .as_ref()
6823 .map_or(false, |completion| {
6824 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6825 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6826 !invalidation_range.contains(&offset_selection.head())
6827 })
6828 {
6829 self.discard_inline_completion(false, cx);
6830 return None;
6831 }
6832
6833 self.take_active_inline_completion(cx);
6834 let Some(provider) = self.edit_prediction_provider() else {
6835 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6836 return None;
6837 };
6838
6839 let (buffer, cursor_buffer_position) =
6840 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6841
6842 self.edit_prediction_settings =
6843 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6844
6845 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6846
6847 if self.edit_prediction_indent_conflict {
6848 let cursor_point = cursor.to_point(&multibuffer);
6849
6850 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6851
6852 if let Some((_, indent)) = indents.iter().next() {
6853 if indent.len == cursor_point.column {
6854 self.edit_prediction_indent_conflict = false;
6855 }
6856 }
6857 }
6858
6859 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6860 let edits = inline_completion
6861 .edits
6862 .into_iter()
6863 .flat_map(|(range, new_text)| {
6864 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6865 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6866 Some((start..end, new_text))
6867 })
6868 .collect::<Vec<_>>();
6869 if edits.is_empty() {
6870 return None;
6871 }
6872
6873 let first_edit_start = edits.first().unwrap().0.start;
6874 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6875 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6876
6877 let last_edit_end = edits.last().unwrap().0.end;
6878 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6879 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6880
6881 let cursor_row = cursor.to_point(&multibuffer).row;
6882
6883 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6884
6885 let mut inlay_ids = Vec::new();
6886 let invalidation_row_range;
6887 let move_invalidation_row_range = if cursor_row < edit_start_row {
6888 Some(cursor_row..edit_end_row)
6889 } else if cursor_row > edit_end_row {
6890 Some(edit_start_row..cursor_row)
6891 } else {
6892 None
6893 };
6894 let is_move =
6895 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6896 let completion = if is_move {
6897 invalidation_row_range =
6898 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6899 let target = first_edit_start;
6900 InlineCompletion::Move { target, snapshot }
6901 } else {
6902 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6903 && !self.inline_completions_hidden_for_vim_mode;
6904
6905 if show_completions_in_buffer {
6906 if edits
6907 .iter()
6908 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6909 {
6910 let mut inlays = Vec::new();
6911 for (range, new_text) in &edits {
6912 let inlay = Inlay::inline_completion(
6913 post_inc(&mut self.next_inlay_id),
6914 range.start,
6915 new_text.as_str(),
6916 );
6917 inlay_ids.push(inlay.id);
6918 inlays.push(inlay);
6919 }
6920
6921 self.splice_inlays(&[], inlays, cx);
6922 } else {
6923 let background_color = cx.theme().status().deleted_background;
6924 self.highlight_text::<InlineCompletionHighlight>(
6925 edits.iter().map(|(range, _)| range.clone()).collect(),
6926 HighlightStyle {
6927 background_color: Some(background_color),
6928 ..Default::default()
6929 },
6930 cx,
6931 );
6932 }
6933 }
6934
6935 invalidation_row_range = edit_start_row..edit_end_row;
6936
6937 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6938 if provider.show_tab_accept_marker() {
6939 EditDisplayMode::TabAccept
6940 } else {
6941 EditDisplayMode::Inline
6942 }
6943 } else {
6944 EditDisplayMode::DiffPopover
6945 };
6946
6947 InlineCompletion::Edit {
6948 edits,
6949 edit_preview: inline_completion.edit_preview,
6950 display_mode,
6951 snapshot,
6952 }
6953 };
6954
6955 let invalidation_range = multibuffer
6956 .anchor_before(Point::new(invalidation_row_range.start, 0))
6957 ..multibuffer.anchor_after(Point::new(
6958 invalidation_row_range.end,
6959 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6960 ));
6961
6962 self.stale_inline_completion_in_menu = None;
6963 self.active_inline_completion = Some(InlineCompletionState {
6964 inlay_ids,
6965 completion,
6966 completion_id: inline_completion.id,
6967 invalidation_range,
6968 });
6969
6970 cx.notify();
6971
6972 Some(())
6973 }
6974
6975 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6976 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6977 }
6978
6979 fn clear_tasks(&mut self) {
6980 self.tasks.clear()
6981 }
6982
6983 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6984 if self.tasks.insert(key, value).is_some() {
6985 // This case should hopefully be rare, but just in case...
6986 log::error!(
6987 "multiple different run targets found on a single line, only the last target will be rendered"
6988 )
6989 }
6990 }
6991
6992 /// Get all display points of breakpoints that will be rendered within editor
6993 ///
6994 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6995 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6996 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6997 fn active_breakpoints(
6998 &self,
6999 range: Range<DisplayRow>,
7000 window: &mut Window,
7001 cx: &mut Context<Self>,
7002 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7003 let mut breakpoint_display_points = HashMap::default();
7004
7005 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7006 return breakpoint_display_points;
7007 };
7008
7009 let snapshot = self.snapshot(window, cx);
7010
7011 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7012 let Some(project) = self.project.as_ref() else {
7013 return breakpoint_display_points;
7014 };
7015
7016 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7017 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7018
7019 for (buffer_snapshot, range, excerpt_id) in
7020 multi_buffer_snapshot.range_to_buffer_ranges(range)
7021 {
7022 let Some(buffer) = project.read_with(cx, |this, cx| {
7023 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7024 }) else {
7025 continue;
7026 };
7027 let breakpoints = breakpoint_store.read(cx).breakpoints(
7028 &buffer,
7029 Some(
7030 buffer_snapshot.anchor_before(range.start)
7031 ..buffer_snapshot.anchor_after(range.end),
7032 ),
7033 buffer_snapshot,
7034 cx,
7035 );
7036 for (breakpoint, state) in breakpoints {
7037 let multi_buffer_anchor =
7038 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7039 let position = multi_buffer_anchor
7040 .to_point(&multi_buffer_snapshot)
7041 .to_display_point(&snapshot);
7042
7043 breakpoint_display_points.insert(
7044 position.row(),
7045 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7046 );
7047 }
7048 }
7049
7050 breakpoint_display_points
7051 }
7052
7053 fn breakpoint_context_menu(
7054 &self,
7055 anchor: Anchor,
7056 window: &mut Window,
7057 cx: &mut Context<Self>,
7058 ) -> Entity<ui::ContextMenu> {
7059 let weak_editor = cx.weak_entity();
7060 let focus_handle = self.focus_handle(cx);
7061
7062 let row = self
7063 .buffer
7064 .read(cx)
7065 .snapshot(cx)
7066 .summary_for_anchor::<Point>(&anchor)
7067 .row;
7068
7069 let breakpoint = self
7070 .breakpoint_at_row(row, window, cx)
7071 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7072
7073 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7074 "Edit Log Breakpoint"
7075 } else {
7076 "Set Log Breakpoint"
7077 };
7078
7079 let condition_breakpoint_msg = if breakpoint
7080 .as_ref()
7081 .is_some_and(|bp| bp.1.condition.is_some())
7082 {
7083 "Edit Condition Breakpoint"
7084 } else {
7085 "Set Condition Breakpoint"
7086 };
7087
7088 let hit_condition_breakpoint_msg = if breakpoint
7089 .as_ref()
7090 .is_some_and(|bp| bp.1.hit_condition.is_some())
7091 {
7092 "Edit Hit Condition Breakpoint"
7093 } else {
7094 "Set Hit Condition Breakpoint"
7095 };
7096
7097 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7098 "Unset Breakpoint"
7099 } else {
7100 "Set Breakpoint"
7101 };
7102
7103 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7104 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7105
7106 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7107 BreakpointState::Enabled => Some("Disable"),
7108 BreakpointState::Disabled => Some("Enable"),
7109 });
7110
7111 let (anchor, breakpoint) =
7112 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7113
7114 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7115 menu.on_blur_subscription(Subscription::new(|| {}))
7116 .context(focus_handle)
7117 .when(run_to_cursor, |this| {
7118 let weak_editor = weak_editor.clone();
7119 this.entry("Run to cursor", None, move |window, cx| {
7120 weak_editor
7121 .update(cx, |editor, cx| {
7122 editor.change_selections(None, window, cx, |s| {
7123 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7124 });
7125 })
7126 .ok();
7127
7128 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7129 })
7130 .separator()
7131 })
7132 .when_some(toggle_state_msg, |this, msg| {
7133 this.entry(msg, None, {
7134 let weak_editor = weak_editor.clone();
7135 let breakpoint = breakpoint.clone();
7136 move |_window, cx| {
7137 weak_editor
7138 .update(cx, |this, cx| {
7139 this.edit_breakpoint_at_anchor(
7140 anchor,
7141 breakpoint.as_ref().clone(),
7142 BreakpointEditAction::InvertState,
7143 cx,
7144 );
7145 })
7146 .log_err();
7147 }
7148 })
7149 })
7150 .entry(set_breakpoint_msg, None, {
7151 let weak_editor = weak_editor.clone();
7152 let breakpoint = breakpoint.clone();
7153 move |_window, cx| {
7154 weak_editor
7155 .update(cx, |this, cx| {
7156 this.edit_breakpoint_at_anchor(
7157 anchor,
7158 breakpoint.as_ref().clone(),
7159 BreakpointEditAction::Toggle,
7160 cx,
7161 );
7162 })
7163 .log_err();
7164 }
7165 })
7166 .entry(log_breakpoint_msg, None, {
7167 let breakpoint = breakpoint.clone();
7168 let weak_editor = weak_editor.clone();
7169 move |window, cx| {
7170 weak_editor
7171 .update(cx, |this, cx| {
7172 this.add_edit_breakpoint_block(
7173 anchor,
7174 breakpoint.as_ref(),
7175 BreakpointPromptEditAction::Log,
7176 window,
7177 cx,
7178 );
7179 })
7180 .log_err();
7181 }
7182 })
7183 .entry(condition_breakpoint_msg, None, {
7184 let breakpoint = breakpoint.clone();
7185 let weak_editor = weak_editor.clone();
7186 move |window, cx| {
7187 weak_editor
7188 .update(cx, |this, cx| {
7189 this.add_edit_breakpoint_block(
7190 anchor,
7191 breakpoint.as_ref(),
7192 BreakpointPromptEditAction::Condition,
7193 window,
7194 cx,
7195 );
7196 })
7197 .log_err();
7198 }
7199 })
7200 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7201 weak_editor
7202 .update(cx, |this, cx| {
7203 this.add_edit_breakpoint_block(
7204 anchor,
7205 breakpoint.as_ref(),
7206 BreakpointPromptEditAction::HitCondition,
7207 window,
7208 cx,
7209 );
7210 })
7211 .log_err();
7212 })
7213 })
7214 }
7215
7216 fn render_breakpoint(
7217 &self,
7218 position: Anchor,
7219 row: DisplayRow,
7220 breakpoint: &Breakpoint,
7221 state: Option<BreakpointSessionState>,
7222 cx: &mut Context<Self>,
7223 ) -> IconButton {
7224 let is_rejected = state.is_some_and(|s| !s.verified);
7225 // Is it a breakpoint that shows up when hovering over gutter?
7226 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7227 (false, false),
7228 |PhantomBreakpointIndicator {
7229 is_active,
7230 display_row,
7231 collides_with_existing_breakpoint,
7232 }| {
7233 (
7234 is_active && display_row == row,
7235 collides_with_existing_breakpoint,
7236 )
7237 },
7238 );
7239
7240 let (color, icon) = {
7241 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7242 (false, false) => ui::IconName::DebugBreakpoint,
7243 (true, false) => ui::IconName::DebugLogBreakpoint,
7244 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7245 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7246 };
7247
7248 let color = if is_phantom {
7249 Color::Hint
7250 } else if is_rejected {
7251 Color::Disabled
7252 } else {
7253 Color::Debugger
7254 };
7255
7256 (color, icon)
7257 };
7258
7259 let breakpoint = Arc::from(breakpoint.clone());
7260
7261 let alt_as_text = gpui::Keystroke {
7262 modifiers: Modifiers::secondary_key(),
7263 ..Default::default()
7264 };
7265 let primary_action_text = if breakpoint.is_disabled() {
7266 "enable"
7267 } else if is_phantom && !collides_with_existing {
7268 "set"
7269 } else {
7270 "unset"
7271 };
7272 let mut primary_text = format!("Click to {primary_action_text}");
7273 if collides_with_existing && !breakpoint.is_disabled() {
7274 use std::fmt::Write;
7275 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7276 }
7277 let primary_text = SharedString::from(primary_text);
7278 let focus_handle = self.focus_handle.clone();
7279
7280 let meta = if is_rejected {
7281 "No executable code is associated with this line."
7282 } else {
7283 "Right-click for more options."
7284 };
7285 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7286 .icon_size(IconSize::XSmall)
7287 .size(ui::ButtonSize::None)
7288 .when(is_rejected, |this| {
7289 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7290 })
7291 .icon_color(color)
7292 .style(ButtonStyle::Transparent)
7293 .on_click(cx.listener({
7294 let breakpoint = breakpoint.clone();
7295
7296 move |editor, event: &ClickEvent, window, cx| {
7297 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7298 BreakpointEditAction::InvertState
7299 } else {
7300 BreakpointEditAction::Toggle
7301 };
7302
7303 window.focus(&editor.focus_handle(cx));
7304 editor.edit_breakpoint_at_anchor(
7305 position,
7306 breakpoint.as_ref().clone(),
7307 edit_action,
7308 cx,
7309 );
7310 }
7311 }))
7312 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7313 editor.set_breakpoint_context_menu(
7314 row,
7315 Some(position),
7316 event.down.position,
7317 window,
7318 cx,
7319 );
7320 }))
7321 .tooltip(move |window, cx| {
7322 Tooltip::with_meta_in(primary_text.clone(), None, meta, &focus_handle, window, cx)
7323 })
7324 }
7325
7326 fn build_tasks_context(
7327 project: &Entity<Project>,
7328 buffer: &Entity<Buffer>,
7329 buffer_row: u32,
7330 tasks: &Arc<RunnableTasks>,
7331 cx: &mut Context<Self>,
7332 ) -> Task<Option<task::TaskContext>> {
7333 let position = Point::new(buffer_row, tasks.column);
7334 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7335 let location = Location {
7336 buffer: buffer.clone(),
7337 range: range_start..range_start,
7338 };
7339 // Fill in the environmental variables from the tree-sitter captures
7340 let mut captured_task_variables = TaskVariables::default();
7341 for (capture_name, value) in tasks.extra_variables.clone() {
7342 captured_task_variables.insert(
7343 task::VariableName::Custom(capture_name.into()),
7344 value.clone(),
7345 );
7346 }
7347 project.update(cx, |project, cx| {
7348 project.task_store().update(cx, |task_store, cx| {
7349 task_store.task_context_for_location(captured_task_variables, location, cx)
7350 })
7351 })
7352 }
7353
7354 pub fn spawn_nearest_task(
7355 &mut self,
7356 action: &SpawnNearestTask,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) {
7360 let Some((workspace, _)) = self.workspace.clone() else {
7361 return;
7362 };
7363 let Some(project) = self.project.clone() else {
7364 return;
7365 };
7366
7367 // Try to find a closest, enclosing node using tree-sitter that has a
7368 // task
7369 let Some((buffer, buffer_row, tasks)) = self
7370 .find_enclosing_node_task(cx)
7371 // Or find the task that's closest in row-distance.
7372 .or_else(|| self.find_closest_task(cx))
7373 else {
7374 return;
7375 };
7376
7377 let reveal_strategy = action.reveal;
7378 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7379 cx.spawn_in(window, async move |_, cx| {
7380 let context = task_context.await?;
7381 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7382
7383 let resolved = &mut resolved_task.resolved;
7384 resolved.reveal = reveal_strategy;
7385
7386 workspace
7387 .update_in(cx, |workspace, window, cx| {
7388 workspace.schedule_resolved_task(
7389 task_source_kind,
7390 resolved_task,
7391 false,
7392 window,
7393 cx,
7394 );
7395 })
7396 .ok()
7397 })
7398 .detach();
7399 }
7400
7401 fn find_closest_task(
7402 &mut self,
7403 cx: &mut Context<Self>,
7404 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7405 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7406
7407 let ((buffer_id, row), tasks) = self
7408 .tasks
7409 .iter()
7410 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7411
7412 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7413 let tasks = Arc::new(tasks.to_owned());
7414 Some((buffer, *row, tasks))
7415 }
7416
7417 fn find_enclosing_node_task(
7418 &mut self,
7419 cx: &mut Context<Self>,
7420 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7421 let snapshot = self.buffer.read(cx).snapshot(cx);
7422 let offset = self.selections.newest::<usize>(cx).head();
7423 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7424 let buffer_id = excerpt.buffer().remote_id();
7425
7426 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7427 let mut cursor = layer.node().walk();
7428
7429 while cursor.goto_first_child_for_byte(offset).is_some() {
7430 if cursor.node().end_byte() == offset {
7431 cursor.goto_next_sibling();
7432 }
7433 }
7434
7435 // Ascend to the smallest ancestor that contains the range and has a task.
7436 loop {
7437 let node = cursor.node();
7438 let node_range = node.byte_range();
7439 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7440
7441 // Check if this node contains our offset
7442 if node_range.start <= offset && node_range.end >= offset {
7443 // If it contains offset, check for task
7444 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7445 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7446 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7447 }
7448 }
7449
7450 if !cursor.goto_parent() {
7451 break;
7452 }
7453 }
7454 None
7455 }
7456
7457 fn render_run_indicator(
7458 &self,
7459 _style: &EditorStyle,
7460 is_active: bool,
7461 row: DisplayRow,
7462 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7463 cx: &mut Context<Self>,
7464 ) -> IconButton {
7465 let color = Color::Muted;
7466 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7467
7468 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7469 .shape(ui::IconButtonShape::Square)
7470 .icon_size(IconSize::XSmall)
7471 .icon_color(color)
7472 .toggle_state(is_active)
7473 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7474 let quick_launch = e.down.button == MouseButton::Left;
7475 window.focus(&editor.focus_handle(cx));
7476 editor.toggle_code_actions(
7477 &ToggleCodeActions {
7478 deployed_from_indicator: Some(row),
7479 quick_launch,
7480 },
7481 window,
7482 cx,
7483 );
7484 }))
7485 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7486 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7487 }))
7488 }
7489
7490 pub fn context_menu_visible(&self) -> bool {
7491 !self.edit_prediction_preview_is_active()
7492 && self
7493 .context_menu
7494 .borrow()
7495 .as_ref()
7496 .map_or(false, |menu| menu.visible())
7497 }
7498
7499 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7500 self.context_menu
7501 .borrow()
7502 .as_ref()
7503 .map(|menu| menu.origin())
7504 }
7505
7506 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7507 self.context_menu_options = Some(options);
7508 }
7509
7510 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7511 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7512
7513 fn render_edit_prediction_popover(
7514 &mut self,
7515 text_bounds: &Bounds<Pixels>,
7516 content_origin: gpui::Point<Pixels>,
7517 right_margin: Pixels,
7518 editor_snapshot: &EditorSnapshot,
7519 visible_row_range: Range<DisplayRow>,
7520 scroll_top: f32,
7521 scroll_bottom: f32,
7522 line_layouts: &[LineWithInvisibles],
7523 line_height: Pixels,
7524 scroll_pixel_position: gpui::Point<Pixels>,
7525 newest_selection_head: Option<DisplayPoint>,
7526 editor_width: Pixels,
7527 style: &EditorStyle,
7528 window: &mut Window,
7529 cx: &mut App,
7530 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7531 if self.mode().is_minimap() {
7532 return None;
7533 }
7534 let active_inline_completion = self.active_inline_completion.as_ref()?;
7535
7536 if self.edit_prediction_visible_in_cursor_popover(true) {
7537 return None;
7538 }
7539
7540 match &active_inline_completion.completion {
7541 InlineCompletion::Move { target, .. } => {
7542 let target_display_point = target.to_display_point(editor_snapshot);
7543
7544 if self.edit_prediction_requires_modifier() {
7545 if !self.edit_prediction_preview_is_active() {
7546 return None;
7547 }
7548
7549 self.render_edit_prediction_modifier_jump_popover(
7550 text_bounds,
7551 content_origin,
7552 visible_row_range,
7553 line_layouts,
7554 line_height,
7555 scroll_pixel_position,
7556 newest_selection_head,
7557 target_display_point,
7558 window,
7559 cx,
7560 )
7561 } else {
7562 self.render_edit_prediction_eager_jump_popover(
7563 text_bounds,
7564 content_origin,
7565 editor_snapshot,
7566 visible_row_range,
7567 scroll_top,
7568 scroll_bottom,
7569 line_height,
7570 scroll_pixel_position,
7571 target_display_point,
7572 editor_width,
7573 window,
7574 cx,
7575 )
7576 }
7577 }
7578 InlineCompletion::Edit {
7579 display_mode: EditDisplayMode::Inline,
7580 ..
7581 } => None,
7582 InlineCompletion::Edit {
7583 display_mode: EditDisplayMode::TabAccept,
7584 edits,
7585 ..
7586 } => {
7587 let range = &edits.first()?.0;
7588 let target_display_point = range.end.to_display_point(editor_snapshot);
7589
7590 self.render_edit_prediction_end_of_line_popover(
7591 "Accept",
7592 editor_snapshot,
7593 visible_row_range,
7594 target_display_point,
7595 line_height,
7596 scroll_pixel_position,
7597 content_origin,
7598 editor_width,
7599 window,
7600 cx,
7601 )
7602 }
7603 InlineCompletion::Edit {
7604 edits,
7605 edit_preview,
7606 display_mode: EditDisplayMode::DiffPopover,
7607 snapshot,
7608 } => self.render_edit_prediction_diff_popover(
7609 text_bounds,
7610 content_origin,
7611 right_margin,
7612 editor_snapshot,
7613 visible_row_range,
7614 line_layouts,
7615 line_height,
7616 scroll_pixel_position,
7617 newest_selection_head,
7618 editor_width,
7619 style,
7620 edits,
7621 edit_preview,
7622 snapshot,
7623 window,
7624 cx,
7625 ),
7626 }
7627 }
7628
7629 fn render_edit_prediction_modifier_jump_popover(
7630 &mut self,
7631 text_bounds: &Bounds<Pixels>,
7632 content_origin: gpui::Point<Pixels>,
7633 visible_row_range: Range<DisplayRow>,
7634 line_layouts: &[LineWithInvisibles],
7635 line_height: Pixels,
7636 scroll_pixel_position: gpui::Point<Pixels>,
7637 newest_selection_head: Option<DisplayPoint>,
7638 target_display_point: DisplayPoint,
7639 window: &mut Window,
7640 cx: &mut App,
7641 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7642 let scrolled_content_origin =
7643 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7644
7645 const SCROLL_PADDING_Y: Pixels = px(12.);
7646
7647 if target_display_point.row() < visible_row_range.start {
7648 return self.render_edit_prediction_scroll_popover(
7649 |_| SCROLL_PADDING_Y,
7650 IconName::ArrowUp,
7651 visible_row_range,
7652 line_layouts,
7653 newest_selection_head,
7654 scrolled_content_origin,
7655 window,
7656 cx,
7657 );
7658 } else if target_display_point.row() >= visible_row_range.end {
7659 return self.render_edit_prediction_scroll_popover(
7660 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7661 IconName::ArrowDown,
7662 visible_row_range,
7663 line_layouts,
7664 newest_selection_head,
7665 scrolled_content_origin,
7666 window,
7667 cx,
7668 );
7669 }
7670
7671 const POLE_WIDTH: Pixels = px(2.);
7672
7673 let line_layout =
7674 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7675 let target_column = target_display_point.column() as usize;
7676
7677 let target_x = line_layout.x_for_index(target_column);
7678 let target_y =
7679 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7680
7681 let flag_on_right = target_x < text_bounds.size.width / 2.;
7682
7683 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7684 border_color.l += 0.001;
7685
7686 let mut element = v_flex()
7687 .items_end()
7688 .when(flag_on_right, |el| el.items_start())
7689 .child(if flag_on_right {
7690 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7691 .rounded_bl(px(0.))
7692 .rounded_tl(px(0.))
7693 .border_l_2()
7694 .border_color(border_color)
7695 } else {
7696 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7697 .rounded_br(px(0.))
7698 .rounded_tr(px(0.))
7699 .border_r_2()
7700 .border_color(border_color)
7701 })
7702 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7703 .into_any();
7704
7705 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7706
7707 let mut origin = scrolled_content_origin + point(target_x, target_y)
7708 - point(
7709 if flag_on_right {
7710 POLE_WIDTH
7711 } else {
7712 size.width - POLE_WIDTH
7713 },
7714 size.height - line_height,
7715 );
7716
7717 origin.x = origin.x.max(content_origin.x);
7718
7719 element.prepaint_at(origin, window, cx);
7720
7721 Some((element, origin))
7722 }
7723
7724 fn render_edit_prediction_scroll_popover(
7725 &mut self,
7726 to_y: impl Fn(Size<Pixels>) -> Pixels,
7727 scroll_icon: IconName,
7728 visible_row_range: Range<DisplayRow>,
7729 line_layouts: &[LineWithInvisibles],
7730 newest_selection_head: Option<DisplayPoint>,
7731 scrolled_content_origin: gpui::Point<Pixels>,
7732 window: &mut Window,
7733 cx: &mut App,
7734 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7735 let mut element = self
7736 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7737 .into_any();
7738
7739 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7740
7741 let cursor = newest_selection_head?;
7742 let cursor_row_layout =
7743 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7744 let cursor_column = cursor.column() as usize;
7745
7746 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7747
7748 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7749
7750 element.prepaint_at(origin, window, cx);
7751 Some((element, origin))
7752 }
7753
7754 fn render_edit_prediction_eager_jump_popover(
7755 &mut self,
7756 text_bounds: &Bounds<Pixels>,
7757 content_origin: gpui::Point<Pixels>,
7758 editor_snapshot: &EditorSnapshot,
7759 visible_row_range: Range<DisplayRow>,
7760 scroll_top: f32,
7761 scroll_bottom: f32,
7762 line_height: Pixels,
7763 scroll_pixel_position: gpui::Point<Pixels>,
7764 target_display_point: DisplayPoint,
7765 editor_width: Pixels,
7766 window: &mut Window,
7767 cx: &mut App,
7768 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7769 if target_display_point.row().as_f32() < scroll_top {
7770 let mut element = self
7771 .render_edit_prediction_line_popover(
7772 "Jump to Edit",
7773 Some(IconName::ArrowUp),
7774 window,
7775 cx,
7776 )?
7777 .into_any();
7778
7779 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7780 let offset = point(
7781 (text_bounds.size.width - size.width) / 2.,
7782 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7783 );
7784
7785 let origin = text_bounds.origin + offset;
7786 element.prepaint_at(origin, window, cx);
7787 Some((element, origin))
7788 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7789 let mut element = self
7790 .render_edit_prediction_line_popover(
7791 "Jump to Edit",
7792 Some(IconName::ArrowDown),
7793 window,
7794 cx,
7795 )?
7796 .into_any();
7797
7798 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7799 let offset = point(
7800 (text_bounds.size.width - size.width) / 2.,
7801 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7802 );
7803
7804 let origin = text_bounds.origin + offset;
7805 element.prepaint_at(origin, window, cx);
7806 Some((element, origin))
7807 } else {
7808 self.render_edit_prediction_end_of_line_popover(
7809 "Jump to Edit",
7810 editor_snapshot,
7811 visible_row_range,
7812 target_display_point,
7813 line_height,
7814 scroll_pixel_position,
7815 content_origin,
7816 editor_width,
7817 window,
7818 cx,
7819 )
7820 }
7821 }
7822
7823 fn render_edit_prediction_end_of_line_popover(
7824 self: &mut Editor,
7825 label: &'static str,
7826 editor_snapshot: &EditorSnapshot,
7827 visible_row_range: Range<DisplayRow>,
7828 target_display_point: DisplayPoint,
7829 line_height: Pixels,
7830 scroll_pixel_position: gpui::Point<Pixels>,
7831 content_origin: gpui::Point<Pixels>,
7832 editor_width: Pixels,
7833 window: &mut Window,
7834 cx: &mut App,
7835 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7836 let target_line_end = DisplayPoint::new(
7837 target_display_point.row(),
7838 editor_snapshot.line_len(target_display_point.row()),
7839 );
7840
7841 let mut element = self
7842 .render_edit_prediction_line_popover(label, None, window, cx)?
7843 .into_any();
7844
7845 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7846
7847 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7848
7849 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7850 let mut origin = start_point
7851 + line_origin
7852 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7853 origin.x = origin.x.max(content_origin.x);
7854
7855 let max_x = content_origin.x + editor_width - size.width;
7856
7857 if origin.x > max_x {
7858 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7859
7860 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7861 origin.y += offset;
7862 IconName::ArrowUp
7863 } else {
7864 origin.y -= offset;
7865 IconName::ArrowDown
7866 };
7867
7868 element = self
7869 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7870 .into_any();
7871
7872 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7873
7874 origin.x = content_origin.x + editor_width - size.width - px(2.);
7875 }
7876
7877 element.prepaint_at(origin, window, cx);
7878 Some((element, origin))
7879 }
7880
7881 fn render_edit_prediction_diff_popover(
7882 self: &Editor,
7883 text_bounds: &Bounds<Pixels>,
7884 content_origin: gpui::Point<Pixels>,
7885 right_margin: Pixels,
7886 editor_snapshot: &EditorSnapshot,
7887 visible_row_range: Range<DisplayRow>,
7888 line_layouts: &[LineWithInvisibles],
7889 line_height: Pixels,
7890 scroll_pixel_position: gpui::Point<Pixels>,
7891 newest_selection_head: Option<DisplayPoint>,
7892 editor_width: Pixels,
7893 style: &EditorStyle,
7894 edits: &Vec<(Range<Anchor>, String)>,
7895 edit_preview: &Option<language::EditPreview>,
7896 snapshot: &language::BufferSnapshot,
7897 window: &mut Window,
7898 cx: &mut App,
7899 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7900 let edit_start = edits
7901 .first()
7902 .unwrap()
7903 .0
7904 .start
7905 .to_display_point(editor_snapshot);
7906 let edit_end = edits
7907 .last()
7908 .unwrap()
7909 .0
7910 .end
7911 .to_display_point(editor_snapshot);
7912
7913 let is_visible = visible_row_range.contains(&edit_start.row())
7914 || visible_row_range.contains(&edit_end.row());
7915 if !is_visible {
7916 return None;
7917 }
7918
7919 let highlighted_edits =
7920 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7921
7922 let styled_text = highlighted_edits.to_styled_text(&style.text);
7923 let line_count = highlighted_edits.text.lines().count();
7924
7925 const BORDER_WIDTH: Pixels = px(1.);
7926
7927 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7928 let has_keybind = keybind.is_some();
7929
7930 let mut element = h_flex()
7931 .items_start()
7932 .child(
7933 h_flex()
7934 .bg(cx.theme().colors().editor_background)
7935 .border(BORDER_WIDTH)
7936 .shadow_sm()
7937 .border_color(cx.theme().colors().border)
7938 .rounded_l_lg()
7939 .when(line_count > 1, |el| el.rounded_br_lg())
7940 .pr_1()
7941 .child(styled_text),
7942 )
7943 .child(
7944 h_flex()
7945 .h(line_height + BORDER_WIDTH * 2.)
7946 .px_1p5()
7947 .gap_1()
7948 // Workaround: For some reason, there's a gap if we don't do this
7949 .ml(-BORDER_WIDTH)
7950 .shadow(smallvec![gpui::BoxShadow {
7951 color: gpui::black().opacity(0.05),
7952 offset: point(px(1.), px(1.)),
7953 blur_radius: px(2.),
7954 spread_radius: px(0.),
7955 }])
7956 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7957 .border(BORDER_WIDTH)
7958 .border_color(cx.theme().colors().border)
7959 .rounded_r_lg()
7960 .id("edit_prediction_diff_popover_keybind")
7961 .when(!has_keybind, |el| {
7962 let status_colors = cx.theme().status();
7963
7964 el.bg(status_colors.error_background)
7965 .border_color(status_colors.error.opacity(0.6))
7966 .child(Icon::new(IconName::Info).color(Color::Error))
7967 .cursor_default()
7968 .hoverable_tooltip(move |_window, cx| {
7969 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7970 })
7971 })
7972 .children(keybind),
7973 )
7974 .into_any();
7975
7976 let longest_row =
7977 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7978 let longest_line_width = if visible_row_range.contains(&longest_row) {
7979 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7980 } else {
7981 layout_line(
7982 longest_row,
7983 editor_snapshot,
7984 style,
7985 editor_width,
7986 |_| false,
7987 window,
7988 cx,
7989 )
7990 .width
7991 };
7992
7993 let viewport_bounds =
7994 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7995 right: -right_margin,
7996 ..Default::default()
7997 });
7998
7999 let x_after_longest =
8000 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8001 - scroll_pixel_position.x;
8002
8003 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8004
8005 // Fully visible if it can be displayed within the window (allow overlapping other
8006 // panes). However, this is only allowed if the popover starts within text_bounds.
8007 let can_position_to_the_right = x_after_longest < text_bounds.right()
8008 && x_after_longest + element_bounds.width < viewport_bounds.right();
8009
8010 let mut origin = if can_position_to_the_right {
8011 point(
8012 x_after_longest,
8013 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8014 - scroll_pixel_position.y,
8015 )
8016 } else {
8017 let cursor_row = newest_selection_head.map(|head| head.row());
8018 let above_edit = edit_start
8019 .row()
8020 .0
8021 .checked_sub(line_count as u32)
8022 .map(DisplayRow);
8023 let below_edit = Some(edit_end.row() + 1);
8024 let above_cursor =
8025 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8026 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8027
8028 // Place the edit popover adjacent to the edit if there is a location
8029 // available that is onscreen and does not obscure the cursor. Otherwise,
8030 // place it adjacent to the cursor.
8031 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8032 .into_iter()
8033 .flatten()
8034 .find(|&start_row| {
8035 let end_row = start_row + line_count as u32;
8036 visible_row_range.contains(&start_row)
8037 && visible_row_range.contains(&end_row)
8038 && cursor_row.map_or(true, |cursor_row| {
8039 !((start_row..end_row).contains(&cursor_row))
8040 })
8041 })?;
8042
8043 content_origin
8044 + point(
8045 -scroll_pixel_position.x,
8046 row_target.as_f32() * line_height - scroll_pixel_position.y,
8047 )
8048 };
8049
8050 origin.x -= BORDER_WIDTH;
8051
8052 window.defer_draw(element, origin, 1);
8053
8054 // Do not return an element, since it will already be drawn due to defer_draw.
8055 None
8056 }
8057
8058 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8059 px(30.)
8060 }
8061
8062 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8063 if self.read_only(cx) {
8064 cx.theme().players().read_only()
8065 } else {
8066 self.style.as_ref().unwrap().local_player
8067 }
8068 }
8069
8070 fn render_edit_prediction_accept_keybind(
8071 &self,
8072 window: &mut Window,
8073 cx: &App,
8074 ) -> Option<AnyElement> {
8075 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8076 let accept_keystroke = accept_binding.keystroke()?;
8077
8078 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8079
8080 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8081 Color::Accent
8082 } else {
8083 Color::Muted
8084 };
8085
8086 h_flex()
8087 .px_0p5()
8088 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8089 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8090 .text_size(TextSize::XSmall.rems(cx))
8091 .child(h_flex().children(ui::render_modifiers(
8092 &accept_keystroke.modifiers,
8093 PlatformStyle::platform(),
8094 Some(modifiers_color),
8095 Some(IconSize::XSmall.rems().into()),
8096 true,
8097 )))
8098 .when(is_platform_style_mac, |parent| {
8099 parent.child(accept_keystroke.key.clone())
8100 })
8101 .when(!is_platform_style_mac, |parent| {
8102 parent.child(
8103 Key::new(
8104 util::capitalize(&accept_keystroke.key),
8105 Some(Color::Default),
8106 )
8107 .size(Some(IconSize::XSmall.rems().into())),
8108 )
8109 })
8110 .into_any()
8111 .into()
8112 }
8113
8114 fn render_edit_prediction_line_popover(
8115 &self,
8116 label: impl Into<SharedString>,
8117 icon: Option<IconName>,
8118 window: &mut Window,
8119 cx: &App,
8120 ) -> Option<Stateful<Div>> {
8121 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8122
8123 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8124 let has_keybind = keybind.is_some();
8125
8126 let result = h_flex()
8127 .id("ep-line-popover")
8128 .py_0p5()
8129 .pl_1()
8130 .pr(padding_right)
8131 .gap_1()
8132 .rounded_md()
8133 .border_1()
8134 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8135 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8136 .shadow_sm()
8137 .when(!has_keybind, |el| {
8138 let status_colors = cx.theme().status();
8139
8140 el.bg(status_colors.error_background)
8141 .border_color(status_colors.error.opacity(0.6))
8142 .pl_2()
8143 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8144 .cursor_default()
8145 .hoverable_tooltip(move |_window, cx| {
8146 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8147 })
8148 })
8149 .children(keybind)
8150 .child(
8151 Label::new(label)
8152 .size(LabelSize::Small)
8153 .when(!has_keybind, |el| {
8154 el.color(cx.theme().status().error.into()).strikethrough()
8155 }),
8156 )
8157 .when(!has_keybind, |el| {
8158 el.child(
8159 h_flex().ml_1().child(
8160 Icon::new(IconName::Info)
8161 .size(IconSize::Small)
8162 .color(cx.theme().status().error.into()),
8163 ),
8164 )
8165 })
8166 .when_some(icon, |element, icon| {
8167 element.child(
8168 div()
8169 .mt(px(1.5))
8170 .child(Icon::new(icon).size(IconSize::Small)),
8171 )
8172 });
8173
8174 Some(result)
8175 }
8176
8177 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8178 let accent_color = cx.theme().colors().text_accent;
8179 let editor_bg_color = cx.theme().colors().editor_background;
8180 editor_bg_color.blend(accent_color.opacity(0.1))
8181 }
8182
8183 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8184 let accent_color = cx.theme().colors().text_accent;
8185 let editor_bg_color = cx.theme().colors().editor_background;
8186 editor_bg_color.blend(accent_color.opacity(0.6))
8187 }
8188
8189 fn render_edit_prediction_cursor_popover(
8190 &self,
8191 min_width: Pixels,
8192 max_width: Pixels,
8193 cursor_point: Point,
8194 style: &EditorStyle,
8195 accept_keystroke: Option<&gpui::Keystroke>,
8196 _window: &Window,
8197 cx: &mut Context<Editor>,
8198 ) -> Option<AnyElement> {
8199 let provider = self.edit_prediction_provider.as_ref()?;
8200
8201 if provider.provider.needs_terms_acceptance(cx) {
8202 return Some(
8203 h_flex()
8204 .min_w(min_width)
8205 .flex_1()
8206 .px_2()
8207 .py_1()
8208 .gap_3()
8209 .elevation_2(cx)
8210 .hover(|style| style.bg(cx.theme().colors().element_hover))
8211 .id("accept-terms")
8212 .cursor_pointer()
8213 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8214 .on_click(cx.listener(|this, _event, window, cx| {
8215 cx.stop_propagation();
8216 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8217 window.dispatch_action(
8218 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8219 cx,
8220 );
8221 }))
8222 .child(
8223 h_flex()
8224 .flex_1()
8225 .gap_2()
8226 .child(Icon::new(IconName::ZedPredict))
8227 .child(Label::new("Accept Terms of Service"))
8228 .child(div().w_full())
8229 .child(
8230 Icon::new(IconName::ArrowUpRight)
8231 .color(Color::Muted)
8232 .size(IconSize::Small),
8233 )
8234 .into_any_element(),
8235 )
8236 .into_any(),
8237 );
8238 }
8239
8240 let is_refreshing = provider.provider.is_refreshing(cx);
8241
8242 fn pending_completion_container() -> Div {
8243 h_flex()
8244 .h_full()
8245 .flex_1()
8246 .gap_2()
8247 .child(Icon::new(IconName::ZedPredict))
8248 }
8249
8250 let completion = match &self.active_inline_completion {
8251 Some(prediction) => {
8252 if !self.has_visible_completions_menu() {
8253 const RADIUS: Pixels = px(6.);
8254 const BORDER_WIDTH: Pixels = px(1.);
8255
8256 return Some(
8257 h_flex()
8258 .elevation_2(cx)
8259 .border(BORDER_WIDTH)
8260 .border_color(cx.theme().colors().border)
8261 .when(accept_keystroke.is_none(), |el| {
8262 el.border_color(cx.theme().status().error)
8263 })
8264 .rounded(RADIUS)
8265 .rounded_tl(px(0.))
8266 .overflow_hidden()
8267 .child(div().px_1p5().child(match &prediction.completion {
8268 InlineCompletion::Move { target, snapshot } => {
8269 use text::ToPoint as _;
8270 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8271 {
8272 Icon::new(IconName::ZedPredictDown)
8273 } else {
8274 Icon::new(IconName::ZedPredictUp)
8275 }
8276 }
8277 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8278 }))
8279 .child(
8280 h_flex()
8281 .gap_1()
8282 .py_1()
8283 .px_2()
8284 .rounded_r(RADIUS - BORDER_WIDTH)
8285 .border_l_1()
8286 .border_color(cx.theme().colors().border)
8287 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8288 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8289 el.child(
8290 Label::new("Hold")
8291 .size(LabelSize::Small)
8292 .when(accept_keystroke.is_none(), |el| {
8293 el.strikethrough()
8294 })
8295 .line_height_style(LineHeightStyle::UiLabel),
8296 )
8297 })
8298 .id("edit_prediction_cursor_popover_keybind")
8299 .when(accept_keystroke.is_none(), |el| {
8300 let status_colors = cx.theme().status();
8301
8302 el.bg(status_colors.error_background)
8303 .border_color(status_colors.error.opacity(0.6))
8304 .child(Icon::new(IconName::Info).color(Color::Error))
8305 .cursor_default()
8306 .hoverable_tooltip(move |_window, cx| {
8307 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8308 .into()
8309 })
8310 })
8311 .when_some(
8312 accept_keystroke.as_ref(),
8313 |el, accept_keystroke| {
8314 el.child(h_flex().children(ui::render_modifiers(
8315 &accept_keystroke.modifiers,
8316 PlatformStyle::platform(),
8317 Some(Color::Default),
8318 Some(IconSize::XSmall.rems().into()),
8319 false,
8320 )))
8321 },
8322 ),
8323 )
8324 .into_any(),
8325 );
8326 }
8327
8328 self.render_edit_prediction_cursor_popover_preview(
8329 prediction,
8330 cursor_point,
8331 style,
8332 cx,
8333 )?
8334 }
8335
8336 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8337 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8338 stale_completion,
8339 cursor_point,
8340 style,
8341 cx,
8342 )?,
8343
8344 None => {
8345 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8346 }
8347 },
8348
8349 None => pending_completion_container().child(Label::new("No Prediction")),
8350 };
8351
8352 let completion = if is_refreshing {
8353 completion
8354 .with_animation(
8355 "loading-completion",
8356 Animation::new(Duration::from_secs(2))
8357 .repeat()
8358 .with_easing(pulsating_between(0.4, 0.8)),
8359 |label, delta| label.opacity(delta),
8360 )
8361 .into_any_element()
8362 } else {
8363 completion.into_any_element()
8364 };
8365
8366 let has_completion = self.active_inline_completion.is_some();
8367
8368 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8369 Some(
8370 h_flex()
8371 .min_w(min_width)
8372 .max_w(max_width)
8373 .flex_1()
8374 .elevation_2(cx)
8375 .border_color(cx.theme().colors().border)
8376 .child(
8377 div()
8378 .flex_1()
8379 .py_1()
8380 .px_2()
8381 .overflow_hidden()
8382 .child(completion),
8383 )
8384 .when_some(accept_keystroke, |el, accept_keystroke| {
8385 if !accept_keystroke.modifiers.modified() {
8386 return el;
8387 }
8388
8389 el.child(
8390 h_flex()
8391 .h_full()
8392 .border_l_1()
8393 .rounded_r_lg()
8394 .border_color(cx.theme().colors().border)
8395 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8396 .gap_1()
8397 .py_1()
8398 .px_2()
8399 .child(
8400 h_flex()
8401 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8402 .when(is_platform_style_mac, |parent| parent.gap_1())
8403 .child(h_flex().children(ui::render_modifiers(
8404 &accept_keystroke.modifiers,
8405 PlatformStyle::platform(),
8406 Some(if !has_completion {
8407 Color::Muted
8408 } else {
8409 Color::Default
8410 }),
8411 None,
8412 false,
8413 ))),
8414 )
8415 .child(Label::new("Preview").into_any_element())
8416 .opacity(if has_completion { 1.0 } else { 0.4 }),
8417 )
8418 })
8419 .into_any(),
8420 )
8421 }
8422
8423 fn render_edit_prediction_cursor_popover_preview(
8424 &self,
8425 completion: &InlineCompletionState,
8426 cursor_point: Point,
8427 style: &EditorStyle,
8428 cx: &mut Context<Editor>,
8429 ) -> Option<Div> {
8430 use text::ToPoint as _;
8431
8432 fn render_relative_row_jump(
8433 prefix: impl Into<String>,
8434 current_row: u32,
8435 target_row: u32,
8436 ) -> Div {
8437 let (row_diff, arrow) = if target_row < current_row {
8438 (current_row - target_row, IconName::ArrowUp)
8439 } else {
8440 (target_row - current_row, IconName::ArrowDown)
8441 };
8442
8443 h_flex()
8444 .child(
8445 Label::new(format!("{}{}", prefix.into(), row_diff))
8446 .color(Color::Muted)
8447 .size(LabelSize::Small),
8448 )
8449 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8450 }
8451
8452 match &completion.completion {
8453 InlineCompletion::Move {
8454 target, snapshot, ..
8455 } => Some(
8456 h_flex()
8457 .px_2()
8458 .gap_2()
8459 .flex_1()
8460 .child(
8461 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8462 Icon::new(IconName::ZedPredictDown)
8463 } else {
8464 Icon::new(IconName::ZedPredictUp)
8465 },
8466 )
8467 .child(Label::new("Jump to Edit")),
8468 ),
8469
8470 InlineCompletion::Edit {
8471 edits,
8472 edit_preview,
8473 snapshot,
8474 display_mode: _,
8475 } => {
8476 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8477
8478 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8479 &snapshot,
8480 &edits,
8481 edit_preview.as_ref()?,
8482 true,
8483 cx,
8484 )
8485 .first_line_preview();
8486
8487 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8488 .with_default_highlights(&style.text, highlighted_edits.highlights);
8489
8490 let preview = h_flex()
8491 .gap_1()
8492 .min_w_16()
8493 .child(styled_text)
8494 .when(has_more_lines, |parent| parent.child("…"));
8495
8496 let left = if first_edit_row != cursor_point.row {
8497 render_relative_row_jump("", cursor_point.row, first_edit_row)
8498 .into_any_element()
8499 } else {
8500 Icon::new(IconName::ZedPredict).into_any_element()
8501 };
8502
8503 Some(
8504 h_flex()
8505 .h_full()
8506 .flex_1()
8507 .gap_2()
8508 .pr_1()
8509 .overflow_x_hidden()
8510 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8511 .child(left)
8512 .child(preview),
8513 )
8514 }
8515 }
8516 }
8517
8518 fn render_context_menu(
8519 &self,
8520 style: &EditorStyle,
8521 max_height_in_lines: u32,
8522 window: &mut Window,
8523 cx: &mut Context<Editor>,
8524 ) -> Option<AnyElement> {
8525 let menu = self.context_menu.borrow();
8526 let menu = menu.as_ref()?;
8527 if !menu.visible() {
8528 return None;
8529 };
8530 Some(menu.render(style, max_height_in_lines, window, cx))
8531 }
8532
8533 fn render_context_menu_aside(
8534 &mut self,
8535 max_size: Size<Pixels>,
8536 window: &mut Window,
8537 cx: &mut Context<Editor>,
8538 ) -> Option<AnyElement> {
8539 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8540 if menu.visible() {
8541 menu.render_aside(self, max_size, window, cx)
8542 } else {
8543 None
8544 }
8545 })
8546 }
8547
8548 fn hide_context_menu(
8549 &mut self,
8550 window: &mut Window,
8551 cx: &mut Context<Self>,
8552 ) -> Option<CodeContextMenu> {
8553 cx.notify();
8554 self.completion_tasks.clear();
8555 let context_menu = self.context_menu.borrow_mut().take();
8556 self.stale_inline_completion_in_menu.take();
8557 self.update_visible_inline_completion(window, cx);
8558 context_menu
8559 }
8560
8561 fn show_snippet_choices(
8562 &mut self,
8563 choices: &Vec<String>,
8564 selection: Range<Anchor>,
8565 cx: &mut Context<Self>,
8566 ) {
8567 if selection.start.buffer_id.is_none() {
8568 return;
8569 }
8570 let buffer_id = selection.start.buffer_id.unwrap();
8571 let buffer = self.buffer().read(cx).buffer(buffer_id);
8572 let id = post_inc(&mut self.next_completion_id);
8573 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8574
8575 if let Some(buffer) = buffer {
8576 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8577 CompletionsMenu::new_snippet_choices(
8578 id,
8579 true,
8580 choices,
8581 selection,
8582 buffer,
8583 snippet_sort_order,
8584 ),
8585 ));
8586 }
8587 }
8588
8589 pub fn insert_snippet(
8590 &mut self,
8591 insertion_ranges: &[Range<usize>],
8592 snippet: Snippet,
8593 window: &mut Window,
8594 cx: &mut Context<Self>,
8595 ) -> Result<()> {
8596 struct Tabstop<T> {
8597 is_end_tabstop: bool,
8598 ranges: Vec<Range<T>>,
8599 choices: Option<Vec<String>>,
8600 }
8601
8602 let tabstops = self.buffer.update(cx, |buffer, cx| {
8603 let snippet_text: Arc<str> = snippet.text.clone().into();
8604 let edits = insertion_ranges
8605 .iter()
8606 .cloned()
8607 .map(|range| (range, snippet_text.clone()));
8608 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8609
8610 let snapshot = &*buffer.read(cx);
8611 let snippet = &snippet;
8612 snippet
8613 .tabstops
8614 .iter()
8615 .map(|tabstop| {
8616 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8617 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8618 });
8619 let mut tabstop_ranges = tabstop
8620 .ranges
8621 .iter()
8622 .flat_map(|tabstop_range| {
8623 let mut delta = 0_isize;
8624 insertion_ranges.iter().map(move |insertion_range| {
8625 let insertion_start = insertion_range.start as isize + delta;
8626 delta +=
8627 snippet.text.len() as isize - insertion_range.len() as isize;
8628
8629 let start = ((insertion_start + tabstop_range.start) as usize)
8630 .min(snapshot.len());
8631 let end = ((insertion_start + tabstop_range.end) as usize)
8632 .min(snapshot.len());
8633 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8634 })
8635 })
8636 .collect::<Vec<_>>();
8637 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8638
8639 Tabstop {
8640 is_end_tabstop,
8641 ranges: tabstop_ranges,
8642 choices: tabstop.choices.clone(),
8643 }
8644 })
8645 .collect::<Vec<_>>()
8646 });
8647 if let Some(tabstop) = tabstops.first() {
8648 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8649 s.select_ranges(tabstop.ranges.iter().cloned());
8650 });
8651
8652 if let Some(choices) = &tabstop.choices {
8653 if let Some(selection) = tabstop.ranges.first() {
8654 self.show_snippet_choices(choices, selection.clone(), cx)
8655 }
8656 }
8657
8658 // If we're already at the last tabstop and it's at the end of the snippet,
8659 // we're done, we don't need to keep the state around.
8660 if !tabstop.is_end_tabstop {
8661 let choices = tabstops
8662 .iter()
8663 .map(|tabstop| tabstop.choices.clone())
8664 .collect();
8665
8666 let ranges = tabstops
8667 .into_iter()
8668 .map(|tabstop| tabstop.ranges)
8669 .collect::<Vec<_>>();
8670
8671 self.snippet_stack.push(SnippetState {
8672 active_index: 0,
8673 ranges,
8674 choices,
8675 });
8676 }
8677
8678 // Check whether the just-entered snippet ends with an auto-closable bracket.
8679 if self.autoclose_regions.is_empty() {
8680 let snapshot = self.buffer.read(cx).snapshot(cx);
8681 for selection in &mut self.selections.all::<Point>(cx) {
8682 let selection_head = selection.head();
8683 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8684 continue;
8685 };
8686
8687 let mut bracket_pair = None;
8688 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8689 let prev_chars = snapshot
8690 .reversed_chars_at(selection_head)
8691 .collect::<String>();
8692 for (pair, enabled) in scope.brackets() {
8693 if enabled
8694 && pair.close
8695 && prev_chars.starts_with(pair.start.as_str())
8696 && next_chars.starts_with(pair.end.as_str())
8697 {
8698 bracket_pair = Some(pair.clone());
8699 break;
8700 }
8701 }
8702 if let Some(pair) = bracket_pair {
8703 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8704 let autoclose_enabled =
8705 self.use_autoclose && snapshot_settings.use_autoclose;
8706 if autoclose_enabled {
8707 let start = snapshot.anchor_after(selection_head);
8708 let end = snapshot.anchor_after(selection_head);
8709 self.autoclose_regions.push(AutocloseRegion {
8710 selection_id: selection.id,
8711 range: start..end,
8712 pair,
8713 });
8714 }
8715 }
8716 }
8717 }
8718 }
8719 Ok(())
8720 }
8721
8722 pub fn move_to_next_snippet_tabstop(
8723 &mut self,
8724 window: &mut Window,
8725 cx: &mut Context<Self>,
8726 ) -> bool {
8727 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8728 }
8729
8730 pub fn move_to_prev_snippet_tabstop(
8731 &mut self,
8732 window: &mut Window,
8733 cx: &mut Context<Self>,
8734 ) -> bool {
8735 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8736 }
8737
8738 pub fn move_to_snippet_tabstop(
8739 &mut self,
8740 bias: Bias,
8741 window: &mut Window,
8742 cx: &mut Context<Self>,
8743 ) -> bool {
8744 if let Some(mut snippet) = self.snippet_stack.pop() {
8745 match bias {
8746 Bias::Left => {
8747 if snippet.active_index > 0 {
8748 snippet.active_index -= 1;
8749 } else {
8750 self.snippet_stack.push(snippet);
8751 return false;
8752 }
8753 }
8754 Bias::Right => {
8755 if snippet.active_index + 1 < snippet.ranges.len() {
8756 snippet.active_index += 1;
8757 } else {
8758 self.snippet_stack.push(snippet);
8759 return false;
8760 }
8761 }
8762 }
8763 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8764 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8765 s.select_anchor_ranges(current_ranges.iter().cloned())
8766 });
8767
8768 if let Some(choices) = &snippet.choices[snippet.active_index] {
8769 if let Some(selection) = current_ranges.first() {
8770 self.show_snippet_choices(&choices, selection.clone(), cx);
8771 }
8772 }
8773
8774 // If snippet state is not at the last tabstop, push it back on the stack
8775 if snippet.active_index + 1 < snippet.ranges.len() {
8776 self.snippet_stack.push(snippet);
8777 }
8778 return true;
8779 }
8780 }
8781
8782 false
8783 }
8784
8785 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8786 self.transact(window, cx, |this, window, cx| {
8787 this.select_all(&SelectAll, window, cx);
8788 this.insert("", window, cx);
8789 });
8790 }
8791
8792 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8793 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8794 self.transact(window, cx, |this, window, cx| {
8795 this.select_autoclose_pair(window, cx);
8796 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8797 if !this.linked_edit_ranges.is_empty() {
8798 let selections = this.selections.all::<MultiBufferPoint>(cx);
8799 let snapshot = this.buffer.read(cx).snapshot(cx);
8800
8801 for selection in selections.iter() {
8802 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8803 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8804 if selection_start.buffer_id != selection_end.buffer_id {
8805 continue;
8806 }
8807 if let Some(ranges) =
8808 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8809 {
8810 for (buffer, entries) in ranges {
8811 linked_ranges.entry(buffer).or_default().extend(entries);
8812 }
8813 }
8814 }
8815 }
8816
8817 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8818 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8819 for selection in &mut selections {
8820 if selection.is_empty() {
8821 let old_head = selection.head();
8822 let mut new_head =
8823 movement::left(&display_map, old_head.to_display_point(&display_map))
8824 .to_point(&display_map);
8825 if let Some((buffer, line_buffer_range)) = display_map
8826 .buffer_snapshot
8827 .buffer_line_for_row(MultiBufferRow(old_head.row))
8828 {
8829 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8830 let indent_len = match indent_size.kind {
8831 IndentKind::Space => {
8832 buffer.settings_at(line_buffer_range.start, cx).tab_size
8833 }
8834 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8835 };
8836 if old_head.column <= indent_size.len && old_head.column > 0 {
8837 let indent_len = indent_len.get();
8838 new_head = cmp::min(
8839 new_head,
8840 MultiBufferPoint::new(
8841 old_head.row,
8842 ((old_head.column - 1) / indent_len) * indent_len,
8843 ),
8844 );
8845 }
8846 }
8847
8848 selection.set_head(new_head, SelectionGoal::None);
8849 }
8850 }
8851
8852 this.signature_help_state.set_backspace_pressed(true);
8853 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8854 s.select(selections)
8855 });
8856 this.insert("", window, cx);
8857 let empty_str: Arc<str> = Arc::from("");
8858 for (buffer, edits) in linked_ranges {
8859 let snapshot = buffer.read(cx).snapshot();
8860 use text::ToPoint as TP;
8861
8862 let edits = edits
8863 .into_iter()
8864 .map(|range| {
8865 let end_point = TP::to_point(&range.end, &snapshot);
8866 let mut start_point = TP::to_point(&range.start, &snapshot);
8867
8868 if end_point == start_point {
8869 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8870 .saturating_sub(1);
8871 start_point =
8872 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8873 };
8874
8875 (start_point..end_point, empty_str.clone())
8876 })
8877 .sorted_by_key(|(range, _)| range.start)
8878 .collect::<Vec<_>>();
8879 buffer.update(cx, |this, cx| {
8880 this.edit(edits, None, cx);
8881 })
8882 }
8883 this.refresh_inline_completion(true, false, window, cx);
8884 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8885 });
8886 }
8887
8888 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8889 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8890 self.transact(window, cx, |this, window, cx| {
8891 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8892 s.move_with(|map, selection| {
8893 if selection.is_empty() {
8894 let cursor = movement::right(map, selection.head());
8895 selection.end = cursor;
8896 selection.reversed = true;
8897 selection.goal = SelectionGoal::None;
8898 }
8899 })
8900 });
8901 this.insert("", window, cx);
8902 this.refresh_inline_completion(true, false, window, cx);
8903 });
8904 }
8905
8906 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8907 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8908 if self.move_to_prev_snippet_tabstop(window, cx) {
8909 return;
8910 }
8911 self.outdent(&Outdent, window, cx);
8912 }
8913
8914 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8915 if self.move_to_next_snippet_tabstop(window, cx) {
8916 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8917 return;
8918 }
8919 if self.read_only(cx) {
8920 return;
8921 }
8922 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8923 let mut selections = self.selections.all_adjusted(cx);
8924 let buffer = self.buffer.read(cx);
8925 let snapshot = buffer.snapshot(cx);
8926 let rows_iter = selections.iter().map(|s| s.head().row);
8927 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8928
8929 let has_some_cursor_in_whitespace = selections
8930 .iter()
8931 .filter(|selection| selection.is_empty())
8932 .any(|selection| {
8933 let cursor = selection.head();
8934 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8935 cursor.column < current_indent.len
8936 });
8937
8938 let mut edits = Vec::new();
8939 let mut prev_edited_row = 0;
8940 let mut row_delta = 0;
8941 for selection in &mut selections {
8942 if selection.start.row != prev_edited_row {
8943 row_delta = 0;
8944 }
8945 prev_edited_row = selection.end.row;
8946
8947 // If the selection is non-empty, then increase the indentation of the selected lines.
8948 if !selection.is_empty() {
8949 row_delta =
8950 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8951 continue;
8952 }
8953
8954 let cursor = selection.head();
8955 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8956 if let Some(suggested_indent) =
8957 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8958 {
8959 // Don't do anything if already at suggested indent
8960 // and there is any other cursor which is not
8961 if has_some_cursor_in_whitespace
8962 && cursor.column == current_indent.len
8963 && current_indent.len == suggested_indent.len
8964 {
8965 continue;
8966 }
8967
8968 // Adjust line and move cursor to suggested indent
8969 // if cursor is not at suggested indent
8970 if cursor.column < suggested_indent.len
8971 && cursor.column <= current_indent.len
8972 && current_indent.len <= suggested_indent.len
8973 {
8974 selection.start = Point::new(cursor.row, suggested_indent.len);
8975 selection.end = selection.start;
8976 if row_delta == 0 {
8977 edits.extend(Buffer::edit_for_indent_size_adjustment(
8978 cursor.row,
8979 current_indent,
8980 suggested_indent,
8981 ));
8982 row_delta = suggested_indent.len - current_indent.len;
8983 }
8984 continue;
8985 }
8986
8987 // If current indent is more than suggested indent
8988 // only move cursor to current indent and skip indent
8989 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8990 selection.start = Point::new(cursor.row, current_indent.len);
8991 selection.end = selection.start;
8992 continue;
8993 }
8994 }
8995
8996 // Otherwise, insert a hard or soft tab.
8997 let settings = buffer.language_settings_at(cursor, cx);
8998 let tab_size = if settings.hard_tabs {
8999 IndentSize::tab()
9000 } else {
9001 let tab_size = settings.tab_size.get();
9002 let indent_remainder = snapshot
9003 .text_for_range(Point::new(cursor.row, 0)..cursor)
9004 .flat_map(str::chars)
9005 .fold(row_delta % tab_size, |counter: u32, c| {
9006 if c == '\t' {
9007 0
9008 } else {
9009 (counter + 1) % tab_size
9010 }
9011 });
9012
9013 let chars_to_next_tab_stop = tab_size - indent_remainder;
9014 IndentSize::spaces(chars_to_next_tab_stop)
9015 };
9016 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9017 selection.end = selection.start;
9018 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9019 row_delta += tab_size.len;
9020 }
9021
9022 self.transact(window, cx, |this, window, cx| {
9023 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9024 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9025 s.select(selections)
9026 });
9027 this.refresh_inline_completion(true, false, window, cx);
9028 });
9029 }
9030
9031 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9032 if self.read_only(cx) {
9033 return;
9034 }
9035 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9036 let mut selections = self.selections.all::<Point>(cx);
9037 let mut prev_edited_row = 0;
9038 let mut row_delta = 0;
9039 let mut edits = Vec::new();
9040 let buffer = self.buffer.read(cx);
9041 let snapshot = buffer.snapshot(cx);
9042 for selection in &mut selections {
9043 if selection.start.row != prev_edited_row {
9044 row_delta = 0;
9045 }
9046 prev_edited_row = selection.end.row;
9047
9048 row_delta =
9049 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9050 }
9051
9052 self.transact(window, cx, |this, window, cx| {
9053 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9054 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9055 s.select(selections)
9056 });
9057 });
9058 }
9059
9060 fn indent_selection(
9061 buffer: &MultiBuffer,
9062 snapshot: &MultiBufferSnapshot,
9063 selection: &mut Selection<Point>,
9064 edits: &mut Vec<(Range<Point>, String)>,
9065 delta_for_start_row: u32,
9066 cx: &App,
9067 ) -> u32 {
9068 let settings = buffer.language_settings_at(selection.start, cx);
9069 let tab_size = settings.tab_size.get();
9070 let indent_kind = if settings.hard_tabs {
9071 IndentKind::Tab
9072 } else {
9073 IndentKind::Space
9074 };
9075 let mut start_row = selection.start.row;
9076 let mut end_row = selection.end.row + 1;
9077
9078 // If a selection ends at the beginning of a line, don't indent
9079 // that last line.
9080 if selection.end.column == 0 && selection.end.row > selection.start.row {
9081 end_row -= 1;
9082 }
9083
9084 // Avoid re-indenting a row that has already been indented by a
9085 // previous selection, but still update this selection's column
9086 // to reflect that indentation.
9087 if delta_for_start_row > 0 {
9088 start_row += 1;
9089 selection.start.column += delta_for_start_row;
9090 if selection.end.row == selection.start.row {
9091 selection.end.column += delta_for_start_row;
9092 }
9093 }
9094
9095 let mut delta_for_end_row = 0;
9096 let has_multiple_rows = start_row + 1 != end_row;
9097 for row in start_row..end_row {
9098 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9099 let indent_delta = match (current_indent.kind, indent_kind) {
9100 (IndentKind::Space, IndentKind::Space) => {
9101 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9102 IndentSize::spaces(columns_to_next_tab_stop)
9103 }
9104 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9105 (_, IndentKind::Tab) => IndentSize::tab(),
9106 };
9107
9108 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9109 0
9110 } else {
9111 selection.start.column
9112 };
9113 let row_start = Point::new(row, start);
9114 edits.push((
9115 row_start..row_start,
9116 indent_delta.chars().collect::<String>(),
9117 ));
9118
9119 // Update this selection's endpoints to reflect the indentation.
9120 if row == selection.start.row {
9121 selection.start.column += indent_delta.len;
9122 }
9123 if row == selection.end.row {
9124 selection.end.column += indent_delta.len;
9125 delta_for_end_row = indent_delta.len;
9126 }
9127 }
9128
9129 if selection.start.row == selection.end.row {
9130 delta_for_start_row + delta_for_end_row
9131 } else {
9132 delta_for_end_row
9133 }
9134 }
9135
9136 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9137 if self.read_only(cx) {
9138 return;
9139 }
9140 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9142 let selections = self.selections.all::<Point>(cx);
9143 let mut deletion_ranges = Vec::new();
9144 let mut last_outdent = None;
9145 {
9146 let buffer = self.buffer.read(cx);
9147 let snapshot = buffer.snapshot(cx);
9148 for selection in &selections {
9149 let settings = buffer.language_settings_at(selection.start, cx);
9150 let tab_size = settings.tab_size.get();
9151 let mut rows = selection.spanned_rows(false, &display_map);
9152
9153 // Avoid re-outdenting a row that has already been outdented by a
9154 // previous selection.
9155 if let Some(last_row) = last_outdent {
9156 if last_row == rows.start {
9157 rows.start = rows.start.next_row();
9158 }
9159 }
9160 let has_multiple_rows = rows.len() > 1;
9161 for row in rows.iter_rows() {
9162 let indent_size = snapshot.indent_size_for_line(row);
9163 if indent_size.len > 0 {
9164 let deletion_len = match indent_size.kind {
9165 IndentKind::Space => {
9166 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9167 if columns_to_prev_tab_stop == 0 {
9168 tab_size
9169 } else {
9170 columns_to_prev_tab_stop
9171 }
9172 }
9173 IndentKind::Tab => 1,
9174 };
9175 let start = if has_multiple_rows
9176 || deletion_len > selection.start.column
9177 || indent_size.len < selection.start.column
9178 {
9179 0
9180 } else {
9181 selection.start.column - deletion_len
9182 };
9183 deletion_ranges.push(
9184 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9185 );
9186 last_outdent = Some(row);
9187 }
9188 }
9189 }
9190 }
9191
9192 self.transact(window, cx, |this, window, cx| {
9193 this.buffer.update(cx, |buffer, cx| {
9194 let empty_str: Arc<str> = Arc::default();
9195 buffer.edit(
9196 deletion_ranges
9197 .into_iter()
9198 .map(|range| (range, empty_str.clone())),
9199 None,
9200 cx,
9201 );
9202 });
9203 let selections = this.selections.all::<usize>(cx);
9204 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9205 s.select(selections)
9206 });
9207 });
9208 }
9209
9210 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9211 if self.read_only(cx) {
9212 return;
9213 }
9214 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9215 let selections = self
9216 .selections
9217 .all::<usize>(cx)
9218 .into_iter()
9219 .map(|s| s.range());
9220
9221 self.transact(window, cx, |this, window, cx| {
9222 this.buffer.update(cx, |buffer, cx| {
9223 buffer.autoindent_ranges(selections, cx);
9224 });
9225 let selections = this.selections.all::<usize>(cx);
9226 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9227 s.select(selections)
9228 });
9229 });
9230 }
9231
9232 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9233 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9234 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9235 let selections = self.selections.all::<Point>(cx);
9236
9237 let mut new_cursors = Vec::new();
9238 let mut edit_ranges = Vec::new();
9239 let mut selections = selections.iter().peekable();
9240 while let Some(selection) = selections.next() {
9241 let mut rows = selection.spanned_rows(false, &display_map);
9242 let goal_display_column = selection.head().to_display_point(&display_map).column();
9243
9244 // Accumulate contiguous regions of rows that we want to delete.
9245 while let Some(next_selection) = selections.peek() {
9246 let next_rows = next_selection.spanned_rows(false, &display_map);
9247 if next_rows.start <= rows.end {
9248 rows.end = next_rows.end;
9249 selections.next().unwrap();
9250 } else {
9251 break;
9252 }
9253 }
9254
9255 let buffer = &display_map.buffer_snapshot;
9256 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9257 let edit_end;
9258 let cursor_buffer_row;
9259 if buffer.max_point().row >= rows.end.0 {
9260 // If there's a line after the range, delete the \n from the end of the row range
9261 // and position the cursor on the next line.
9262 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9263 cursor_buffer_row = rows.end;
9264 } else {
9265 // If there isn't a line after the range, delete the \n from the line before the
9266 // start of the row range and position the cursor there.
9267 edit_start = edit_start.saturating_sub(1);
9268 edit_end = buffer.len();
9269 cursor_buffer_row = rows.start.previous_row();
9270 }
9271
9272 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9273 *cursor.column_mut() =
9274 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9275
9276 new_cursors.push((
9277 selection.id,
9278 buffer.anchor_after(cursor.to_point(&display_map)),
9279 ));
9280 edit_ranges.push(edit_start..edit_end);
9281 }
9282
9283 self.transact(window, cx, |this, window, cx| {
9284 let buffer = this.buffer.update(cx, |buffer, cx| {
9285 let empty_str: Arc<str> = Arc::default();
9286 buffer.edit(
9287 edit_ranges
9288 .into_iter()
9289 .map(|range| (range, empty_str.clone())),
9290 None,
9291 cx,
9292 );
9293 buffer.snapshot(cx)
9294 });
9295 let new_selections = new_cursors
9296 .into_iter()
9297 .map(|(id, cursor)| {
9298 let cursor = cursor.to_point(&buffer);
9299 Selection {
9300 id,
9301 start: cursor,
9302 end: cursor,
9303 reversed: false,
9304 goal: SelectionGoal::None,
9305 }
9306 })
9307 .collect();
9308
9309 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9310 s.select(new_selections);
9311 });
9312 });
9313 }
9314
9315 pub fn join_lines_impl(
9316 &mut self,
9317 insert_whitespace: bool,
9318 window: &mut Window,
9319 cx: &mut Context<Self>,
9320 ) {
9321 if self.read_only(cx) {
9322 return;
9323 }
9324 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9325 for selection in self.selections.all::<Point>(cx) {
9326 let start = MultiBufferRow(selection.start.row);
9327 // Treat single line selections as if they include the next line. Otherwise this action
9328 // would do nothing for single line selections individual cursors.
9329 let end = if selection.start.row == selection.end.row {
9330 MultiBufferRow(selection.start.row + 1)
9331 } else {
9332 MultiBufferRow(selection.end.row)
9333 };
9334
9335 if let Some(last_row_range) = row_ranges.last_mut() {
9336 if start <= last_row_range.end {
9337 last_row_range.end = end;
9338 continue;
9339 }
9340 }
9341 row_ranges.push(start..end);
9342 }
9343
9344 let snapshot = self.buffer.read(cx).snapshot(cx);
9345 let mut cursor_positions = Vec::new();
9346 for row_range in &row_ranges {
9347 let anchor = snapshot.anchor_before(Point::new(
9348 row_range.end.previous_row().0,
9349 snapshot.line_len(row_range.end.previous_row()),
9350 ));
9351 cursor_positions.push(anchor..anchor);
9352 }
9353
9354 self.transact(window, cx, |this, window, cx| {
9355 for row_range in row_ranges.into_iter().rev() {
9356 for row in row_range.iter_rows().rev() {
9357 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9358 let next_line_row = row.next_row();
9359 let indent = snapshot.indent_size_for_line(next_line_row);
9360 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9361
9362 let replace =
9363 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9364 " "
9365 } else {
9366 ""
9367 };
9368
9369 this.buffer.update(cx, |buffer, cx| {
9370 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9371 });
9372 }
9373 }
9374
9375 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9376 s.select_anchor_ranges(cursor_positions)
9377 });
9378 });
9379 }
9380
9381 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9382 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9383 self.join_lines_impl(true, window, cx);
9384 }
9385
9386 pub fn sort_lines_case_sensitive(
9387 &mut self,
9388 _: &SortLinesCaseSensitive,
9389 window: &mut Window,
9390 cx: &mut Context<Self>,
9391 ) {
9392 self.manipulate_lines(window, cx, |lines| lines.sort())
9393 }
9394
9395 pub fn sort_lines_case_insensitive(
9396 &mut self,
9397 _: &SortLinesCaseInsensitive,
9398 window: &mut Window,
9399 cx: &mut Context<Self>,
9400 ) {
9401 self.manipulate_lines(window, cx, |lines| {
9402 lines.sort_by_key(|line| line.to_lowercase())
9403 })
9404 }
9405
9406 pub fn unique_lines_case_insensitive(
9407 &mut self,
9408 _: &UniqueLinesCaseInsensitive,
9409 window: &mut Window,
9410 cx: &mut Context<Self>,
9411 ) {
9412 self.manipulate_lines(window, cx, |lines| {
9413 let mut seen = HashSet::default();
9414 lines.retain(|line| seen.insert(line.to_lowercase()));
9415 })
9416 }
9417
9418 pub fn unique_lines_case_sensitive(
9419 &mut self,
9420 _: &UniqueLinesCaseSensitive,
9421 window: &mut Window,
9422 cx: &mut Context<Self>,
9423 ) {
9424 self.manipulate_lines(window, cx, |lines| {
9425 let mut seen = HashSet::default();
9426 lines.retain(|line| seen.insert(*line));
9427 })
9428 }
9429
9430 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9431 let Some(project) = self.project.clone() else {
9432 return;
9433 };
9434 self.reload(project, window, cx)
9435 .detach_and_notify_err(window, cx);
9436 }
9437
9438 pub fn restore_file(
9439 &mut self,
9440 _: &::git::RestoreFile,
9441 window: &mut Window,
9442 cx: &mut Context<Self>,
9443 ) {
9444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9445 let mut buffer_ids = HashSet::default();
9446 let snapshot = self.buffer().read(cx).snapshot(cx);
9447 for selection in self.selections.all::<usize>(cx) {
9448 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9449 }
9450
9451 let buffer = self.buffer().read(cx);
9452 let ranges = buffer_ids
9453 .into_iter()
9454 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9455 .collect::<Vec<_>>();
9456
9457 self.restore_hunks_in_ranges(ranges, window, cx);
9458 }
9459
9460 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9461 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9462 let selections = self
9463 .selections
9464 .all(cx)
9465 .into_iter()
9466 .map(|s| s.range())
9467 .collect();
9468 self.restore_hunks_in_ranges(selections, window, cx);
9469 }
9470
9471 pub fn restore_hunks_in_ranges(
9472 &mut self,
9473 ranges: Vec<Range<Point>>,
9474 window: &mut Window,
9475 cx: &mut Context<Editor>,
9476 ) {
9477 let mut revert_changes = HashMap::default();
9478 let chunk_by = self
9479 .snapshot(window, cx)
9480 .hunks_for_ranges(ranges)
9481 .into_iter()
9482 .chunk_by(|hunk| hunk.buffer_id);
9483 for (buffer_id, hunks) in &chunk_by {
9484 let hunks = hunks.collect::<Vec<_>>();
9485 for hunk in &hunks {
9486 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9487 }
9488 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9489 }
9490 drop(chunk_by);
9491 if !revert_changes.is_empty() {
9492 self.transact(window, cx, |editor, window, cx| {
9493 editor.restore(revert_changes, window, cx);
9494 });
9495 }
9496 }
9497
9498 pub fn open_active_item_in_terminal(
9499 &mut self,
9500 _: &OpenInTerminal,
9501 window: &mut Window,
9502 cx: &mut Context<Self>,
9503 ) {
9504 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9505 let project_path = buffer.read(cx).project_path(cx)?;
9506 let project = self.project.as_ref()?.read(cx);
9507 let entry = project.entry_for_path(&project_path, cx)?;
9508 let parent = match &entry.canonical_path {
9509 Some(canonical_path) => canonical_path.to_path_buf(),
9510 None => project.absolute_path(&project_path, cx)?,
9511 }
9512 .parent()?
9513 .to_path_buf();
9514 Some(parent)
9515 }) {
9516 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9517 }
9518 }
9519
9520 fn set_breakpoint_context_menu(
9521 &mut self,
9522 display_row: DisplayRow,
9523 position: Option<Anchor>,
9524 clicked_point: gpui::Point<Pixels>,
9525 window: &mut Window,
9526 cx: &mut Context<Self>,
9527 ) {
9528 if !cx.has_flag::<DebuggerFeatureFlag>() {
9529 return;
9530 }
9531 let source = self
9532 .buffer
9533 .read(cx)
9534 .snapshot(cx)
9535 .anchor_before(Point::new(display_row.0, 0u32));
9536
9537 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9538
9539 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9540 self,
9541 source,
9542 clicked_point,
9543 context_menu,
9544 window,
9545 cx,
9546 );
9547 }
9548
9549 fn add_edit_breakpoint_block(
9550 &mut self,
9551 anchor: Anchor,
9552 breakpoint: &Breakpoint,
9553 edit_action: BreakpointPromptEditAction,
9554 window: &mut Window,
9555 cx: &mut Context<Self>,
9556 ) {
9557 let weak_editor = cx.weak_entity();
9558 let bp_prompt = cx.new(|cx| {
9559 BreakpointPromptEditor::new(
9560 weak_editor,
9561 anchor,
9562 breakpoint.clone(),
9563 edit_action,
9564 window,
9565 cx,
9566 )
9567 });
9568
9569 let height = bp_prompt.update(cx, |this, cx| {
9570 this.prompt
9571 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9572 });
9573 let cloned_prompt = bp_prompt.clone();
9574 let blocks = vec![BlockProperties {
9575 style: BlockStyle::Sticky,
9576 placement: BlockPlacement::Above(anchor),
9577 height: Some(height),
9578 render: Arc::new(move |cx| {
9579 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9580 cloned_prompt.clone().into_any_element()
9581 }),
9582 priority: 0,
9583 render_in_minimap: true,
9584 }];
9585
9586 let focus_handle = bp_prompt.focus_handle(cx);
9587 window.focus(&focus_handle);
9588
9589 let block_ids = self.insert_blocks(blocks, None, cx);
9590 bp_prompt.update(cx, |prompt, _| {
9591 prompt.add_block_ids(block_ids);
9592 });
9593 }
9594
9595 pub(crate) fn breakpoint_at_row(
9596 &self,
9597 row: u32,
9598 window: &mut Window,
9599 cx: &mut Context<Self>,
9600 ) -> Option<(Anchor, Breakpoint)> {
9601 let snapshot = self.snapshot(window, cx);
9602 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9603
9604 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9605 }
9606
9607 pub(crate) fn breakpoint_at_anchor(
9608 &self,
9609 breakpoint_position: Anchor,
9610 snapshot: &EditorSnapshot,
9611 cx: &mut Context<Self>,
9612 ) -> Option<(Anchor, Breakpoint)> {
9613 let project = self.project.clone()?;
9614
9615 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9616 snapshot
9617 .buffer_snapshot
9618 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9619 })?;
9620
9621 let enclosing_excerpt = breakpoint_position.excerpt_id;
9622 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9623 let buffer_snapshot = buffer.read(cx).snapshot();
9624
9625 let row = buffer_snapshot
9626 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9627 .row;
9628
9629 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9630 let anchor_end = snapshot
9631 .buffer_snapshot
9632 .anchor_after(Point::new(row, line_len));
9633
9634 let bp = self
9635 .breakpoint_store
9636 .as_ref()?
9637 .read_with(cx, |breakpoint_store, cx| {
9638 breakpoint_store
9639 .breakpoints(
9640 &buffer,
9641 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9642 &buffer_snapshot,
9643 cx,
9644 )
9645 .next()
9646 .and_then(|(bp, _)| {
9647 let breakpoint_row = buffer_snapshot
9648 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9649 .row;
9650
9651 if breakpoint_row == row {
9652 snapshot
9653 .buffer_snapshot
9654 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9655 .map(|position| (position, bp.bp.clone()))
9656 } else {
9657 None
9658 }
9659 })
9660 });
9661 bp
9662 }
9663
9664 pub fn edit_log_breakpoint(
9665 &mut self,
9666 _: &EditLogBreakpoint,
9667 window: &mut Window,
9668 cx: &mut Context<Self>,
9669 ) {
9670 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9671 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9672 message: None,
9673 state: BreakpointState::Enabled,
9674 condition: None,
9675 hit_condition: None,
9676 });
9677
9678 self.add_edit_breakpoint_block(
9679 anchor,
9680 &breakpoint,
9681 BreakpointPromptEditAction::Log,
9682 window,
9683 cx,
9684 );
9685 }
9686 }
9687
9688 fn breakpoints_at_cursors(
9689 &self,
9690 window: &mut Window,
9691 cx: &mut Context<Self>,
9692 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9693 let snapshot = self.snapshot(window, cx);
9694 let cursors = self
9695 .selections
9696 .disjoint_anchors()
9697 .into_iter()
9698 .map(|selection| {
9699 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9700
9701 let breakpoint_position = self
9702 .breakpoint_at_row(cursor_position.row, window, cx)
9703 .map(|bp| bp.0)
9704 .unwrap_or_else(|| {
9705 snapshot
9706 .display_snapshot
9707 .buffer_snapshot
9708 .anchor_after(Point::new(cursor_position.row, 0))
9709 });
9710
9711 let breakpoint = self
9712 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9713 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9714
9715 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9716 })
9717 // 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.
9718 .collect::<HashMap<Anchor, _>>();
9719
9720 cursors.into_iter().collect()
9721 }
9722
9723 pub fn enable_breakpoint(
9724 &mut self,
9725 _: &crate::actions::EnableBreakpoint,
9726 window: &mut Window,
9727 cx: &mut Context<Self>,
9728 ) {
9729 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9730 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9731 continue;
9732 };
9733 self.edit_breakpoint_at_anchor(
9734 anchor,
9735 breakpoint,
9736 BreakpointEditAction::InvertState,
9737 cx,
9738 );
9739 }
9740 }
9741
9742 pub fn disable_breakpoint(
9743 &mut self,
9744 _: &crate::actions::DisableBreakpoint,
9745 window: &mut Window,
9746 cx: &mut Context<Self>,
9747 ) {
9748 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9749 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9750 continue;
9751 };
9752 self.edit_breakpoint_at_anchor(
9753 anchor,
9754 breakpoint,
9755 BreakpointEditAction::InvertState,
9756 cx,
9757 );
9758 }
9759 }
9760
9761 pub fn toggle_breakpoint(
9762 &mut self,
9763 _: &crate::actions::ToggleBreakpoint,
9764 window: &mut Window,
9765 cx: &mut Context<Self>,
9766 ) {
9767 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9768 if let Some(breakpoint) = breakpoint {
9769 self.edit_breakpoint_at_anchor(
9770 anchor,
9771 breakpoint,
9772 BreakpointEditAction::Toggle,
9773 cx,
9774 );
9775 } else {
9776 self.edit_breakpoint_at_anchor(
9777 anchor,
9778 Breakpoint::new_standard(),
9779 BreakpointEditAction::Toggle,
9780 cx,
9781 );
9782 }
9783 }
9784 }
9785
9786 pub fn edit_breakpoint_at_anchor(
9787 &mut self,
9788 breakpoint_position: Anchor,
9789 breakpoint: Breakpoint,
9790 edit_action: BreakpointEditAction,
9791 cx: &mut Context<Self>,
9792 ) {
9793 let Some(breakpoint_store) = &self.breakpoint_store else {
9794 return;
9795 };
9796
9797 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9798 if breakpoint_position == Anchor::min() {
9799 self.buffer()
9800 .read(cx)
9801 .excerpt_buffer_ids()
9802 .into_iter()
9803 .next()
9804 } else {
9805 None
9806 }
9807 }) else {
9808 return;
9809 };
9810
9811 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9812 return;
9813 };
9814
9815 breakpoint_store.update(cx, |breakpoint_store, cx| {
9816 breakpoint_store.toggle_breakpoint(
9817 buffer,
9818 BreakpointWithPosition {
9819 position: breakpoint_position.text_anchor,
9820 bp: breakpoint,
9821 },
9822 edit_action,
9823 cx,
9824 );
9825 });
9826
9827 cx.notify();
9828 }
9829
9830 #[cfg(any(test, feature = "test-support"))]
9831 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9832 self.breakpoint_store.clone()
9833 }
9834
9835 pub fn prepare_restore_change(
9836 &self,
9837 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9838 hunk: &MultiBufferDiffHunk,
9839 cx: &mut App,
9840 ) -> Option<()> {
9841 if hunk.is_created_file() {
9842 return None;
9843 }
9844 let buffer = self.buffer.read(cx);
9845 let diff = buffer.diff_for(hunk.buffer_id)?;
9846 let buffer = buffer.buffer(hunk.buffer_id)?;
9847 let buffer = buffer.read(cx);
9848 let original_text = diff
9849 .read(cx)
9850 .base_text()
9851 .as_rope()
9852 .slice(hunk.diff_base_byte_range.clone());
9853 let buffer_snapshot = buffer.snapshot();
9854 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9855 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9856 probe
9857 .0
9858 .start
9859 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9860 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9861 }) {
9862 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9863 Some(())
9864 } else {
9865 None
9866 }
9867 }
9868
9869 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9870 self.manipulate_lines(window, cx, |lines| lines.reverse())
9871 }
9872
9873 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9874 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9875 }
9876
9877 fn manipulate_lines<Fn>(
9878 &mut self,
9879 window: &mut Window,
9880 cx: &mut Context<Self>,
9881 mut callback: Fn,
9882 ) where
9883 Fn: FnMut(&mut Vec<&str>),
9884 {
9885 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9886
9887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9888 let buffer = self.buffer.read(cx).snapshot(cx);
9889
9890 let mut edits = Vec::new();
9891
9892 let selections = self.selections.all::<Point>(cx);
9893 let mut selections = selections.iter().peekable();
9894 let mut contiguous_row_selections = Vec::new();
9895 let mut new_selections = Vec::new();
9896 let mut added_lines = 0;
9897 let mut removed_lines = 0;
9898
9899 while let Some(selection) = selections.next() {
9900 let (start_row, end_row) = consume_contiguous_rows(
9901 &mut contiguous_row_selections,
9902 selection,
9903 &display_map,
9904 &mut selections,
9905 );
9906
9907 let start_point = Point::new(start_row.0, 0);
9908 let end_point = Point::new(
9909 end_row.previous_row().0,
9910 buffer.line_len(end_row.previous_row()),
9911 );
9912 let text = buffer
9913 .text_for_range(start_point..end_point)
9914 .collect::<String>();
9915
9916 let mut lines = text.split('\n').collect_vec();
9917
9918 let lines_before = lines.len();
9919 callback(&mut lines);
9920 let lines_after = lines.len();
9921
9922 edits.push((start_point..end_point, lines.join("\n")));
9923
9924 // Selections must change based on added and removed line count
9925 let start_row =
9926 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9927 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9928 new_selections.push(Selection {
9929 id: selection.id,
9930 start: start_row,
9931 end: end_row,
9932 goal: SelectionGoal::None,
9933 reversed: selection.reversed,
9934 });
9935
9936 if lines_after > lines_before {
9937 added_lines += lines_after - lines_before;
9938 } else if lines_before > lines_after {
9939 removed_lines += lines_before - lines_after;
9940 }
9941 }
9942
9943 self.transact(window, cx, |this, window, cx| {
9944 let buffer = this.buffer.update(cx, |buffer, cx| {
9945 buffer.edit(edits, None, cx);
9946 buffer.snapshot(cx)
9947 });
9948
9949 // Recalculate offsets on newly edited buffer
9950 let new_selections = new_selections
9951 .iter()
9952 .map(|s| {
9953 let start_point = Point::new(s.start.0, 0);
9954 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9955 Selection {
9956 id: s.id,
9957 start: buffer.point_to_offset(start_point),
9958 end: buffer.point_to_offset(end_point),
9959 goal: s.goal,
9960 reversed: s.reversed,
9961 }
9962 })
9963 .collect();
9964
9965 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9966 s.select(new_selections);
9967 });
9968
9969 this.request_autoscroll(Autoscroll::fit(), cx);
9970 });
9971 }
9972
9973 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9974 self.manipulate_text(window, cx, |text| {
9975 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9976 if has_upper_case_characters {
9977 text.to_lowercase()
9978 } else {
9979 text.to_uppercase()
9980 }
9981 })
9982 }
9983
9984 pub fn convert_to_upper_case(
9985 &mut self,
9986 _: &ConvertToUpperCase,
9987 window: &mut Window,
9988 cx: &mut Context<Self>,
9989 ) {
9990 self.manipulate_text(window, cx, |text| text.to_uppercase())
9991 }
9992
9993 pub fn convert_to_lower_case(
9994 &mut self,
9995 _: &ConvertToLowerCase,
9996 window: &mut Window,
9997 cx: &mut Context<Self>,
9998 ) {
9999 self.manipulate_text(window, cx, |text| text.to_lowercase())
10000 }
10001
10002 pub fn convert_to_title_case(
10003 &mut self,
10004 _: &ConvertToTitleCase,
10005 window: &mut Window,
10006 cx: &mut Context<Self>,
10007 ) {
10008 self.manipulate_text(window, cx, |text| {
10009 text.split('\n')
10010 .map(|line| line.to_case(Case::Title))
10011 .join("\n")
10012 })
10013 }
10014
10015 pub fn convert_to_snake_case(
10016 &mut self,
10017 _: &ConvertToSnakeCase,
10018 window: &mut Window,
10019 cx: &mut Context<Self>,
10020 ) {
10021 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10022 }
10023
10024 pub fn convert_to_kebab_case(
10025 &mut self,
10026 _: &ConvertToKebabCase,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) {
10030 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10031 }
10032
10033 pub fn convert_to_upper_camel_case(
10034 &mut self,
10035 _: &ConvertToUpperCamelCase,
10036 window: &mut Window,
10037 cx: &mut Context<Self>,
10038 ) {
10039 self.manipulate_text(window, cx, |text| {
10040 text.split('\n')
10041 .map(|line| line.to_case(Case::UpperCamel))
10042 .join("\n")
10043 })
10044 }
10045
10046 pub fn convert_to_lower_camel_case(
10047 &mut self,
10048 _: &ConvertToLowerCamelCase,
10049 window: &mut Window,
10050 cx: &mut Context<Self>,
10051 ) {
10052 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10053 }
10054
10055 pub fn convert_to_opposite_case(
10056 &mut self,
10057 _: &ConvertToOppositeCase,
10058 window: &mut Window,
10059 cx: &mut Context<Self>,
10060 ) {
10061 self.manipulate_text(window, cx, |text| {
10062 text.chars()
10063 .fold(String::with_capacity(text.len()), |mut t, c| {
10064 if c.is_uppercase() {
10065 t.extend(c.to_lowercase());
10066 } else {
10067 t.extend(c.to_uppercase());
10068 }
10069 t
10070 })
10071 })
10072 }
10073
10074 pub fn convert_to_rot13(
10075 &mut self,
10076 _: &ConvertToRot13,
10077 window: &mut Window,
10078 cx: &mut Context<Self>,
10079 ) {
10080 self.manipulate_text(window, cx, |text| {
10081 text.chars()
10082 .map(|c| match c {
10083 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10084 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10085 _ => c,
10086 })
10087 .collect()
10088 })
10089 }
10090
10091 pub fn convert_to_rot47(
10092 &mut self,
10093 _: &ConvertToRot47,
10094 window: &mut Window,
10095 cx: &mut Context<Self>,
10096 ) {
10097 self.manipulate_text(window, cx, |text| {
10098 text.chars()
10099 .map(|c| {
10100 let code_point = c as u32;
10101 if code_point >= 33 && code_point <= 126 {
10102 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10103 }
10104 c
10105 })
10106 .collect()
10107 })
10108 }
10109
10110 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10111 where
10112 Fn: FnMut(&str) -> String,
10113 {
10114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10115 let buffer = self.buffer.read(cx).snapshot(cx);
10116
10117 let mut new_selections = Vec::new();
10118 let mut edits = Vec::new();
10119 let mut selection_adjustment = 0i32;
10120
10121 for selection in self.selections.all::<usize>(cx) {
10122 let selection_is_empty = selection.is_empty();
10123
10124 let (start, end) = if selection_is_empty {
10125 let word_range = movement::surrounding_word(
10126 &display_map,
10127 selection.start.to_display_point(&display_map),
10128 );
10129 let start = word_range.start.to_offset(&display_map, Bias::Left);
10130 let end = word_range.end.to_offset(&display_map, Bias::Left);
10131 (start, end)
10132 } else {
10133 (selection.start, selection.end)
10134 };
10135
10136 let text = buffer.text_for_range(start..end).collect::<String>();
10137 let old_length = text.len() as i32;
10138 let text = callback(&text);
10139
10140 new_selections.push(Selection {
10141 start: (start as i32 - selection_adjustment) as usize,
10142 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10143 goal: SelectionGoal::None,
10144 ..selection
10145 });
10146
10147 selection_adjustment += old_length - text.len() as i32;
10148
10149 edits.push((start..end, text));
10150 }
10151
10152 self.transact(window, cx, |this, window, cx| {
10153 this.buffer.update(cx, |buffer, cx| {
10154 buffer.edit(edits, None, cx);
10155 });
10156
10157 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10158 s.select(new_selections);
10159 });
10160
10161 this.request_autoscroll(Autoscroll::fit(), cx);
10162 });
10163 }
10164
10165 pub fn duplicate(
10166 &mut self,
10167 upwards: bool,
10168 whole_lines: bool,
10169 window: &mut Window,
10170 cx: &mut Context<Self>,
10171 ) {
10172 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10173
10174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10175 let buffer = &display_map.buffer_snapshot;
10176 let selections = self.selections.all::<Point>(cx);
10177
10178 let mut edits = Vec::new();
10179 let mut selections_iter = selections.iter().peekable();
10180 while let Some(selection) = selections_iter.next() {
10181 let mut rows = selection.spanned_rows(false, &display_map);
10182 // duplicate line-wise
10183 if whole_lines || selection.start == selection.end {
10184 // Avoid duplicating the same lines twice.
10185 while let Some(next_selection) = selections_iter.peek() {
10186 let next_rows = next_selection.spanned_rows(false, &display_map);
10187 if next_rows.start < rows.end {
10188 rows.end = next_rows.end;
10189 selections_iter.next().unwrap();
10190 } else {
10191 break;
10192 }
10193 }
10194
10195 // Copy the text from the selected row region and splice it either at the start
10196 // or end of the region.
10197 let start = Point::new(rows.start.0, 0);
10198 let end = Point::new(
10199 rows.end.previous_row().0,
10200 buffer.line_len(rows.end.previous_row()),
10201 );
10202 let text = buffer
10203 .text_for_range(start..end)
10204 .chain(Some("\n"))
10205 .collect::<String>();
10206 let insert_location = if upwards {
10207 Point::new(rows.end.0, 0)
10208 } else {
10209 start
10210 };
10211 edits.push((insert_location..insert_location, text));
10212 } else {
10213 // duplicate character-wise
10214 let start = selection.start;
10215 let end = selection.end;
10216 let text = buffer.text_for_range(start..end).collect::<String>();
10217 edits.push((selection.end..selection.end, text));
10218 }
10219 }
10220
10221 self.transact(window, cx, |this, _, cx| {
10222 this.buffer.update(cx, |buffer, cx| {
10223 buffer.edit(edits, None, cx);
10224 });
10225
10226 this.request_autoscroll(Autoscroll::fit(), cx);
10227 });
10228 }
10229
10230 pub fn duplicate_line_up(
10231 &mut self,
10232 _: &DuplicateLineUp,
10233 window: &mut Window,
10234 cx: &mut Context<Self>,
10235 ) {
10236 self.duplicate(true, true, window, cx);
10237 }
10238
10239 pub fn duplicate_line_down(
10240 &mut self,
10241 _: &DuplicateLineDown,
10242 window: &mut Window,
10243 cx: &mut Context<Self>,
10244 ) {
10245 self.duplicate(false, true, window, cx);
10246 }
10247
10248 pub fn duplicate_selection(
10249 &mut self,
10250 _: &DuplicateSelection,
10251 window: &mut Window,
10252 cx: &mut Context<Self>,
10253 ) {
10254 self.duplicate(false, false, window, cx);
10255 }
10256
10257 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10258 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10259
10260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10261 let buffer = self.buffer.read(cx).snapshot(cx);
10262
10263 let mut edits = Vec::new();
10264 let mut unfold_ranges = Vec::new();
10265 let mut refold_creases = Vec::new();
10266
10267 let selections = self.selections.all::<Point>(cx);
10268 let mut selections = selections.iter().peekable();
10269 let mut contiguous_row_selections = Vec::new();
10270 let mut new_selections = Vec::new();
10271
10272 while let Some(selection) = selections.next() {
10273 // Find all the selections that span a contiguous row range
10274 let (start_row, end_row) = consume_contiguous_rows(
10275 &mut contiguous_row_selections,
10276 selection,
10277 &display_map,
10278 &mut selections,
10279 );
10280
10281 // Move the text spanned by the row range to be before the line preceding the row range
10282 if start_row.0 > 0 {
10283 let range_to_move = Point::new(
10284 start_row.previous_row().0,
10285 buffer.line_len(start_row.previous_row()),
10286 )
10287 ..Point::new(
10288 end_row.previous_row().0,
10289 buffer.line_len(end_row.previous_row()),
10290 );
10291 let insertion_point = display_map
10292 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10293 .0;
10294
10295 // Don't move lines across excerpts
10296 if buffer
10297 .excerpt_containing(insertion_point..range_to_move.end)
10298 .is_some()
10299 {
10300 let text = buffer
10301 .text_for_range(range_to_move.clone())
10302 .flat_map(|s| s.chars())
10303 .skip(1)
10304 .chain(['\n'])
10305 .collect::<String>();
10306
10307 edits.push((
10308 buffer.anchor_after(range_to_move.start)
10309 ..buffer.anchor_before(range_to_move.end),
10310 String::new(),
10311 ));
10312 let insertion_anchor = buffer.anchor_after(insertion_point);
10313 edits.push((insertion_anchor..insertion_anchor, text));
10314
10315 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10316
10317 // Move selections up
10318 new_selections.extend(contiguous_row_selections.drain(..).map(
10319 |mut selection| {
10320 selection.start.row -= row_delta;
10321 selection.end.row -= row_delta;
10322 selection
10323 },
10324 ));
10325
10326 // Move folds up
10327 unfold_ranges.push(range_to_move.clone());
10328 for fold in display_map.folds_in_range(
10329 buffer.anchor_before(range_to_move.start)
10330 ..buffer.anchor_after(range_to_move.end),
10331 ) {
10332 let mut start = fold.range.start.to_point(&buffer);
10333 let mut end = fold.range.end.to_point(&buffer);
10334 start.row -= row_delta;
10335 end.row -= row_delta;
10336 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10337 }
10338 }
10339 }
10340
10341 // If we didn't move line(s), preserve the existing selections
10342 new_selections.append(&mut contiguous_row_selections);
10343 }
10344
10345 self.transact(window, cx, |this, window, cx| {
10346 this.unfold_ranges(&unfold_ranges, true, true, cx);
10347 this.buffer.update(cx, |buffer, cx| {
10348 for (range, text) in edits {
10349 buffer.edit([(range, text)], None, cx);
10350 }
10351 });
10352 this.fold_creases(refold_creases, true, window, cx);
10353 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10354 s.select(new_selections);
10355 })
10356 });
10357 }
10358
10359 pub fn move_line_down(
10360 &mut self,
10361 _: &MoveLineDown,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 ) {
10365 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10366
10367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10368 let buffer = self.buffer.read(cx).snapshot(cx);
10369
10370 let mut edits = Vec::new();
10371 let mut unfold_ranges = Vec::new();
10372 let mut refold_creases = Vec::new();
10373
10374 let selections = self.selections.all::<Point>(cx);
10375 let mut selections = selections.iter().peekable();
10376 let mut contiguous_row_selections = Vec::new();
10377 let mut new_selections = Vec::new();
10378
10379 while let Some(selection) = selections.next() {
10380 // Find all the selections that span a contiguous row range
10381 let (start_row, end_row) = consume_contiguous_rows(
10382 &mut contiguous_row_selections,
10383 selection,
10384 &display_map,
10385 &mut selections,
10386 );
10387
10388 // Move the text spanned by the row range to be after the last line of the row range
10389 if end_row.0 <= buffer.max_point().row {
10390 let range_to_move =
10391 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10392 let insertion_point = display_map
10393 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10394 .0;
10395
10396 // Don't move lines across excerpt boundaries
10397 if buffer
10398 .excerpt_containing(range_to_move.start..insertion_point)
10399 .is_some()
10400 {
10401 let mut text = String::from("\n");
10402 text.extend(buffer.text_for_range(range_to_move.clone()));
10403 text.pop(); // Drop trailing newline
10404 edits.push((
10405 buffer.anchor_after(range_to_move.start)
10406 ..buffer.anchor_before(range_to_move.end),
10407 String::new(),
10408 ));
10409 let insertion_anchor = buffer.anchor_after(insertion_point);
10410 edits.push((insertion_anchor..insertion_anchor, text));
10411
10412 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10413
10414 // Move selections down
10415 new_selections.extend(contiguous_row_selections.drain(..).map(
10416 |mut selection| {
10417 selection.start.row += row_delta;
10418 selection.end.row += row_delta;
10419 selection
10420 },
10421 ));
10422
10423 // Move folds down
10424 unfold_ranges.push(range_to_move.clone());
10425 for fold in display_map.folds_in_range(
10426 buffer.anchor_before(range_to_move.start)
10427 ..buffer.anchor_after(range_to_move.end),
10428 ) {
10429 let mut start = fold.range.start.to_point(&buffer);
10430 let mut end = fold.range.end.to_point(&buffer);
10431 start.row += row_delta;
10432 end.row += row_delta;
10433 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10434 }
10435 }
10436 }
10437
10438 // If we didn't move line(s), preserve the existing selections
10439 new_selections.append(&mut contiguous_row_selections);
10440 }
10441
10442 self.transact(window, cx, |this, window, cx| {
10443 this.unfold_ranges(&unfold_ranges, true, true, cx);
10444 this.buffer.update(cx, |buffer, cx| {
10445 for (range, text) in edits {
10446 buffer.edit([(range, text)], None, cx);
10447 }
10448 });
10449 this.fold_creases(refold_creases, true, window, cx);
10450 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10451 s.select(new_selections)
10452 });
10453 });
10454 }
10455
10456 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10457 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10458 let text_layout_details = &self.text_layout_details(window);
10459 self.transact(window, cx, |this, window, cx| {
10460 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10461 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10462 s.move_with(|display_map, selection| {
10463 if !selection.is_empty() {
10464 return;
10465 }
10466
10467 let mut head = selection.head();
10468 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10469 if head.column() == display_map.line_len(head.row()) {
10470 transpose_offset = display_map
10471 .buffer_snapshot
10472 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10473 }
10474
10475 if transpose_offset == 0 {
10476 return;
10477 }
10478
10479 *head.column_mut() += 1;
10480 head = display_map.clip_point(head, Bias::Right);
10481 let goal = SelectionGoal::HorizontalPosition(
10482 display_map
10483 .x_for_display_point(head, text_layout_details)
10484 .into(),
10485 );
10486 selection.collapse_to(head, goal);
10487
10488 let transpose_start = display_map
10489 .buffer_snapshot
10490 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10491 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10492 let transpose_end = display_map
10493 .buffer_snapshot
10494 .clip_offset(transpose_offset + 1, Bias::Right);
10495 if let Some(ch) =
10496 display_map.buffer_snapshot.chars_at(transpose_start).next()
10497 {
10498 edits.push((transpose_start..transpose_offset, String::new()));
10499 edits.push((transpose_end..transpose_end, ch.to_string()));
10500 }
10501 }
10502 });
10503 edits
10504 });
10505 this.buffer
10506 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10507 let selections = this.selections.all::<usize>(cx);
10508 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10509 s.select(selections);
10510 });
10511 });
10512 }
10513
10514 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10515 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10516 self.rewrap_impl(RewrapOptions::default(), cx)
10517 }
10518
10519 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10520 let buffer = self.buffer.read(cx).snapshot(cx);
10521 let selections = self.selections.all::<Point>(cx);
10522 let mut selections = selections.iter().peekable();
10523
10524 let mut edits = Vec::new();
10525 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10526
10527 while let Some(selection) = selections.next() {
10528 let mut start_row = selection.start.row;
10529 let mut end_row = selection.end.row;
10530
10531 // Skip selections that overlap with a range that has already been rewrapped.
10532 let selection_range = start_row..end_row;
10533 if rewrapped_row_ranges
10534 .iter()
10535 .any(|range| range.overlaps(&selection_range))
10536 {
10537 continue;
10538 }
10539
10540 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10541
10542 // Since not all lines in the selection may be at the same indent
10543 // level, choose the indent size that is the most common between all
10544 // of the lines.
10545 //
10546 // If there is a tie, we use the deepest indent.
10547 let (indent_size, indent_end) = {
10548 let mut indent_size_occurrences = HashMap::default();
10549 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10550
10551 for row in start_row..=end_row {
10552 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10553 rows_by_indent_size.entry(indent).or_default().push(row);
10554 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10555 }
10556
10557 let indent_size = indent_size_occurrences
10558 .into_iter()
10559 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10560 .map(|(indent, _)| indent)
10561 .unwrap_or_default();
10562 let row = rows_by_indent_size[&indent_size][0];
10563 let indent_end = Point::new(row, indent_size.len);
10564
10565 (indent_size, indent_end)
10566 };
10567
10568 let mut line_prefix = indent_size.chars().collect::<String>();
10569
10570 let mut inside_comment = false;
10571 if let Some(comment_prefix) =
10572 buffer
10573 .language_scope_at(selection.head())
10574 .and_then(|language| {
10575 language
10576 .line_comment_prefixes()
10577 .iter()
10578 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10579 .cloned()
10580 })
10581 {
10582 line_prefix.push_str(&comment_prefix);
10583 inside_comment = true;
10584 }
10585
10586 let language_settings = buffer.language_settings_at(selection.head(), cx);
10587 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10588 RewrapBehavior::InComments => inside_comment,
10589 RewrapBehavior::InSelections => !selection.is_empty(),
10590 RewrapBehavior::Anywhere => true,
10591 };
10592
10593 let should_rewrap = options.override_language_settings
10594 || allow_rewrap_based_on_language
10595 || self.hard_wrap.is_some();
10596 if !should_rewrap {
10597 continue;
10598 }
10599
10600 if selection.is_empty() {
10601 'expand_upwards: while start_row > 0 {
10602 let prev_row = start_row - 1;
10603 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10604 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10605 {
10606 start_row = prev_row;
10607 } else {
10608 break 'expand_upwards;
10609 }
10610 }
10611
10612 'expand_downwards: while end_row < buffer.max_point().row {
10613 let next_row = end_row + 1;
10614 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10615 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10616 {
10617 end_row = next_row;
10618 } else {
10619 break 'expand_downwards;
10620 }
10621 }
10622 }
10623
10624 let start = Point::new(start_row, 0);
10625 let start_offset = start.to_offset(&buffer);
10626 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10627 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10628 let Some(lines_without_prefixes) = selection_text
10629 .lines()
10630 .map(|line| {
10631 line.strip_prefix(&line_prefix)
10632 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10633 .with_context(|| {
10634 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10635 })
10636 })
10637 .collect::<Result<Vec<_>, _>>()
10638 .log_err()
10639 else {
10640 continue;
10641 };
10642
10643 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10644 buffer
10645 .language_settings_at(Point::new(start_row, 0), cx)
10646 .preferred_line_length as usize
10647 });
10648 let wrapped_text = wrap_with_prefix(
10649 line_prefix,
10650 lines_without_prefixes.join("\n"),
10651 wrap_column,
10652 tab_size,
10653 options.preserve_existing_whitespace,
10654 );
10655
10656 // TODO: should always use char-based diff while still supporting cursor behavior that
10657 // matches vim.
10658 let mut diff_options = DiffOptions::default();
10659 if options.override_language_settings {
10660 diff_options.max_word_diff_len = 0;
10661 diff_options.max_word_diff_line_count = 0;
10662 } else {
10663 diff_options.max_word_diff_len = usize::MAX;
10664 diff_options.max_word_diff_line_count = usize::MAX;
10665 }
10666
10667 for (old_range, new_text) in
10668 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10669 {
10670 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10671 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10672 edits.push((edit_start..edit_end, new_text));
10673 }
10674
10675 rewrapped_row_ranges.push(start_row..=end_row);
10676 }
10677
10678 self.buffer
10679 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10680 }
10681
10682 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10683 let mut text = String::new();
10684 let buffer = self.buffer.read(cx).snapshot(cx);
10685 let mut selections = self.selections.all::<Point>(cx);
10686 let mut clipboard_selections = Vec::with_capacity(selections.len());
10687 {
10688 let max_point = buffer.max_point();
10689 let mut is_first = true;
10690 for selection in &mut selections {
10691 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10692 if is_entire_line {
10693 selection.start = Point::new(selection.start.row, 0);
10694 if !selection.is_empty() && selection.end.column == 0 {
10695 selection.end = cmp::min(max_point, selection.end);
10696 } else {
10697 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10698 }
10699 selection.goal = SelectionGoal::None;
10700 }
10701 if is_first {
10702 is_first = false;
10703 } else {
10704 text += "\n";
10705 }
10706 let mut len = 0;
10707 for chunk in buffer.text_for_range(selection.start..selection.end) {
10708 text.push_str(chunk);
10709 len += chunk.len();
10710 }
10711 clipboard_selections.push(ClipboardSelection {
10712 len,
10713 is_entire_line,
10714 first_line_indent: buffer
10715 .indent_size_for_line(MultiBufferRow(selection.start.row))
10716 .len,
10717 });
10718 }
10719 }
10720
10721 self.transact(window, cx, |this, window, cx| {
10722 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10723 s.select(selections);
10724 });
10725 this.insert("", window, cx);
10726 });
10727 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10728 }
10729
10730 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10732 let item = self.cut_common(window, cx);
10733 cx.write_to_clipboard(item);
10734 }
10735
10736 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10737 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10738 self.change_selections(None, window, cx, |s| {
10739 s.move_with(|snapshot, sel| {
10740 if sel.is_empty() {
10741 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10742 }
10743 });
10744 });
10745 let item = self.cut_common(window, cx);
10746 cx.set_global(KillRing(item))
10747 }
10748
10749 pub fn kill_ring_yank(
10750 &mut self,
10751 _: &KillRingYank,
10752 window: &mut Window,
10753 cx: &mut Context<Self>,
10754 ) {
10755 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10756 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10757 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10758 (kill_ring.text().to_string(), kill_ring.metadata_json())
10759 } else {
10760 return;
10761 }
10762 } else {
10763 return;
10764 };
10765 self.do_paste(&text, metadata, false, window, cx);
10766 }
10767
10768 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10769 self.do_copy(true, cx);
10770 }
10771
10772 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10773 self.do_copy(false, cx);
10774 }
10775
10776 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10777 let selections = self.selections.all::<Point>(cx);
10778 let buffer = self.buffer.read(cx).read(cx);
10779 let mut text = String::new();
10780
10781 let mut clipboard_selections = Vec::with_capacity(selections.len());
10782 {
10783 let max_point = buffer.max_point();
10784 let mut is_first = true;
10785 for selection in &selections {
10786 let mut start = selection.start;
10787 let mut end = selection.end;
10788 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10789 if is_entire_line {
10790 start = Point::new(start.row, 0);
10791 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10792 }
10793
10794 let mut trimmed_selections = Vec::new();
10795 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10796 let row = MultiBufferRow(start.row);
10797 let first_indent = buffer.indent_size_for_line(row);
10798 if first_indent.len == 0 || start.column > first_indent.len {
10799 trimmed_selections.push(start..end);
10800 } else {
10801 trimmed_selections.push(
10802 Point::new(row.0, first_indent.len)
10803 ..Point::new(row.0, buffer.line_len(row)),
10804 );
10805 for row in start.row + 1..=end.row {
10806 let mut line_len = buffer.line_len(MultiBufferRow(row));
10807 if row == end.row {
10808 line_len = end.column;
10809 }
10810 if line_len == 0 {
10811 trimmed_selections
10812 .push(Point::new(row, 0)..Point::new(row, line_len));
10813 continue;
10814 }
10815 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10816 if row_indent_size.len >= first_indent.len {
10817 trimmed_selections.push(
10818 Point::new(row, first_indent.len)..Point::new(row, line_len),
10819 );
10820 } else {
10821 trimmed_selections.clear();
10822 trimmed_selections.push(start..end);
10823 break;
10824 }
10825 }
10826 }
10827 } else {
10828 trimmed_selections.push(start..end);
10829 }
10830
10831 for trimmed_range in trimmed_selections {
10832 if is_first {
10833 is_first = false;
10834 } else {
10835 text += "\n";
10836 }
10837 let mut len = 0;
10838 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10839 text.push_str(chunk);
10840 len += chunk.len();
10841 }
10842 clipboard_selections.push(ClipboardSelection {
10843 len,
10844 is_entire_line,
10845 first_line_indent: buffer
10846 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10847 .len,
10848 });
10849 }
10850 }
10851 }
10852
10853 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10854 text,
10855 clipboard_selections,
10856 ));
10857 }
10858
10859 pub fn do_paste(
10860 &mut self,
10861 text: &String,
10862 clipboard_selections: Option<Vec<ClipboardSelection>>,
10863 handle_entire_lines: bool,
10864 window: &mut Window,
10865 cx: &mut Context<Self>,
10866 ) {
10867 if self.read_only(cx) {
10868 return;
10869 }
10870
10871 let clipboard_text = Cow::Borrowed(text);
10872
10873 self.transact(window, cx, |this, window, cx| {
10874 if let Some(mut clipboard_selections) = clipboard_selections {
10875 let old_selections = this.selections.all::<usize>(cx);
10876 let all_selections_were_entire_line =
10877 clipboard_selections.iter().all(|s| s.is_entire_line);
10878 let first_selection_indent_column =
10879 clipboard_selections.first().map(|s| s.first_line_indent);
10880 if clipboard_selections.len() != old_selections.len() {
10881 clipboard_selections.drain(..);
10882 }
10883 let cursor_offset = this.selections.last::<usize>(cx).head();
10884 let mut auto_indent_on_paste = true;
10885
10886 this.buffer.update(cx, |buffer, cx| {
10887 let snapshot = buffer.read(cx);
10888 auto_indent_on_paste = snapshot
10889 .language_settings_at(cursor_offset, cx)
10890 .auto_indent_on_paste;
10891
10892 let mut start_offset = 0;
10893 let mut edits = Vec::new();
10894 let mut original_indent_columns = Vec::new();
10895 for (ix, selection) in old_selections.iter().enumerate() {
10896 let to_insert;
10897 let entire_line;
10898 let original_indent_column;
10899 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10900 let end_offset = start_offset + clipboard_selection.len;
10901 to_insert = &clipboard_text[start_offset..end_offset];
10902 entire_line = clipboard_selection.is_entire_line;
10903 start_offset = end_offset + 1;
10904 original_indent_column = Some(clipboard_selection.first_line_indent);
10905 } else {
10906 to_insert = clipboard_text.as_str();
10907 entire_line = all_selections_were_entire_line;
10908 original_indent_column = first_selection_indent_column
10909 }
10910
10911 // If the corresponding selection was empty when this slice of the
10912 // clipboard text was written, then the entire line containing the
10913 // selection was copied. If this selection is also currently empty,
10914 // then paste the line before the current line of the buffer.
10915 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10916 let column = selection.start.to_point(&snapshot).column as usize;
10917 let line_start = selection.start - column;
10918 line_start..line_start
10919 } else {
10920 selection.range()
10921 };
10922
10923 edits.push((range, to_insert));
10924 original_indent_columns.push(original_indent_column);
10925 }
10926 drop(snapshot);
10927
10928 buffer.edit(
10929 edits,
10930 if auto_indent_on_paste {
10931 Some(AutoindentMode::Block {
10932 original_indent_columns,
10933 })
10934 } else {
10935 None
10936 },
10937 cx,
10938 );
10939 });
10940
10941 let selections = this.selections.all::<usize>(cx);
10942 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10943 s.select(selections)
10944 });
10945 } else {
10946 this.insert(&clipboard_text, window, cx);
10947 }
10948 });
10949 }
10950
10951 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10952 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10953 if let Some(item) = cx.read_from_clipboard() {
10954 let entries = item.entries();
10955
10956 match entries.first() {
10957 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10958 // of all the pasted entries.
10959 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10960 .do_paste(
10961 clipboard_string.text(),
10962 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10963 true,
10964 window,
10965 cx,
10966 ),
10967 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10968 }
10969 }
10970 }
10971
10972 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10973 if self.read_only(cx) {
10974 return;
10975 }
10976
10977 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10978
10979 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10980 if let Some((selections, _)) =
10981 self.selection_history.transaction(transaction_id).cloned()
10982 {
10983 self.change_selections(None, window, cx, |s| {
10984 s.select_anchors(selections.to_vec());
10985 });
10986 } else {
10987 log::error!(
10988 "No entry in selection_history found for undo. \
10989 This may correspond to a bug where undo does not update the selection. \
10990 If this is occurring, please add details to \
10991 https://github.com/zed-industries/zed/issues/22692"
10992 );
10993 }
10994 self.request_autoscroll(Autoscroll::fit(), cx);
10995 self.unmark_text(window, cx);
10996 self.refresh_inline_completion(true, false, window, cx);
10997 cx.emit(EditorEvent::Edited { transaction_id });
10998 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10999 }
11000 }
11001
11002 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11003 if self.read_only(cx) {
11004 return;
11005 }
11006
11007 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11008
11009 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11010 if let Some((_, Some(selections))) =
11011 self.selection_history.transaction(transaction_id).cloned()
11012 {
11013 self.change_selections(None, window, cx, |s| {
11014 s.select_anchors(selections.to_vec());
11015 });
11016 } else {
11017 log::error!(
11018 "No entry in selection_history found for redo. \
11019 This may correspond to a bug where undo does not update the selection. \
11020 If this is occurring, please add details to \
11021 https://github.com/zed-industries/zed/issues/22692"
11022 );
11023 }
11024 self.request_autoscroll(Autoscroll::fit(), cx);
11025 self.unmark_text(window, cx);
11026 self.refresh_inline_completion(true, false, window, cx);
11027 cx.emit(EditorEvent::Edited { transaction_id });
11028 }
11029 }
11030
11031 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11032 self.buffer
11033 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11034 }
11035
11036 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11037 self.buffer
11038 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11039 }
11040
11041 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11042 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11043 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11044 s.move_with(|map, selection| {
11045 let cursor = if selection.is_empty() {
11046 movement::left(map, selection.start)
11047 } else {
11048 selection.start
11049 };
11050 selection.collapse_to(cursor, SelectionGoal::None);
11051 });
11052 })
11053 }
11054
11055 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11057 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11058 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11059 })
11060 }
11061
11062 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11063 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11064 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11065 s.move_with(|map, selection| {
11066 let cursor = if selection.is_empty() {
11067 movement::right(map, selection.end)
11068 } else {
11069 selection.end
11070 };
11071 selection.collapse_to(cursor, SelectionGoal::None)
11072 });
11073 })
11074 }
11075
11076 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11077 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11078 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11079 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11080 })
11081 }
11082
11083 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11084 if self.take_rename(true, window, cx).is_some() {
11085 return;
11086 }
11087
11088 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11089 cx.propagate();
11090 return;
11091 }
11092
11093 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11094
11095 let text_layout_details = &self.text_layout_details(window);
11096 let selection_count = self.selections.count();
11097 let first_selection = self.selections.first_anchor();
11098
11099 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11100 s.move_with(|map, selection| {
11101 if !selection.is_empty() {
11102 selection.goal = SelectionGoal::None;
11103 }
11104 let (cursor, goal) = movement::up(
11105 map,
11106 selection.start,
11107 selection.goal,
11108 false,
11109 text_layout_details,
11110 );
11111 selection.collapse_to(cursor, goal);
11112 });
11113 });
11114
11115 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11116 {
11117 cx.propagate();
11118 }
11119 }
11120
11121 pub fn move_up_by_lines(
11122 &mut self,
11123 action: &MoveUpByLines,
11124 window: &mut Window,
11125 cx: &mut Context<Self>,
11126 ) {
11127 if self.take_rename(true, window, cx).is_some() {
11128 return;
11129 }
11130
11131 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11132 cx.propagate();
11133 return;
11134 }
11135
11136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11137
11138 let text_layout_details = &self.text_layout_details(window);
11139
11140 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11141 s.move_with(|map, selection| {
11142 if !selection.is_empty() {
11143 selection.goal = SelectionGoal::None;
11144 }
11145 let (cursor, goal) = movement::up_by_rows(
11146 map,
11147 selection.start,
11148 action.lines,
11149 selection.goal,
11150 false,
11151 text_layout_details,
11152 );
11153 selection.collapse_to(cursor, goal);
11154 });
11155 })
11156 }
11157
11158 pub fn move_down_by_lines(
11159 &mut self,
11160 action: &MoveDownByLines,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 if self.take_rename(true, window, cx).is_some() {
11165 return;
11166 }
11167
11168 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11169 cx.propagate();
11170 return;
11171 }
11172
11173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11174
11175 let text_layout_details = &self.text_layout_details(window);
11176
11177 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11178 s.move_with(|map, selection| {
11179 if !selection.is_empty() {
11180 selection.goal = SelectionGoal::None;
11181 }
11182 let (cursor, goal) = movement::down_by_rows(
11183 map,
11184 selection.start,
11185 action.lines,
11186 selection.goal,
11187 false,
11188 text_layout_details,
11189 );
11190 selection.collapse_to(cursor, goal);
11191 });
11192 })
11193 }
11194
11195 pub fn select_down_by_lines(
11196 &mut self,
11197 action: &SelectDownByLines,
11198 window: &mut Window,
11199 cx: &mut Context<Self>,
11200 ) {
11201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11202 let text_layout_details = &self.text_layout_details(window);
11203 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11204 s.move_heads_with(|map, head, goal| {
11205 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11206 })
11207 })
11208 }
11209
11210 pub fn select_up_by_lines(
11211 &mut self,
11212 action: &SelectUpByLines,
11213 window: &mut Window,
11214 cx: &mut Context<Self>,
11215 ) {
11216 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11217 let text_layout_details = &self.text_layout_details(window);
11218 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11219 s.move_heads_with(|map, head, goal| {
11220 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11221 })
11222 })
11223 }
11224
11225 pub fn select_page_up(
11226 &mut self,
11227 _: &SelectPageUp,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 let Some(row_count) = self.visible_row_count() else {
11232 return;
11233 };
11234
11235 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11236
11237 let text_layout_details = &self.text_layout_details(window);
11238
11239 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11240 s.move_heads_with(|map, head, goal| {
11241 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11242 })
11243 })
11244 }
11245
11246 pub fn move_page_up(
11247 &mut self,
11248 action: &MovePageUp,
11249 window: &mut Window,
11250 cx: &mut Context<Self>,
11251 ) {
11252 if self.take_rename(true, window, cx).is_some() {
11253 return;
11254 }
11255
11256 if self
11257 .context_menu
11258 .borrow_mut()
11259 .as_mut()
11260 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11261 .unwrap_or(false)
11262 {
11263 return;
11264 }
11265
11266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11267 cx.propagate();
11268 return;
11269 }
11270
11271 let Some(row_count) = self.visible_row_count() else {
11272 return;
11273 };
11274
11275 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11276
11277 let autoscroll = if action.center_cursor {
11278 Autoscroll::center()
11279 } else {
11280 Autoscroll::fit()
11281 };
11282
11283 let text_layout_details = &self.text_layout_details(window);
11284
11285 self.change_selections(Some(autoscroll), window, cx, |s| {
11286 s.move_with(|map, selection| {
11287 if !selection.is_empty() {
11288 selection.goal = SelectionGoal::None;
11289 }
11290 let (cursor, goal) = movement::up_by_rows(
11291 map,
11292 selection.end,
11293 row_count,
11294 selection.goal,
11295 false,
11296 text_layout_details,
11297 );
11298 selection.collapse_to(cursor, goal);
11299 });
11300 });
11301 }
11302
11303 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11304 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11305 let text_layout_details = &self.text_layout_details(window);
11306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11307 s.move_heads_with(|map, head, goal| {
11308 movement::up(map, head, goal, false, text_layout_details)
11309 })
11310 })
11311 }
11312
11313 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11314 self.take_rename(true, window, cx);
11315
11316 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11317 cx.propagate();
11318 return;
11319 }
11320
11321 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11322
11323 let text_layout_details = &self.text_layout_details(window);
11324 let selection_count = self.selections.count();
11325 let first_selection = self.selections.first_anchor();
11326
11327 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11328 s.move_with(|map, selection| {
11329 if !selection.is_empty() {
11330 selection.goal = SelectionGoal::None;
11331 }
11332 let (cursor, goal) = movement::down(
11333 map,
11334 selection.end,
11335 selection.goal,
11336 false,
11337 text_layout_details,
11338 );
11339 selection.collapse_to(cursor, goal);
11340 });
11341 });
11342
11343 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11344 {
11345 cx.propagate();
11346 }
11347 }
11348
11349 pub fn select_page_down(
11350 &mut self,
11351 _: &SelectPageDown,
11352 window: &mut Window,
11353 cx: &mut Context<Self>,
11354 ) {
11355 let Some(row_count) = self.visible_row_count() else {
11356 return;
11357 };
11358
11359 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11360
11361 let text_layout_details = &self.text_layout_details(window);
11362
11363 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11364 s.move_heads_with(|map, head, goal| {
11365 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11366 })
11367 })
11368 }
11369
11370 pub fn move_page_down(
11371 &mut self,
11372 action: &MovePageDown,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 if self.take_rename(true, window, cx).is_some() {
11377 return;
11378 }
11379
11380 if self
11381 .context_menu
11382 .borrow_mut()
11383 .as_mut()
11384 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11385 .unwrap_or(false)
11386 {
11387 return;
11388 }
11389
11390 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11391 cx.propagate();
11392 return;
11393 }
11394
11395 let Some(row_count) = self.visible_row_count() else {
11396 return;
11397 };
11398
11399 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11400
11401 let autoscroll = if action.center_cursor {
11402 Autoscroll::center()
11403 } else {
11404 Autoscroll::fit()
11405 };
11406
11407 let text_layout_details = &self.text_layout_details(window);
11408 self.change_selections(Some(autoscroll), window, cx, |s| {
11409 s.move_with(|map, selection| {
11410 if !selection.is_empty() {
11411 selection.goal = SelectionGoal::None;
11412 }
11413 let (cursor, goal) = movement::down_by_rows(
11414 map,
11415 selection.end,
11416 row_count,
11417 selection.goal,
11418 false,
11419 text_layout_details,
11420 );
11421 selection.collapse_to(cursor, goal);
11422 });
11423 });
11424 }
11425
11426 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11427 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11428 let text_layout_details = &self.text_layout_details(window);
11429 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11430 s.move_heads_with(|map, head, goal| {
11431 movement::down(map, head, goal, false, text_layout_details)
11432 })
11433 });
11434 }
11435
11436 pub fn context_menu_first(
11437 &mut self,
11438 _: &ContextMenuFirst,
11439 _window: &mut Window,
11440 cx: &mut Context<Self>,
11441 ) {
11442 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11443 context_menu.select_first(self.completion_provider.as_deref(), cx);
11444 }
11445 }
11446
11447 pub fn context_menu_prev(
11448 &mut self,
11449 _: &ContextMenuPrevious,
11450 _window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11454 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11455 }
11456 }
11457
11458 pub fn context_menu_next(
11459 &mut self,
11460 _: &ContextMenuNext,
11461 _window: &mut Window,
11462 cx: &mut Context<Self>,
11463 ) {
11464 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11465 context_menu.select_next(self.completion_provider.as_deref(), cx);
11466 }
11467 }
11468
11469 pub fn context_menu_last(
11470 &mut self,
11471 _: &ContextMenuLast,
11472 _window: &mut Window,
11473 cx: &mut Context<Self>,
11474 ) {
11475 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11476 context_menu.select_last(self.completion_provider.as_deref(), cx);
11477 }
11478 }
11479
11480 pub fn move_to_previous_word_start(
11481 &mut self,
11482 _: &MoveToPreviousWordStart,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11487 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11488 s.move_cursors_with(|map, head, _| {
11489 (
11490 movement::previous_word_start(map, head),
11491 SelectionGoal::None,
11492 )
11493 });
11494 })
11495 }
11496
11497 pub fn move_to_previous_subword_start(
11498 &mut self,
11499 _: &MoveToPreviousSubwordStart,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11504 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11505 s.move_cursors_with(|map, head, _| {
11506 (
11507 movement::previous_subword_start(map, head),
11508 SelectionGoal::None,
11509 )
11510 });
11511 })
11512 }
11513
11514 pub fn select_to_previous_word_start(
11515 &mut self,
11516 _: &SelectToPreviousWordStart,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11521 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11522 s.move_heads_with(|map, head, _| {
11523 (
11524 movement::previous_word_start(map, head),
11525 SelectionGoal::None,
11526 )
11527 });
11528 })
11529 }
11530
11531 pub fn select_to_previous_subword_start(
11532 &mut self,
11533 _: &SelectToPreviousSubwordStart,
11534 window: &mut Window,
11535 cx: &mut Context<Self>,
11536 ) {
11537 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11538 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11539 s.move_heads_with(|map, head, _| {
11540 (
11541 movement::previous_subword_start(map, head),
11542 SelectionGoal::None,
11543 )
11544 });
11545 })
11546 }
11547
11548 pub fn delete_to_previous_word_start(
11549 &mut self,
11550 action: &DeleteToPreviousWordStart,
11551 window: &mut Window,
11552 cx: &mut Context<Self>,
11553 ) {
11554 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11555 self.transact(window, cx, |this, window, cx| {
11556 this.select_autoclose_pair(window, cx);
11557 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11558 s.move_with(|map, selection| {
11559 if selection.is_empty() {
11560 let cursor = if action.ignore_newlines {
11561 movement::previous_word_start(map, selection.head())
11562 } else {
11563 movement::previous_word_start_or_newline(map, selection.head())
11564 };
11565 selection.set_head(cursor, SelectionGoal::None);
11566 }
11567 });
11568 });
11569 this.insert("", window, cx);
11570 });
11571 }
11572
11573 pub fn delete_to_previous_subword_start(
11574 &mut self,
11575 _: &DeleteToPreviousSubwordStart,
11576 window: &mut Window,
11577 cx: &mut Context<Self>,
11578 ) {
11579 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11580 self.transact(window, cx, |this, window, cx| {
11581 this.select_autoclose_pair(window, cx);
11582 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11583 s.move_with(|map, selection| {
11584 if selection.is_empty() {
11585 let cursor = movement::previous_subword_start(map, selection.head());
11586 selection.set_head(cursor, SelectionGoal::None);
11587 }
11588 });
11589 });
11590 this.insert("", window, cx);
11591 });
11592 }
11593
11594 pub fn move_to_next_word_end(
11595 &mut self,
11596 _: &MoveToNextWordEnd,
11597 window: &mut Window,
11598 cx: &mut Context<Self>,
11599 ) {
11600 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11601 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11602 s.move_cursors_with(|map, head, _| {
11603 (movement::next_word_end(map, head), SelectionGoal::None)
11604 });
11605 })
11606 }
11607
11608 pub fn move_to_next_subword_end(
11609 &mut self,
11610 _: &MoveToNextSubwordEnd,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11616 s.move_cursors_with(|map, head, _| {
11617 (movement::next_subword_end(map, head), SelectionGoal::None)
11618 });
11619 })
11620 }
11621
11622 pub fn select_to_next_word_end(
11623 &mut self,
11624 _: &SelectToNextWordEnd,
11625 window: &mut Window,
11626 cx: &mut Context<Self>,
11627 ) {
11628 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11629 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11630 s.move_heads_with(|map, head, _| {
11631 (movement::next_word_end(map, head), SelectionGoal::None)
11632 });
11633 })
11634 }
11635
11636 pub fn select_to_next_subword_end(
11637 &mut self,
11638 _: &SelectToNextSubwordEnd,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_heads_with(|map, head, _| {
11645 (movement::next_subword_end(map, head), SelectionGoal::None)
11646 });
11647 })
11648 }
11649
11650 pub fn delete_to_next_word_end(
11651 &mut self,
11652 action: &DeleteToNextWordEnd,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11657 self.transact(window, cx, |this, window, cx| {
11658 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11659 s.move_with(|map, selection| {
11660 if selection.is_empty() {
11661 let cursor = if action.ignore_newlines {
11662 movement::next_word_end(map, selection.head())
11663 } else {
11664 movement::next_word_end_or_newline(map, selection.head())
11665 };
11666 selection.set_head(cursor, SelectionGoal::None);
11667 }
11668 });
11669 });
11670 this.insert("", window, cx);
11671 });
11672 }
11673
11674 pub fn delete_to_next_subword_end(
11675 &mut self,
11676 _: &DeleteToNextSubwordEnd,
11677 window: &mut Window,
11678 cx: &mut Context<Self>,
11679 ) {
11680 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11681 self.transact(window, cx, |this, window, cx| {
11682 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11683 s.move_with(|map, selection| {
11684 if selection.is_empty() {
11685 let cursor = movement::next_subword_end(map, selection.head());
11686 selection.set_head(cursor, SelectionGoal::None);
11687 }
11688 });
11689 });
11690 this.insert("", window, cx);
11691 });
11692 }
11693
11694 pub fn move_to_beginning_of_line(
11695 &mut self,
11696 action: &MoveToBeginningOfLine,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11701 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11702 s.move_cursors_with(|map, head, _| {
11703 (
11704 movement::indented_line_beginning(
11705 map,
11706 head,
11707 action.stop_at_soft_wraps,
11708 action.stop_at_indent,
11709 ),
11710 SelectionGoal::None,
11711 )
11712 });
11713 })
11714 }
11715
11716 pub fn select_to_beginning_of_line(
11717 &mut self,
11718 action: &SelectToBeginningOfLine,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11723 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11724 s.move_heads_with(|map, head, _| {
11725 (
11726 movement::indented_line_beginning(
11727 map,
11728 head,
11729 action.stop_at_soft_wraps,
11730 action.stop_at_indent,
11731 ),
11732 SelectionGoal::None,
11733 )
11734 });
11735 });
11736 }
11737
11738 pub fn delete_to_beginning_of_line(
11739 &mut self,
11740 action: &DeleteToBeginningOfLine,
11741 window: &mut Window,
11742 cx: &mut Context<Self>,
11743 ) {
11744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11745 self.transact(window, cx, |this, window, cx| {
11746 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11747 s.move_with(|_, selection| {
11748 selection.reversed = true;
11749 });
11750 });
11751
11752 this.select_to_beginning_of_line(
11753 &SelectToBeginningOfLine {
11754 stop_at_soft_wraps: false,
11755 stop_at_indent: action.stop_at_indent,
11756 },
11757 window,
11758 cx,
11759 );
11760 this.backspace(&Backspace, window, cx);
11761 });
11762 }
11763
11764 pub fn move_to_end_of_line(
11765 &mut self,
11766 action: &MoveToEndOfLine,
11767 window: &mut Window,
11768 cx: &mut Context<Self>,
11769 ) {
11770 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11771 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11772 s.move_cursors_with(|map, head, _| {
11773 (
11774 movement::line_end(map, head, action.stop_at_soft_wraps),
11775 SelectionGoal::None,
11776 )
11777 });
11778 })
11779 }
11780
11781 pub fn select_to_end_of_line(
11782 &mut self,
11783 action: &SelectToEndOfLine,
11784 window: &mut Window,
11785 cx: &mut Context<Self>,
11786 ) {
11787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11788 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11789 s.move_heads_with(|map, head, _| {
11790 (
11791 movement::line_end(map, head, action.stop_at_soft_wraps),
11792 SelectionGoal::None,
11793 )
11794 });
11795 })
11796 }
11797
11798 pub fn delete_to_end_of_line(
11799 &mut self,
11800 _: &DeleteToEndOfLine,
11801 window: &mut Window,
11802 cx: &mut Context<Self>,
11803 ) {
11804 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11805 self.transact(window, cx, |this, window, cx| {
11806 this.select_to_end_of_line(
11807 &SelectToEndOfLine {
11808 stop_at_soft_wraps: false,
11809 },
11810 window,
11811 cx,
11812 );
11813 this.delete(&Delete, window, cx);
11814 });
11815 }
11816
11817 pub fn cut_to_end_of_line(
11818 &mut self,
11819 _: &CutToEndOfLine,
11820 window: &mut Window,
11821 cx: &mut Context<Self>,
11822 ) {
11823 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11824 self.transact(window, cx, |this, window, cx| {
11825 this.select_to_end_of_line(
11826 &SelectToEndOfLine {
11827 stop_at_soft_wraps: false,
11828 },
11829 window,
11830 cx,
11831 );
11832 this.cut(&Cut, window, cx);
11833 });
11834 }
11835
11836 pub fn move_to_start_of_paragraph(
11837 &mut self,
11838 _: &MoveToStartOfParagraph,
11839 window: &mut Window,
11840 cx: &mut Context<Self>,
11841 ) {
11842 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11843 cx.propagate();
11844 return;
11845 }
11846 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11847 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11848 s.move_with(|map, selection| {
11849 selection.collapse_to(
11850 movement::start_of_paragraph(map, selection.head(), 1),
11851 SelectionGoal::None,
11852 )
11853 });
11854 })
11855 }
11856
11857 pub fn move_to_end_of_paragraph(
11858 &mut self,
11859 _: &MoveToEndOfParagraph,
11860 window: &mut Window,
11861 cx: &mut Context<Self>,
11862 ) {
11863 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11864 cx.propagate();
11865 return;
11866 }
11867 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11869 s.move_with(|map, selection| {
11870 selection.collapse_to(
11871 movement::end_of_paragraph(map, selection.head(), 1),
11872 SelectionGoal::None,
11873 )
11874 });
11875 })
11876 }
11877
11878 pub fn select_to_start_of_paragraph(
11879 &mut self,
11880 _: &SelectToStartOfParagraph,
11881 window: &mut Window,
11882 cx: &mut Context<Self>,
11883 ) {
11884 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11885 cx.propagate();
11886 return;
11887 }
11888 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11890 s.move_heads_with(|map, head, _| {
11891 (
11892 movement::start_of_paragraph(map, head, 1),
11893 SelectionGoal::None,
11894 )
11895 });
11896 })
11897 }
11898
11899 pub fn select_to_end_of_paragraph(
11900 &mut self,
11901 _: &SelectToEndOfParagraph,
11902 window: &mut Window,
11903 cx: &mut Context<Self>,
11904 ) {
11905 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11906 cx.propagate();
11907 return;
11908 }
11909 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11910 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11911 s.move_heads_with(|map, head, _| {
11912 (
11913 movement::end_of_paragraph(map, head, 1),
11914 SelectionGoal::None,
11915 )
11916 });
11917 })
11918 }
11919
11920 pub fn move_to_start_of_excerpt(
11921 &mut self,
11922 _: &MoveToStartOfExcerpt,
11923 window: &mut Window,
11924 cx: &mut Context<Self>,
11925 ) {
11926 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11927 cx.propagate();
11928 return;
11929 }
11930 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11931 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11932 s.move_with(|map, selection| {
11933 selection.collapse_to(
11934 movement::start_of_excerpt(
11935 map,
11936 selection.head(),
11937 workspace::searchable::Direction::Prev,
11938 ),
11939 SelectionGoal::None,
11940 )
11941 });
11942 })
11943 }
11944
11945 pub fn move_to_start_of_next_excerpt(
11946 &mut self,
11947 _: &MoveToStartOfNextExcerpt,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11952 cx.propagate();
11953 return;
11954 }
11955
11956 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11957 s.move_with(|map, selection| {
11958 selection.collapse_to(
11959 movement::start_of_excerpt(
11960 map,
11961 selection.head(),
11962 workspace::searchable::Direction::Next,
11963 ),
11964 SelectionGoal::None,
11965 )
11966 });
11967 })
11968 }
11969
11970 pub fn move_to_end_of_excerpt(
11971 &mut self,
11972 _: &MoveToEndOfExcerpt,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11977 cx.propagate();
11978 return;
11979 }
11980 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11981 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11982 s.move_with(|map, selection| {
11983 selection.collapse_to(
11984 movement::end_of_excerpt(
11985 map,
11986 selection.head(),
11987 workspace::searchable::Direction::Next,
11988 ),
11989 SelectionGoal::None,
11990 )
11991 });
11992 })
11993 }
11994
11995 pub fn move_to_end_of_previous_excerpt(
11996 &mut self,
11997 _: &MoveToEndOfPreviousExcerpt,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12002 cx.propagate();
12003 return;
12004 }
12005 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12006 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12007 s.move_with(|map, selection| {
12008 selection.collapse_to(
12009 movement::end_of_excerpt(
12010 map,
12011 selection.head(),
12012 workspace::searchable::Direction::Prev,
12013 ),
12014 SelectionGoal::None,
12015 )
12016 });
12017 })
12018 }
12019
12020 pub fn select_to_start_of_excerpt(
12021 &mut self,
12022 _: &SelectToStartOfExcerpt,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12027 cx.propagate();
12028 return;
12029 }
12030 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12031 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12032 s.move_heads_with(|map, head, _| {
12033 (
12034 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12035 SelectionGoal::None,
12036 )
12037 });
12038 })
12039 }
12040
12041 pub fn select_to_start_of_next_excerpt(
12042 &mut self,
12043 _: &SelectToStartOfNextExcerpt,
12044 window: &mut Window,
12045 cx: &mut Context<Self>,
12046 ) {
12047 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12048 cx.propagate();
12049 return;
12050 }
12051 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12052 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12053 s.move_heads_with(|map, head, _| {
12054 (
12055 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12056 SelectionGoal::None,
12057 )
12058 });
12059 })
12060 }
12061
12062 pub fn select_to_end_of_excerpt(
12063 &mut self,
12064 _: &SelectToEndOfExcerpt,
12065 window: &mut Window,
12066 cx: &mut Context<Self>,
12067 ) {
12068 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12069 cx.propagate();
12070 return;
12071 }
12072 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12073 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12074 s.move_heads_with(|map, head, _| {
12075 (
12076 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12077 SelectionGoal::None,
12078 )
12079 });
12080 })
12081 }
12082
12083 pub fn select_to_end_of_previous_excerpt(
12084 &mut self,
12085 _: &SelectToEndOfPreviousExcerpt,
12086 window: &mut Window,
12087 cx: &mut Context<Self>,
12088 ) {
12089 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12090 cx.propagate();
12091 return;
12092 }
12093 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12094 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12095 s.move_heads_with(|map, head, _| {
12096 (
12097 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12098 SelectionGoal::None,
12099 )
12100 });
12101 })
12102 }
12103
12104 pub fn move_to_beginning(
12105 &mut self,
12106 _: &MoveToBeginning,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12111 cx.propagate();
12112 return;
12113 }
12114 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12116 s.select_ranges(vec![0..0]);
12117 });
12118 }
12119
12120 pub fn select_to_beginning(
12121 &mut self,
12122 _: &SelectToBeginning,
12123 window: &mut Window,
12124 cx: &mut Context<Self>,
12125 ) {
12126 let mut selection = self.selections.last::<Point>(cx);
12127 selection.set_head(Point::zero(), SelectionGoal::None);
12128 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12129 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12130 s.select(vec![selection]);
12131 });
12132 }
12133
12134 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12135 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12136 cx.propagate();
12137 return;
12138 }
12139 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12140 let cursor = self.buffer.read(cx).read(cx).len();
12141 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12142 s.select_ranges(vec![cursor..cursor])
12143 });
12144 }
12145
12146 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12147 self.nav_history = nav_history;
12148 }
12149
12150 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12151 self.nav_history.as_ref()
12152 }
12153
12154 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12155 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12156 }
12157
12158 fn push_to_nav_history(
12159 &mut self,
12160 cursor_anchor: Anchor,
12161 new_position: Option<Point>,
12162 is_deactivate: bool,
12163 cx: &mut Context<Self>,
12164 ) {
12165 if let Some(nav_history) = self.nav_history.as_mut() {
12166 let buffer = self.buffer.read(cx).read(cx);
12167 let cursor_position = cursor_anchor.to_point(&buffer);
12168 let scroll_state = self.scroll_manager.anchor();
12169 let scroll_top_row = scroll_state.top_row(&buffer);
12170 drop(buffer);
12171
12172 if let Some(new_position) = new_position {
12173 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12174 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12175 return;
12176 }
12177 }
12178
12179 nav_history.push(
12180 Some(NavigationData {
12181 cursor_anchor,
12182 cursor_position,
12183 scroll_anchor: scroll_state,
12184 scroll_top_row,
12185 }),
12186 cx,
12187 );
12188 cx.emit(EditorEvent::PushedToNavHistory {
12189 anchor: cursor_anchor,
12190 is_deactivate,
12191 })
12192 }
12193 }
12194
12195 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12196 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12197 let buffer = self.buffer.read(cx).snapshot(cx);
12198 let mut selection = self.selections.first::<usize>(cx);
12199 selection.set_head(buffer.len(), SelectionGoal::None);
12200 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12201 s.select(vec![selection]);
12202 });
12203 }
12204
12205 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12206 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12207 let end = self.buffer.read(cx).read(cx).len();
12208 self.change_selections(None, window, cx, |s| {
12209 s.select_ranges(vec![0..end]);
12210 });
12211 }
12212
12213 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12215 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12216 let mut selections = self.selections.all::<Point>(cx);
12217 let max_point = display_map.buffer_snapshot.max_point();
12218 for selection in &mut selections {
12219 let rows = selection.spanned_rows(true, &display_map);
12220 selection.start = Point::new(rows.start.0, 0);
12221 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12222 selection.reversed = false;
12223 }
12224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12225 s.select(selections);
12226 });
12227 }
12228
12229 pub fn split_selection_into_lines(
12230 &mut self,
12231 _: &SplitSelectionIntoLines,
12232 window: &mut Window,
12233 cx: &mut Context<Self>,
12234 ) {
12235 let selections = self
12236 .selections
12237 .all::<Point>(cx)
12238 .into_iter()
12239 .map(|selection| selection.start..selection.end)
12240 .collect::<Vec<_>>();
12241 self.unfold_ranges(&selections, true, true, cx);
12242
12243 let mut new_selection_ranges = Vec::new();
12244 {
12245 let buffer = self.buffer.read(cx).read(cx);
12246 for selection in selections {
12247 for row in selection.start.row..selection.end.row {
12248 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12249 new_selection_ranges.push(cursor..cursor);
12250 }
12251
12252 let is_multiline_selection = selection.start.row != selection.end.row;
12253 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12254 // so this action feels more ergonomic when paired with other selection operations
12255 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12256 if !should_skip_last {
12257 new_selection_ranges.push(selection.end..selection.end);
12258 }
12259 }
12260 }
12261 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12262 s.select_ranges(new_selection_ranges);
12263 });
12264 }
12265
12266 pub fn add_selection_above(
12267 &mut self,
12268 _: &AddSelectionAbove,
12269 window: &mut Window,
12270 cx: &mut Context<Self>,
12271 ) {
12272 self.add_selection(true, window, cx);
12273 }
12274
12275 pub fn add_selection_below(
12276 &mut self,
12277 _: &AddSelectionBelow,
12278 window: &mut Window,
12279 cx: &mut Context<Self>,
12280 ) {
12281 self.add_selection(false, window, cx);
12282 }
12283
12284 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12285 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12286
12287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12288 let mut selections = self.selections.all::<Point>(cx);
12289 let text_layout_details = self.text_layout_details(window);
12290 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12291 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12292 let range = oldest_selection.display_range(&display_map).sorted();
12293
12294 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12295 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12296 let positions = start_x.min(end_x)..start_x.max(end_x);
12297
12298 selections.clear();
12299 let mut stack = Vec::new();
12300 for row in range.start.row().0..=range.end.row().0 {
12301 if let Some(selection) = self.selections.build_columnar_selection(
12302 &display_map,
12303 DisplayRow(row),
12304 &positions,
12305 oldest_selection.reversed,
12306 &text_layout_details,
12307 ) {
12308 stack.push(selection.id);
12309 selections.push(selection);
12310 }
12311 }
12312
12313 if above {
12314 stack.reverse();
12315 }
12316
12317 AddSelectionsState { above, stack }
12318 });
12319
12320 let last_added_selection = *state.stack.last().unwrap();
12321 let mut new_selections = Vec::new();
12322 if above == state.above {
12323 let end_row = if above {
12324 DisplayRow(0)
12325 } else {
12326 display_map.max_point().row()
12327 };
12328
12329 'outer: for selection in selections {
12330 if selection.id == last_added_selection {
12331 let range = selection.display_range(&display_map).sorted();
12332 debug_assert_eq!(range.start.row(), range.end.row());
12333 let mut row = range.start.row();
12334 let positions =
12335 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12336 px(start)..px(end)
12337 } else {
12338 let start_x =
12339 display_map.x_for_display_point(range.start, &text_layout_details);
12340 let end_x =
12341 display_map.x_for_display_point(range.end, &text_layout_details);
12342 start_x.min(end_x)..start_x.max(end_x)
12343 };
12344
12345 while row != end_row {
12346 if above {
12347 row.0 -= 1;
12348 } else {
12349 row.0 += 1;
12350 }
12351
12352 if let Some(new_selection) = self.selections.build_columnar_selection(
12353 &display_map,
12354 row,
12355 &positions,
12356 selection.reversed,
12357 &text_layout_details,
12358 ) {
12359 state.stack.push(new_selection.id);
12360 if above {
12361 new_selections.push(new_selection);
12362 new_selections.push(selection);
12363 } else {
12364 new_selections.push(selection);
12365 new_selections.push(new_selection);
12366 }
12367
12368 continue 'outer;
12369 }
12370 }
12371 }
12372
12373 new_selections.push(selection);
12374 }
12375 } else {
12376 new_selections = selections;
12377 new_selections.retain(|s| s.id != last_added_selection);
12378 state.stack.pop();
12379 }
12380
12381 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12382 s.select(new_selections);
12383 });
12384 if state.stack.len() > 1 {
12385 self.add_selections_state = Some(state);
12386 }
12387 }
12388
12389 fn select_match_ranges(
12390 &mut self,
12391 range: Range<usize>,
12392 reversed: bool,
12393 replace_newest: bool,
12394 auto_scroll: Option<Autoscroll>,
12395 window: &mut Window,
12396 cx: &mut Context<Editor>,
12397 ) {
12398 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12399 self.change_selections(auto_scroll, window, cx, |s| {
12400 if replace_newest {
12401 s.delete(s.newest_anchor().id);
12402 }
12403 if reversed {
12404 s.insert_range(range.end..range.start);
12405 } else {
12406 s.insert_range(range);
12407 }
12408 });
12409 }
12410
12411 pub fn select_next_match_internal(
12412 &mut self,
12413 display_map: &DisplaySnapshot,
12414 replace_newest: bool,
12415 autoscroll: Option<Autoscroll>,
12416 window: &mut Window,
12417 cx: &mut Context<Self>,
12418 ) -> Result<()> {
12419 let buffer = &display_map.buffer_snapshot;
12420 let mut selections = self.selections.all::<usize>(cx);
12421 if let Some(mut select_next_state) = self.select_next_state.take() {
12422 let query = &select_next_state.query;
12423 if !select_next_state.done {
12424 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12425 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12426 let mut next_selected_range = None;
12427
12428 let bytes_after_last_selection =
12429 buffer.bytes_in_range(last_selection.end..buffer.len());
12430 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12431 let query_matches = query
12432 .stream_find_iter(bytes_after_last_selection)
12433 .map(|result| (last_selection.end, result))
12434 .chain(
12435 query
12436 .stream_find_iter(bytes_before_first_selection)
12437 .map(|result| (0, result)),
12438 );
12439
12440 for (start_offset, query_match) in query_matches {
12441 let query_match = query_match.unwrap(); // can only fail due to I/O
12442 let offset_range =
12443 start_offset + query_match.start()..start_offset + query_match.end();
12444 let display_range = offset_range.start.to_display_point(display_map)
12445 ..offset_range.end.to_display_point(display_map);
12446
12447 if !select_next_state.wordwise
12448 || (!movement::is_inside_word(display_map, display_range.start)
12449 && !movement::is_inside_word(display_map, display_range.end))
12450 {
12451 // TODO: This is n^2, because we might check all the selections
12452 if !selections
12453 .iter()
12454 .any(|selection| selection.range().overlaps(&offset_range))
12455 {
12456 next_selected_range = Some(offset_range);
12457 break;
12458 }
12459 }
12460 }
12461
12462 if let Some(next_selected_range) = next_selected_range {
12463 self.select_match_ranges(
12464 next_selected_range,
12465 last_selection.reversed,
12466 replace_newest,
12467 autoscroll,
12468 window,
12469 cx,
12470 );
12471 } else {
12472 select_next_state.done = true;
12473 }
12474 }
12475
12476 self.select_next_state = Some(select_next_state);
12477 } else {
12478 let mut only_carets = true;
12479 let mut same_text_selected = true;
12480 let mut selected_text = None;
12481
12482 let mut selections_iter = selections.iter().peekable();
12483 while let Some(selection) = selections_iter.next() {
12484 if selection.start != selection.end {
12485 only_carets = false;
12486 }
12487
12488 if same_text_selected {
12489 if selected_text.is_none() {
12490 selected_text =
12491 Some(buffer.text_for_range(selection.range()).collect::<String>());
12492 }
12493
12494 if let Some(next_selection) = selections_iter.peek() {
12495 if next_selection.range().len() == selection.range().len() {
12496 let next_selected_text = buffer
12497 .text_for_range(next_selection.range())
12498 .collect::<String>();
12499 if Some(next_selected_text) != selected_text {
12500 same_text_selected = false;
12501 selected_text = None;
12502 }
12503 } else {
12504 same_text_selected = false;
12505 selected_text = None;
12506 }
12507 }
12508 }
12509 }
12510
12511 if only_carets {
12512 for selection in &mut selections {
12513 let word_range = movement::surrounding_word(
12514 display_map,
12515 selection.start.to_display_point(display_map),
12516 );
12517 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12518 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12519 selection.goal = SelectionGoal::None;
12520 selection.reversed = false;
12521 self.select_match_ranges(
12522 selection.start..selection.end,
12523 selection.reversed,
12524 replace_newest,
12525 autoscroll,
12526 window,
12527 cx,
12528 );
12529 }
12530
12531 if selections.len() == 1 {
12532 let selection = selections
12533 .last()
12534 .expect("ensured that there's only one selection");
12535 let query = buffer
12536 .text_for_range(selection.start..selection.end)
12537 .collect::<String>();
12538 let is_empty = query.is_empty();
12539 let select_state = SelectNextState {
12540 query: AhoCorasick::new(&[query])?,
12541 wordwise: true,
12542 done: is_empty,
12543 };
12544 self.select_next_state = Some(select_state);
12545 } else {
12546 self.select_next_state = None;
12547 }
12548 } else if let Some(selected_text) = selected_text {
12549 self.select_next_state = Some(SelectNextState {
12550 query: AhoCorasick::new(&[selected_text])?,
12551 wordwise: false,
12552 done: false,
12553 });
12554 self.select_next_match_internal(
12555 display_map,
12556 replace_newest,
12557 autoscroll,
12558 window,
12559 cx,
12560 )?;
12561 }
12562 }
12563 Ok(())
12564 }
12565
12566 pub fn select_all_matches(
12567 &mut self,
12568 _action: &SelectAllMatches,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) -> Result<()> {
12572 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12573
12574 self.push_to_selection_history();
12575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12576
12577 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12578 let Some(select_next_state) = self.select_next_state.as_mut() else {
12579 return Ok(());
12580 };
12581 if select_next_state.done {
12582 return Ok(());
12583 }
12584
12585 let mut new_selections = Vec::new();
12586
12587 let reversed = self.selections.oldest::<usize>(cx).reversed;
12588 let buffer = &display_map.buffer_snapshot;
12589 let query_matches = select_next_state
12590 .query
12591 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12592
12593 for query_match in query_matches.into_iter() {
12594 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12595 let offset_range = if reversed {
12596 query_match.end()..query_match.start()
12597 } else {
12598 query_match.start()..query_match.end()
12599 };
12600 let display_range = offset_range.start.to_display_point(&display_map)
12601 ..offset_range.end.to_display_point(&display_map);
12602
12603 if !select_next_state.wordwise
12604 || (!movement::is_inside_word(&display_map, display_range.start)
12605 && !movement::is_inside_word(&display_map, display_range.end))
12606 {
12607 new_selections.push(offset_range.start..offset_range.end);
12608 }
12609 }
12610
12611 select_next_state.done = true;
12612 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12613 self.change_selections(None, window, cx, |selections| {
12614 selections.select_ranges(new_selections)
12615 });
12616
12617 Ok(())
12618 }
12619
12620 pub fn select_next(
12621 &mut self,
12622 action: &SelectNext,
12623 window: &mut Window,
12624 cx: &mut Context<Self>,
12625 ) -> Result<()> {
12626 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12627 self.push_to_selection_history();
12628 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12629 self.select_next_match_internal(
12630 &display_map,
12631 action.replace_newest,
12632 Some(Autoscroll::newest()),
12633 window,
12634 cx,
12635 )?;
12636 Ok(())
12637 }
12638
12639 pub fn select_previous(
12640 &mut self,
12641 action: &SelectPrevious,
12642 window: &mut Window,
12643 cx: &mut Context<Self>,
12644 ) -> Result<()> {
12645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12646 self.push_to_selection_history();
12647 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12648 let buffer = &display_map.buffer_snapshot;
12649 let mut selections = self.selections.all::<usize>(cx);
12650 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12651 let query = &select_prev_state.query;
12652 if !select_prev_state.done {
12653 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12654 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12655 let mut next_selected_range = None;
12656 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12657 let bytes_before_last_selection =
12658 buffer.reversed_bytes_in_range(0..last_selection.start);
12659 let bytes_after_first_selection =
12660 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12661 let query_matches = query
12662 .stream_find_iter(bytes_before_last_selection)
12663 .map(|result| (last_selection.start, result))
12664 .chain(
12665 query
12666 .stream_find_iter(bytes_after_first_selection)
12667 .map(|result| (buffer.len(), result)),
12668 );
12669 for (end_offset, query_match) in query_matches {
12670 let query_match = query_match.unwrap(); // can only fail due to I/O
12671 let offset_range =
12672 end_offset - query_match.end()..end_offset - query_match.start();
12673 let display_range = offset_range.start.to_display_point(&display_map)
12674 ..offset_range.end.to_display_point(&display_map);
12675
12676 if !select_prev_state.wordwise
12677 || (!movement::is_inside_word(&display_map, display_range.start)
12678 && !movement::is_inside_word(&display_map, display_range.end))
12679 {
12680 next_selected_range = Some(offset_range);
12681 break;
12682 }
12683 }
12684
12685 if let Some(next_selected_range) = next_selected_range {
12686 self.select_match_ranges(
12687 next_selected_range,
12688 last_selection.reversed,
12689 action.replace_newest,
12690 Some(Autoscroll::newest()),
12691 window,
12692 cx,
12693 );
12694 } else {
12695 select_prev_state.done = true;
12696 }
12697 }
12698
12699 self.select_prev_state = Some(select_prev_state);
12700 } else {
12701 let mut only_carets = true;
12702 let mut same_text_selected = true;
12703 let mut selected_text = None;
12704
12705 let mut selections_iter = selections.iter().peekable();
12706 while let Some(selection) = selections_iter.next() {
12707 if selection.start != selection.end {
12708 only_carets = false;
12709 }
12710
12711 if same_text_selected {
12712 if selected_text.is_none() {
12713 selected_text =
12714 Some(buffer.text_for_range(selection.range()).collect::<String>());
12715 }
12716
12717 if let Some(next_selection) = selections_iter.peek() {
12718 if next_selection.range().len() == selection.range().len() {
12719 let next_selected_text = buffer
12720 .text_for_range(next_selection.range())
12721 .collect::<String>();
12722 if Some(next_selected_text) != selected_text {
12723 same_text_selected = false;
12724 selected_text = None;
12725 }
12726 } else {
12727 same_text_selected = false;
12728 selected_text = None;
12729 }
12730 }
12731 }
12732 }
12733
12734 if only_carets {
12735 for selection in &mut selections {
12736 let word_range = movement::surrounding_word(
12737 &display_map,
12738 selection.start.to_display_point(&display_map),
12739 );
12740 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12741 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12742 selection.goal = SelectionGoal::None;
12743 selection.reversed = false;
12744 self.select_match_ranges(
12745 selection.start..selection.end,
12746 selection.reversed,
12747 action.replace_newest,
12748 Some(Autoscroll::newest()),
12749 window,
12750 cx,
12751 );
12752 }
12753 if selections.len() == 1 {
12754 let selection = selections
12755 .last()
12756 .expect("ensured that there's only one selection");
12757 let query = buffer
12758 .text_for_range(selection.start..selection.end)
12759 .collect::<String>();
12760 let is_empty = query.is_empty();
12761 let select_state = SelectNextState {
12762 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12763 wordwise: true,
12764 done: is_empty,
12765 };
12766 self.select_prev_state = Some(select_state);
12767 } else {
12768 self.select_prev_state = None;
12769 }
12770 } else if let Some(selected_text) = selected_text {
12771 self.select_prev_state = Some(SelectNextState {
12772 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12773 wordwise: false,
12774 done: false,
12775 });
12776 self.select_previous(action, window, cx)?;
12777 }
12778 }
12779 Ok(())
12780 }
12781
12782 pub fn find_next_match(
12783 &mut self,
12784 _: &FindNextMatch,
12785 window: &mut Window,
12786 cx: &mut Context<Self>,
12787 ) -> Result<()> {
12788 let selections = self.selections.disjoint_anchors();
12789 match selections.first() {
12790 Some(first) if selections.len() >= 2 => {
12791 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12792 s.select_ranges([first.range()]);
12793 });
12794 }
12795 _ => self.select_next(
12796 &SelectNext {
12797 replace_newest: true,
12798 },
12799 window,
12800 cx,
12801 )?,
12802 }
12803 Ok(())
12804 }
12805
12806 pub fn find_previous_match(
12807 &mut self,
12808 _: &FindPreviousMatch,
12809 window: &mut Window,
12810 cx: &mut Context<Self>,
12811 ) -> Result<()> {
12812 let selections = self.selections.disjoint_anchors();
12813 match selections.last() {
12814 Some(last) if selections.len() >= 2 => {
12815 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12816 s.select_ranges([last.range()]);
12817 });
12818 }
12819 _ => self.select_previous(
12820 &SelectPrevious {
12821 replace_newest: true,
12822 },
12823 window,
12824 cx,
12825 )?,
12826 }
12827 Ok(())
12828 }
12829
12830 pub fn toggle_comments(
12831 &mut self,
12832 action: &ToggleComments,
12833 window: &mut Window,
12834 cx: &mut Context<Self>,
12835 ) {
12836 if self.read_only(cx) {
12837 return;
12838 }
12839 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12840 let text_layout_details = &self.text_layout_details(window);
12841 self.transact(window, cx, |this, window, cx| {
12842 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12843 let mut edits = Vec::new();
12844 let mut selection_edit_ranges = Vec::new();
12845 let mut last_toggled_row = None;
12846 let snapshot = this.buffer.read(cx).read(cx);
12847 let empty_str: Arc<str> = Arc::default();
12848 let mut suffixes_inserted = Vec::new();
12849 let ignore_indent = action.ignore_indent;
12850
12851 fn comment_prefix_range(
12852 snapshot: &MultiBufferSnapshot,
12853 row: MultiBufferRow,
12854 comment_prefix: &str,
12855 comment_prefix_whitespace: &str,
12856 ignore_indent: bool,
12857 ) -> Range<Point> {
12858 let indent_size = if ignore_indent {
12859 0
12860 } else {
12861 snapshot.indent_size_for_line(row).len
12862 };
12863
12864 let start = Point::new(row.0, indent_size);
12865
12866 let mut line_bytes = snapshot
12867 .bytes_in_range(start..snapshot.max_point())
12868 .flatten()
12869 .copied();
12870
12871 // If this line currently begins with the line comment prefix, then record
12872 // the range containing the prefix.
12873 if line_bytes
12874 .by_ref()
12875 .take(comment_prefix.len())
12876 .eq(comment_prefix.bytes())
12877 {
12878 // Include any whitespace that matches the comment prefix.
12879 let matching_whitespace_len = line_bytes
12880 .zip(comment_prefix_whitespace.bytes())
12881 .take_while(|(a, b)| a == b)
12882 .count() as u32;
12883 let end = Point::new(
12884 start.row,
12885 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12886 );
12887 start..end
12888 } else {
12889 start..start
12890 }
12891 }
12892
12893 fn comment_suffix_range(
12894 snapshot: &MultiBufferSnapshot,
12895 row: MultiBufferRow,
12896 comment_suffix: &str,
12897 comment_suffix_has_leading_space: bool,
12898 ) -> Range<Point> {
12899 let end = Point::new(row.0, snapshot.line_len(row));
12900 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12901
12902 let mut line_end_bytes = snapshot
12903 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12904 .flatten()
12905 .copied();
12906
12907 let leading_space_len = if suffix_start_column > 0
12908 && line_end_bytes.next() == Some(b' ')
12909 && comment_suffix_has_leading_space
12910 {
12911 1
12912 } else {
12913 0
12914 };
12915
12916 // If this line currently begins with the line comment prefix, then record
12917 // the range containing the prefix.
12918 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12919 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12920 start..end
12921 } else {
12922 end..end
12923 }
12924 }
12925
12926 // TODO: Handle selections that cross excerpts
12927 for selection in &mut selections {
12928 let start_column = snapshot
12929 .indent_size_for_line(MultiBufferRow(selection.start.row))
12930 .len;
12931 let language = if let Some(language) =
12932 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12933 {
12934 language
12935 } else {
12936 continue;
12937 };
12938
12939 selection_edit_ranges.clear();
12940
12941 // If multiple selections contain a given row, avoid processing that
12942 // row more than once.
12943 let mut start_row = MultiBufferRow(selection.start.row);
12944 if last_toggled_row == Some(start_row) {
12945 start_row = start_row.next_row();
12946 }
12947 let end_row =
12948 if selection.end.row > selection.start.row && selection.end.column == 0 {
12949 MultiBufferRow(selection.end.row - 1)
12950 } else {
12951 MultiBufferRow(selection.end.row)
12952 };
12953 last_toggled_row = Some(end_row);
12954
12955 if start_row > end_row {
12956 continue;
12957 }
12958
12959 // If the language has line comments, toggle those.
12960 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12961
12962 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12963 if ignore_indent {
12964 full_comment_prefixes = full_comment_prefixes
12965 .into_iter()
12966 .map(|s| Arc::from(s.trim_end()))
12967 .collect();
12968 }
12969
12970 if !full_comment_prefixes.is_empty() {
12971 let first_prefix = full_comment_prefixes
12972 .first()
12973 .expect("prefixes is non-empty");
12974 let prefix_trimmed_lengths = full_comment_prefixes
12975 .iter()
12976 .map(|p| p.trim_end_matches(' ').len())
12977 .collect::<SmallVec<[usize; 4]>>();
12978
12979 let mut all_selection_lines_are_comments = true;
12980
12981 for row in start_row.0..=end_row.0 {
12982 let row = MultiBufferRow(row);
12983 if start_row < end_row && snapshot.is_line_blank(row) {
12984 continue;
12985 }
12986
12987 let prefix_range = full_comment_prefixes
12988 .iter()
12989 .zip(prefix_trimmed_lengths.iter().copied())
12990 .map(|(prefix, trimmed_prefix_len)| {
12991 comment_prefix_range(
12992 snapshot.deref(),
12993 row,
12994 &prefix[..trimmed_prefix_len],
12995 &prefix[trimmed_prefix_len..],
12996 ignore_indent,
12997 )
12998 })
12999 .max_by_key(|range| range.end.column - range.start.column)
13000 .expect("prefixes is non-empty");
13001
13002 if prefix_range.is_empty() {
13003 all_selection_lines_are_comments = false;
13004 }
13005
13006 selection_edit_ranges.push(prefix_range);
13007 }
13008
13009 if all_selection_lines_are_comments {
13010 edits.extend(
13011 selection_edit_ranges
13012 .iter()
13013 .cloned()
13014 .map(|range| (range, empty_str.clone())),
13015 );
13016 } else {
13017 let min_column = selection_edit_ranges
13018 .iter()
13019 .map(|range| range.start.column)
13020 .min()
13021 .unwrap_or(0);
13022 edits.extend(selection_edit_ranges.iter().map(|range| {
13023 let position = Point::new(range.start.row, min_column);
13024 (position..position, first_prefix.clone())
13025 }));
13026 }
13027 } else if let Some((full_comment_prefix, comment_suffix)) =
13028 language.block_comment_delimiters()
13029 {
13030 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13031 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13032 let prefix_range = comment_prefix_range(
13033 snapshot.deref(),
13034 start_row,
13035 comment_prefix,
13036 comment_prefix_whitespace,
13037 ignore_indent,
13038 );
13039 let suffix_range = comment_suffix_range(
13040 snapshot.deref(),
13041 end_row,
13042 comment_suffix.trim_start_matches(' '),
13043 comment_suffix.starts_with(' '),
13044 );
13045
13046 if prefix_range.is_empty() || suffix_range.is_empty() {
13047 edits.push((
13048 prefix_range.start..prefix_range.start,
13049 full_comment_prefix.clone(),
13050 ));
13051 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13052 suffixes_inserted.push((end_row, comment_suffix.len()));
13053 } else {
13054 edits.push((prefix_range, empty_str.clone()));
13055 edits.push((suffix_range, empty_str.clone()));
13056 }
13057 } else {
13058 continue;
13059 }
13060 }
13061
13062 drop(snapshot);
13063 this.buffer.update(cx, |buffer, cx| {
13064 buffer.edit(edits, None, cx);
13065 });
13066
13067 // Adjust selections so that they end before any comment suffixes that
13068 // were inserted.
13069 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13070 let mut selections = this.selections.all::<Point>(cx);
13071 let snapshot = this.buffer.read(cx).read(cx);
13072 for selection in &mut selections {
13073 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13074 match row.cmp(&MultiBufferRow(selection.end.row)) {
13075 Ordering::Less => {
13076 suffixes_inserted.next();
13077 continue;
13078 }
13079 Ordering::Greater => break,
13080 Ordering::Equal => {
13081 if selection.end.column == snapshot.line_len(row) {
13082 if selection.is_empty() {
13083 selection.start.column -= suffix_len as u32;
13084 }
13085 selection.end.column -= suffix_len as u32;
13086 }
13087 break;
13088 }
13089 }
13090 }
13091 }
13092
13093 drop(snapshot);
13094 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13095 s.select(selections)
13096 });
13097
13098 let selections = this.selections.all::<Point>(cx);
13099 let selections_on_single_row = selections.windows(2).all(|selections| {
13100 selections[0].start.row == selections[1].start.row
13101 && selections[0].end.row == selections[1].end.row
13102 && selections[0].start.row == selections[0].end.row
13103 });
13104 let selections_selecting = selections
13105 .iter()
13106 .any(|selection| selection.start != selection.end);
13107 let advance_downwards = action.advance_downwards
13108 && selections_on_single_row
13109 && !selections_selecting
13110 && !matches!(this.mode, EditorMode::SingleLine { .. });
13111
13112 if advance_downwards {
13113 let snapshot = this.buffer.read(cx).snapshot(cx);
13114
13115 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13116 s.move_cursors_with(|display_snapshot, display_point, _| {
13117 let mut point = display_point.to_point(display_snapshot);
13118 point.row += 1;
13119 point = snapshot.clip_point(point, Bias::Left);
13120 let display_point = point.to_display_point(display_snapshot);
13121 let goal = SelectionGoal::HorizontalPosition(
13122 display_snapshot
13123 .x_for_display_point(display_point, text_layout_details)
13124 .into(),
13125 );
13126 (display_point, goal)
13127 })
13128 });
13129 }
13130 });
13131 }
13132
13133 pub fn select_enclosing_symbol(
13134 &mut self,
13135 _: &SelectEnclosingSymbol,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13140
13141 let buffer = self.buffer.read(cx).snapshot(cx);
13142 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13143
13144 fn update_selection(
13145 selection: &Selection<usize>,
13146 buffer_snap: &MultiBufferSnapshot,
13147 ) -> Option<Selection<usize>> {
13148 let cursor = selection.head();
13149 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13150 for symbol in symbols.iter().rev() {
13151 let start = symbol.range.start.to_offset(buffer_snap);
13152 let end = symbol.range.end.to_offset(buffer_snap);
13153 let new_range = start..end;
13154 if start < selection.start || end > selection.end {
13155 return Some(Selection {
13156 id: selection.id,
13157 start: new_range.start,
13158 end: new_range.end,
13159 goal: SelectionGoal::None,
13160 reversed: selection.reversed,
13161 });
13162 }
13163 }
13164 None
13165 }
13166
13167 let mut selected_larger_symbol = false;
13168 let new_selections = old_selections
13169 .iter()
13170 .map(|selection| match update_selection(selection, &buffer) {
13171 Some(new_selection) => {
13172 if new_selection.range() != selection.range() {
13173 selected_larger_symbol = true;
13174 }
13175 new_selection
13176 }
13177 None => selection.clone(),
13178 })
13179 .collect::<Vec<_>>();
13180
13181 if selected_larger_symbol {
13182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13183 s.select(new_selections);
13184 });
13185 }
13186 }
13187
13188 pub fn select_larger_syntax_node(
13189 &mut self,
13190 _: &SelectLargerSyntaxNode,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 let Some(visible_row_count) = self.visible_row_count() else {
13195 return;
13196 };
13197 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13198 if old_selections.is_empty() {
13199 return;
13200 }
13201
13202 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13203
13204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13205 let buffer = self.buffer.read(cx).snapshot(cx);
13206
13207 let mut selected_larger_node = false;
13208 let mut new_selections = old_selections
13209 .iter()
13210 .map(|selection| {
13211 let old_range = selection.start..selection.end;
13212
13213 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13214 // manually select word at selection
13215 if ["string_content", "inline"].contains(&node.kind()) {
13216 let word_range = {
13217 let display_point = buffer
13218 .offset_to_point(old_range.start)
13219 .to_display_point(&display_map);
13220 let Range { start, end } =
13221 movement::surrounding_word(&display_map, display_point);
13222 start.to_point(&display_map).to_offset(&buffer)
13223 ..end.to_point(&display_map).to_offset(&buffer)
13224 };
13225 // ignore if word is already selected
13226 if !word_range.is_empty() && old_range != word_range {
13227 let last_word_range = {
13228 let display_point = buffer
13229 .offset_to_point(old_range.end)
13230 .to_display_point(&display_map);
13231 let Range { start, end } =
13232 movement::surrounding_word(&display_map, display_point);
13233 start.to_point(&display_map).to_offset(&buffer)
13234 ..end.to_point(&display_map).to_offset(&buffer)
13235 };
13236 // only select word if start and end point belongs to same word
13237 if word_range == last_word_range {
13238 selected_larger_node = true;
13239 return Selection {
13240 id: selection.id,
13241 start: word_range.start,
13242 end: word_range.end,
13243 goal: SelectionGoal::None,
13244 reversed: selection.reversed,
13245 };
13246 }
13247 }
13248 }
13249 }
13250
13251 let mut new_range = old_range.clone();
13252 while let Some((_node, containing_range)) =
13253 buffer.syntax_ancestor(new_range.clone())
13254 {
13255 new_range = match containing_range {
13256 MultiOrSingleBufferOffsetRange::Single(_) => break,
13257 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13258 };
13259 if !display_map.intersects_fold(new_range.start)
13260 && !display_map.intersects_fold(new_range.end)
13261 {
13262 break;
13263 }
13264 }
13265
13266 selected_larger_node |= new_range != old_range;
13267 Selection {
13268 id: selection.id,
13269 start: new_range.start,
13270 end: new_range.end,
13271 goal: SelectionGoal::None,
13272 reversed: selection.reversed,
13273 }
13274 })
13275 .collect::<Vec<_>>();
13276
13277 if !selected_larger_node {
13278 return; // don't put this call in the history
13279 }
13280
13281 // scroll based on transformation done to the last selection created by the user
13282 let (last_old, last_new) = old_selections
13283 .last()
13284 .zip(new_selections.last().cloned())
13285 .expect("old_selections isn't empty");
13286
13287 // revert selection
13288 let is_selection_reversed = {
13289 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13290 new_selections.last_mut().expect("checked above").reversed =
13291 should_newest_selection_be_reversed;
13292 should_newest_selection_be_reversed
13293 };
13294
13295 if selected_larger_node {
13296 self.select_syntax_node_history.disable_clearing = true;
13297 self.change_selections(None, window, cx, |s| {
13298 s.select(new_selections.clone());
13299 });
13300 self.select_syntax_node_history.disable_clearing = false;
13301 }
13302
13303 let start_row = last_new.start.to_display_point(&display_map).row().0;
13304 let end_row = last_new.end.to_display_point(&display_map).row().0;
13305 let selection_height = end_row - start_row + 1;
13306 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13307
13308 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13309 let scroll_behavior = if fits_on_the_screen {
13310 self.request_autoscroll(Autoscroll::fit(), cx);
13311 SelectSyntaxNodeScrollBehavior::FitSelection
13312 } else if is_selection_reversed {
13313 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13314 SelectSyntaxNodeScrollBehavior::CursorTop
13315 } else {
13316 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13317 SelectSyntaxNodeScrollBehavior::CursorBottom
13318 };
13319
13320 self.select_syntax_node_history.push((
13321 old_selections,
13322 scroll_behavior,
13323 is_selection_reversed,
13324 ));
13325 }
13326
13327 pub fn select_smaller_syntax_node(
13328 &mut self,
13329 _: &SelectSmallerSyntaxNode,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13334
13335 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13336 self.select_syntax_node_history.pop()
13337 {
13338 if let Some(selection) = selections.last_mut() {
13339 selection.reversed = is_selection_reversed;
13340 }
13341
13342 self.select_syntax_node_history.disable_clearing = true;
13343 self.change_selections(None, window, cx, |s| {
13344 s.select(selections.to_vec());
13345 });
13346 self.select_syntax_node_history.disable_clearing = false;
13347
13348 match scroll_behavior {
13349 SelectSyntaxNodeScrollBehavior::CursorTop => {
13350 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13351 }
13352 SelectSyntaxNodeScrollBehavior::FitSelection => {
13353 self.request_autoscroll(Autoscroll::fit(), cx);
13354 }
13355 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13356 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13357 }
13358 }
13359 }
13360 }
13361
13362 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13363 if !EditorSettings::get_global(cx).gutter.runnables {
13364 self.clear_tasks();
13365 return Task::ready(());
13366 }
13367 let project = self.project.as_ref().map(Entity::downgrade);
13368 let task_sources = self.lsp_task_sources(cx);
13369 cx.spawn_in(window, async move |editor, cx| {
13370 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13371 let Some(project) = project.and_then(|p| p.upgrade()) else {
13372 return;
13373 };
13374 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13375 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13376 }) else {
13377 return;
13378 };
13379
13380 let hide_runnables = project
13381 .update(cx, |project, cx| {
13382 // Do not display any test indicators in non-dev server remote projects.
13383 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13384 })
13385 .unwrap_or(true);
13386 if hide_runnables {
13387 return;
13388 }
13389 let new_rows =
13390 cx.background_spawn({
13391 let snapshot = display_snapshot.clone();
13392 async move {
13393 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13394 }
13395 })
13396 .await;
13397 let Ok(lsp_tasks) =
13398 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13399 else {
13400 return;
13401 };
13402 let lsp_tasks = lsp_tasks.await;
13403
13404 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13405 lsp_tasks
13406 .into_iter()
13407 .flat_map(|(kind, tasks)| {
13408 tasks.into_iter().filter_map(move |(location, task)| {
13409 Some((kind.clone(), location?, task))
13410 })
13411 })
13412 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13413 let buffer = location.target.buffer;
13414 let buffer_snapshot = buffer.read(cx).snapshot();
13415 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13416 |(excerpt_id, snapshot, _)| {
13417 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13418 display_snapshot
13419 .buffer_snapshot
13420 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13421 } else {
13422 None
13423 }
13424 },
13425 );
13426 if let Some(offset) = offset {
13427 let task_buffer_range =
13428 location.target.range.to_point(&buffer_snapshot);
13429 let context_buffer_range =
13430 task_buffer_range.to_offset(&buffer_snapshot);
13431 let context_range = BufferOffset(context_buffer_range.start)
13432 ..BufferOffset(context_buffer_range.end);
13433
13434 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13435 .or_insert_with(|| RunnableTasks {
13436 templates: Vec::new(),
13437 offset,
13438 column: task_buffer_range.start.column,
13439 extra_variables: HashMap::default(),
13440 context_range,
13441 })
13442 .templates
13443 .push((kind, task.original_task().clone()));
13444 }
13445
13446 acc
13447 })
13448 }) else {
13449 return;
13450 };
13451
13452 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13453 editor
13454 .update(cx, |editor, _| {
13455 editor.clear_tasks();
13456 for (key, mut value) in rows {
13457 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13458 value.templates.extend(lsp_tasks.templates);
13459 }
13460
13461 editor.insert_tasks(key, value);
13462 }
13463 for (key, value) in lsp_tasks_by_rows {
13464 editor.insert_tasks(key, value);
13465 }
13466 })
13467 .ok();
13468 })
13469 }
13470 fn fetch_runnable_ranges(
13471 snapshot: &DisplaySnapshot,
13472 range: Range<Anchor>,
13473 ) -> Vec<language::RunnableRange> {
13474 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13475 }
13476
13477 fn runnable_rows(
13478 project: Entity<Project>,
13479 snapshot: DisplaySnapshot,
13480 runnable_ranges: Vec<RunnableRange>,
13481 mut cx: AsyncWindowContext,
13482 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13483 runnable_ranges
13484 .into_iter()
13485 .filter_map(|mut runnable| {
13486 let tasks = cx
13487 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13488 .ok()?;
13489 if tasks.is_empty() {
13490 return None;
13491 }
13492
13493 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13494
13495 let row = snapshot
13496 .buffer_snapshot
13497 .buffer_line_for_row(MultiBufferRow(point.row))?
13498 .1
13499 .start
13500 .row;
13501
13502 let context_range =
13503 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13504 Some((
13505 (runnable.buffer_id, row),
13506 RunnableTasks {
13507 templates: tasks,
13508 offset: snapshot
13509 .buffer_snapshot
13510 .anchor_before(runnable.run_range.start),
13511 context_range,
13512 column: point.column,
13513 extra_variables: runnable.extra_captures,
13514 },
13515 ))
13516 })
13517 .collect()
13518 }
13519
13520 fn templates_with_tags(
13521 project: &Entity<Project>,
13522 runnable: &mut Runnable,
13523 cx: &mut App,
13524 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13525 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13526 let (worktree_id, file) = project
13527 .buffer_for_id(runnable.buffer, cx)
13528 .and_then(|buffer| buffer.read(cx).file())
13529 .map(|file| (file.worktree_id(cx), file.clone()))
13530 .unzip();
13531
13532 (
13533 project.task_store().read(cx).task_inventory().cloned(),
13534 worktree_id,
13535 file,
13536 )
13537 });
13538
13539 let mut templates_with_tags = mem::take(&mut runnable.tags)
13540 .into_iter()
13541 .flat_map(|RunnableTag(tag)| {
13542 inventory
13543 .as_ref()
13544 .into_iter()
13545 .flat_map(|inventory| {
13546 inventory.read(cx).list_tasks(
13547 file.clone(),
13548 Some(runnable.language.clone()),
13549 worktree_id,
13550 cx,
13551 )
13552 })
13553 .filter(move |(_, template)| {
13554 template.tags.iter().any(|source_tag| source_tag == &tag)
13555 })
13556 })
13557 .sorted_by_key(|(kind, _)| kind.to_owned())
13558 .collect::<Vec<_>>();
13559 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13560 // Strongest source wins; if we have worktree tag binding, prefer that to
13561 // global and language bindings;
13562 // if we have a global binding, prefer that to language binding.
13563 let first_mismatch = templates_with_tags
13564 .iter()
13565 .position(|(tag_source, _)| tag_source != leading_tag_source);
13566 if let Some(index) = first_mismatch {
13567 templates_with_tags.truncate(index);
13568 }
13569 }
13570
13571 templates_with_tags
13572 }
13573
13574 pub fn move_to_enclosing_bracket(
13575 &mut self,
13576 _: &MoveToEnclosingBracket,
13577 window: &mut Window,
13578 cx: &mut Context<Self>,
13579 ) {
13580 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13581 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13582 s.move_offsets_with(|snapshot, selection| {
13583 let Some(enclosing_bracket_ranges) =
13584 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13585 else {
13586 return;
13587 };
13588
13589 let mut best_length = usize::MAX;
13590 let mut best_inside = false;
13591 let mut best_in_bracket_range = false;
13592 let mut best_destination = None;
13593 for (open, close) in enclosing_bracket_ranges {
13594 let close = close.to_inclusive();
13595 let length = close.end() - open.start;
13596 let inside = selection.start >= open.end && selection.end <= *close.start();
13597 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13598 || close.contains(&selection.head());
13599
13600 // If best is next to a bracket and current isn't, skip
13601 if !in_bracket_range && best_in_bracket_range {
13602 continue;
13603 }
13604
13605 // Prefer smaller lengths unless best is inside and current isn't
13606 if length > best_length && (best_inside || !inside) {
13607 continue;
13608 }
13609
13610 best_length = length;
13611 best_inside = inside;
13612 best_in_bracket_range = in_bracket_range;
13613 best_destination = Some(
13614 if close.contains(&selection.start) && close.contains(&selection.end) {
13615 if inside { open.end } else { open.start }
13616 } else if inside {
13617 *close.start()
13618 } else {
13619 *close.end()
13620 },
13621 );
13622 }
13623
13624 if let Some(destination) = best_destination {
13625 selection.collapse_to(destination, SelectionGoal::None);
13626 }
13627 })
13628 });
13629 }
13630
13631 pub fn undo_selection(
13632 &mut self,
13633 _: &UndoSelection,
13634 window: &mut Window,
13635 cx: &mut Context<Self>,
13636 ) {
13637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13638 self.end_selection(window, cx);
13639 self.selection_history.mode = SelectionHistoryMode::Undoing;
13640 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13641 self.change_selections(None, window, cx, |s| {
13642 s.select_anchors(entry.selections.to_vec())
13643 });
13644 self.select_next_state = entry.select_next_state;
13645 self.select_prev_state = entry.select_prev_state;
13646 self.add_selections_state = entry.add_selections_state;
13647 self.request_autoscroll(Autoscroll::newest(), cx);
13648 }
13649 self.selection_history.mode = SelectionHistoryMode::Normal;
13650 }
13651
13652 pub fn redo_selection(
13653 &mut self,
13654 _: &RedoSelection,
13655 window: &mut Window,
13656 cx: &mut Context<Self>,
13657 ) {
13658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13659 self.end_selection(window, cx);
13660 self.selection_history.mode = SelectionHistoryMode::Redoing;
13661 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13662 self.change_selections(None, window, cx, |s| {
13663 s.select_anchors(entry.selections.to_vec())
13664 });
13665 self.select_next_state = entry.select_next_state;
13666 self.select_prev_state = entry.select_prev_state;
13667 self.add_selections_state = entry.add_selections_state;
13668 self.request_autoscroll(Autoscroll::newest(), cx);
13669 }
13670 self.selection_history.mode = SelectionHistoryMode::Normal;
13671 }
13672
13673 pub fn expand_excerpts(
13674 &mut self,
13675 action: &ExpandExcerpts,
13676 _: &mut Window,
13677 cx: &mut Context<Self>,
13678 ) {
13679 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13680 }
13681
13682 pub fn expand_excerpts_down(
13683 &mut self,
13684 action: &ExpandExcerptsDown,
13685 _: &mut Window,
13686 cx: &mut Context<Self>,
13687 ) {
13688 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13689 }
13690
13691 pub fn expand_excerpts_up(
13692 &mut self,
13693 action: &ExpandExcerptsUp,
13694 _: &mut Window,
13695 cx: &mut Context<Self>,
13696 ) {
13697 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13698 }
13699
13700 pub fn expand_excerpts_for_direction(
13701 &mut self,
13702 lines: u32,
13703 direction: ExpandExcerptDirection,
13704
13705 cx: &mut Context<Self>,
13706 ) {
13707 let selections = self.selections.disjoint_anchors();
13708
13709 let lines = if lines == 0 {
13710 EditorSettings::get_global(cx).expand_excerpt_lines
13711 } else {
13712 lines
13713 };
13714
13715 self.buffer.update(cx, |buffer, cx| {
13716 let snapshot = buffer.snapshot(cx);
13717 let mut excerpt_ids = selections
13718 .iter()
13719 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13720 .collect::<Vec<_>>();
13721 excerpt_ids.sort();
13722 excerpt_ids.dedup();
13723 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13724 })
13725 }
13726
13727 pub fn expand_excerpt(
13728 &mut self,
13729 excerpt: ExcerptId,
13730 direction: ExpandExcerptDirection,
13731 window: &mut Window,
13732 cx: &mut Context<Self>,
13733 ) {
13734 let current_scroll_position = self.scroll_position(cx);
13735 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13736 let mut should_scroll_up = false;
13737
13738 if direction == ExpandExcerptDirection::Down {
13739 let multi_buffer = self.buffer.read(cx);
13740 let snapshot = multi_buffer.snapshot(cx);
13741 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13742 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13743 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13744 let buffer_snapshot = buffer.read(cx).snapshot();
13745 let excerpt_end_row =
13746 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13747 let last_row = buffer_snapshot.max_point().row;
13748 let lines_below = last_row.saturating_sub(excerpt_end_row);
13749 should_scroll_up = lines_below >= lines_to_expand;
13750 }
13751 }
13752 }
13753 }
13754
13755 self.buffer.update(cx, |buffer, cx| {
13756 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13757 });
13758
13759 if should_scroll_up {
13760 let new_scroll_position =
13761 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13762 self.set_scroll_position(new_scroll_position, window, cx);
13763 }
13764 }
13765
13766 pub fn go_to_singleton_buffer_point(
13767 &mut self,
13768 point: Point,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 self.go_to_singleton_buffer_range(point..point, window, cx);
13773 }
13774
13775 pub fn go_to_singleton_buffer_range(
13776 &mut self,
13777 range: Range<Point>,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 let multibuffer = self.buffer().read(cx);
13782 let Some(buffer) = multibuffer.as_singleton() else {
13783 return;
13784 };
13785 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13786 return;
13787 };
13788 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13789 return;
13790 };
13791 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13792 s.select_anchor_ranges([start..end])
13793 });
13794 }
13795
13796 pub fn go_to_diagnostic(
13797 &mut self,
13798 _: &GoToDiagnostic,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) {
13802 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13803 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13804 }
13805
13806 pub fn go_to_prev_diagnostic(
13807 &mut self,
13808 _: &GoToPreviousDiagnostic,
13809 window: &mut Window,
13810 cx: &mut Context<Self>,
13811 ) {
13812 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13813 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13814 }
13815
13816 pub fn go_to_diagnostic_impl(
13817 &mut self,
13818 direction: Direction,
13819 window: &mut Window,
13820 cx: &mut Context<Self>,
13821 ) {
13822 let buffer = self.buffer.read(cx).snapshot(cx);
13823 let selection = self.selections.newest::<usize>(cx);
13824
13825 let mut active_group_id = None;
13826 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13827 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13828 active_group_id = Some(active_group.group_id);
13829 }
13830 }
13831
13832 fn filtered(
13833 snapshot: EditorSnapshot,
13834 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13835 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13836 diagnostics
13837 .filter(|entry| entry.range.start != entry.range.end)
13838 .filter(|entry| !entry.diagnostic.is_unnecessary)
13839 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13840 }
13841
13842 let snapshot = self.snapshot(window, cx);
13843 let before = filtered(
13844 snapshot.clone(),
13845 buffer
13846 .diagnostics_in_range(0..selection.start)
13847 .filter(|entry| entry.range.start <= selection.start),
13848 );
13849 let after = filtered(
13850 snapshot,
13851 buffer
13852 .diagnostics_in_range(selection.start..buffer.len())
13853 .filter(|entry| entry.range.start >= selection.start),
13854 );
13855
13856 let mut found: Option<DiagnosticEntry<usize>> = None;
13857 if direction == Direction::Prev {
13858 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13859 {
13860 for diagnostic in prev_diagnostics.into_iter().rev() {
13861 if diagnostic.range.start != selection.start
13862 || active_group_id
13863 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13864 {
13865 found = Some(diagnostic);
13866 break 'outer;
13867 }
13868 }
13869 }
13870 } else {
13871 for diagnostic in after.chain(before) {
13872 if diagnostic.range.start != selection.start
13873 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13874 {
13875 found = Some(diagnostic);
13876 break;
13877 }
13878 }
13879 }
13880 let Some(next_diagnostic) = found else {
13881 return;
13882 };
13883
13884 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13885 return;
13886 };
13887 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13888 s.select_ranges(vec![
13889 next_diagnostic.range.start..next_diagnostic.range.start,
13890 ])
13891 });
13892 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13893 self.refresh_inline_completion(false, true, window, cx);
13894 }
13895
13896 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13897 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13898 let snapshot = self.snapshot(window, cx);
13899 let selection = self.selections.newest::<Point>(cx);
13900 self.go_to_hunk_before_or_after_position(
13901 &snapshot,
13902 selection.head(),
13903 Direction::Next,
13904 window,
13905 cx,
13906 );
13907 }
13908
13909 pub fn go_to_hunk_before_or_after_position(
13910 &mut self,
13911 snapshot: &EditorSnapshot,
13912 position: Point,
13913 direction: Direction,
13914 window: &mut Window,
13915 cx: &mut Context<Editor>,
13916 ) {
13917 let row = if direction == Direction::Next {
13918 self.hunk_after_position(snapshot, position)
13919 .map(|hunk| hunk.row_range.start)
13920 } else {
13921 self.hunk_before_position(snapshot, position)
13922 };
13923
13924 if let Some(row) = row {
13925 let destination = Point::new(row.0, 0);
13926 let autoscroll = Autoscroll::center();
13927
13928 self.unfold_ranges(&[destination..destination], false, false, cx);
13929 self.change_selections(Some(autoscroll), window, cx, |s| {
13930 s.select_ranges([destination..destination]);
13931 });
13932 }
13933 }
13934
13935 fn hunk_after_position(
13936 &mut self,
13937 snapshot: &EditorSnapshot,
13938 position: Point,
13939 ) -> Option<MultiBufferDiffHunk> {
13940 snapshot
13941 .buffer_snapshot
13942 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13943 .find(|hunk| hunk.row_range.start.0 > position.row)
13944 .or_else(|| {
13945 snapshot
13946 .buffer_snapshot
13947 .diff_hunks_in_range(Point::zero()..position)
13948 .find(|hunk| hunk.row_range.end.0 < position.row)
13949 })
13950 }
13951
13952 fn go_to_prev_hunk(
13953 &mut self,
13954 _: &GoToPreviousHunk,
13955 window: &mut Window,
13956 cx: &mut Context<Self>,
13957 ) {
13958 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13959 let snapshot = self.snapshot(window, cx);
13960 let selection = self.selections.newest::<Point>(cx);
13961 self.go_to_hunk_before_or_after_position(
13962 &snapshot,
13963 selection.head(),
13964 Direction::Prev,
13965 window,
13966 cx,
13967 );
13968 }
13969
13970 fn hunk_before_position(
13971 &mut self,
13972 snapshot: &EditorSnapshot,
13973 position: Point,
13974 ) -> Option<MultiBufferRow> {
13975 snapshot
13976 .buffer_snapshot
13977 .diff_hunk_before(position)
13978 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13979 }
13980
13981 fn go_to_next_change(
13982 &mut self,
13983 _: &GoToNextChange,
13984 window: &mut Window,
13985 cx: &mut Context<Self>,
13986 ) {
13987 if let Some(selections) = self
13988 .change_list
13989 .next_change(1, Direction::Next)
13990 .map(|s| s.to_vec())
13991 {
13992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13993 let map = s.display_map();
13994 s.select_display_ranges(selections.iter().map(|a| {
13995 let point = a.to_display_point(&map);
13996 point..point
13997 }))
13998 })
13999 }
14000 }
14001
14002 fn go_to_previous_change(
14003 &mut self,
14004 _: &GoToPreviousChange,
14005 window: &mut Window,
14006 cx: &mut Context<Self>,
14007 ) {
14008 if let Some(selections) = self
14009 .change_list
14010 .next_change(1, Direction::Prev)
14011 .map(|s| s.to_vec())
14012 {
14013 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14014 let map = s.display_map();
14015 s.select_display_ranges(selections.iter().map(|a| {
14016 let point = a.to_display_point(&map);
14017 point..point
14018 }))
14019 })
14020 }
14021 }
14022
14023 fn go_to_line<T: 'static>(
14024 &mut self,
14025 position: Anchor,
14026 highlight_color: Option<Hsla>,
14027 window: &mut Window,
14028 cx: &mut Context<Self>,
14029 ) {
14030 let snapshot = self.snapshot(window, cx).display_snapshot;
14031 let position = position.to_point(&snapshot.buffer_snapshot);
14032 let start = snapshot
14033 .buffer_snapshot
14034 .clip_point(Point::new(position.row, 0), Bias::Left);
14035 let end = start + Point::new(1, 0);
14036 let start = snapshot.buffer_snapshot.anchor_before(start);
14037 let end = snapshot.buffer_snapshot.anchor_before(end);
14038
14039 self.highlight_rows::<T>(
14040 start..end,
14041 highlight_color
14042 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14043 Default::default(),
14044 cx,
14045 );
14046
14047 if self.buffer.read(cx).is_singleton() {
14048 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14049 }
14050 }
14051
14052 pub fn go_to_definition(
14053 &mut self,
14054 _: &GoToDefinition,
14055 window: &mut Window,
14056 cx: &mut Context<Self>,
14057 ) -> Task<Result<Navigated>> {
14058 let definition =
14059 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14060 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14061 cx.spawn_in(window, async move |editor, cx| {
14062 if definition.await? == Navigated::Yes {
14063 return Ok(Navigated::Yes);
14064 }
14065 match fallback_strategy {
14066 GoToDefinitionFallback::None => Ok(Navigated::No),
14067 GoToDefinitionFallback::FindAllReferences => {
14068 match editor.update_in(cx, |editor, window, cx| {
14069 editor.find_all_references(&FindAllReferences, window, cx)
14070 })? {
14071 Some(references) => references.await,
14072 None => Ok(Navigated::No),
14073 }
14074 }
14075 }
14076 })
14077 }
14078
14079 pub fn go_to_declaration(
14080 &mut self,
14081 _: &GoToDeclaration,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) -> Task<Result<Navigated>> {
14085 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14086 }
14087
14088 pub fn go_to_declaration_split(
14089 &mut self,
14090 _: &GoToDeclaration,
14091 window: &mut Window,
14092 cx: &mut Context<Self>,
14093 ) -> Task<Result<Navigated>> {
14094 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14095 }
14096
14097 pub fn go_to_implementation(
14098 &mut self,
14099 _: &GoToImplementation,
14100 window: &mut Window,
14101 cx: &mut Context<Self>,
14102 ) -> Task<Result<Navigated>> {
14103 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14104 }
14105
14106 pub fn go_to_implementation_split(
14107 &mut self,
14108 _: &GoToImplementationSplit,
14109 window: &mut Window,
14110 cx: &mut Context<Self>,
14111 ) -> Task<Result<Navigated>> {
14112 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14113 }
14114
14115 pub fn go_to_type_definition(
14116 &mut self,
14117 _: &GoToTypeDefinition,
14118 window: &mut Window,
14119 cx: &mut Context<Self>,
14120 ) -> Task<Result<Navigated>> {
14121 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14122 }
14123
14124 pub fn go_to_definition_split(
14125 &mut self,
14126 _: &GoToDefinitionSplit,
14127 window: &mut Window,
14128 cx: &mut Context<Self>,
14129 ) -> Task<Result<Navigated>> {
14130 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14131 }
14132
14133 pub fn go_to_type_definition_split(
14134 &mut self,
14135 _: &GoToTypeDefinitionSplit,
14136 window: &mut Window,
14137 cx: &mut Context<Self>,
14138 ) -> Task<Result<Navigated>> {
14139 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14140 }
14141
14142 fn go_to_definition_of_kind(
14143 &mut self,
14144 kind: GotoDefinitionKind,
14145 split: bool,
14146 window: &mut Window,
14147 cx: &mut Context<Self>,
14148 ) -> Task<Result<Navigated>> {
14149 let Some(provider) = self.semantics_provider.clone() else {
14150 return Task::ready(Ok(Navigated::No));
14151 };
14152 let head = self.selections.newest::<usize>(cx).head();
14153 let buffer = self.buffer.read(cx);
14154 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14155 text_anchor
14156 } else {
14157 return Task::ready(Ok(Navigated::No));
14158 };
14159
14160 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14161 return Task::ready(Ok(Navigated::No));
14162 };
14163
14164 cx.spawn_in(window, async move |editor, cx| {
14165 let definitions = definitions.await?;
14166 let navigated = editor
14167 .update_in(cx, |editor, window, cx| {
14168 editor.navigate_to_hover_links(
14169 Some(kind),
14170 definitions
14171 .into_iter()
14172 .filter(|location| {
14173 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14174 })
14175 .map(HoverLink::Text)
14176 .collect::<Vec<_>>(),
14177 split,
14178 window,
14179 cx,
14180 )
14181 })?
14182 .await?;
14183 anyhow::Ok(navigated)
14184 })
14185 }
14186
14187 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14188 let selection = self.selections.newest_anchor();
14189 let head = selection.head();
14190 let tail = selection.tail();
14191
14192 let Some((buffer, start_position)) =
14193 self.buffer.read(cx).text_anchor_for_position(head, cx)
14194 else {
14195 return;
14196 };
14197
14198 let end_position = if head != tail {
14199 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14200 return;
14201 };
14202 Some(pos)
14203 } else {
14204 None
14205 };
14206
14207 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14208 let url = if let Some(end_pos) = end_position {
14209 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14210 } else {
14211 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14212 };
14213
14214 if let Some(url) = url {
14215 editor.update(cx, |_, cx| {
14216 cx.open_url(&url);
14217 })
14218 } else {
14219 Ok(())
14220 }
14221 });
14222
14223 url_finder.detach();
14224 }
14225
14226 pub fn open_selected_filename(
14227 &mut self,
14228 _: &OpenSelectedFilename,
14229 window: &mut Window,
14230 cx: &mut Context<Self>,
14231 ) {
14232 let Some(workspace) = self.workspace() else {
14233 return;
14234 };
14235
14236 let position = self.selections.newest_anchor().head();
14237
14238 let Some((buffer, buffer_position)) =
14239 self.buffer.read(cx).text_anchor_for_position(position, cx)
14240 else {
14241 return;
14242 };
14243
14244 let project = self.project.clone();
14245
14246 cx.spawn_in(window, async move |_, cx| {
14247 let result = find_file(&buffer, project, buffer_position, cx).await;
14248
14249 if let Some((_, path)) = result {
14250 workspace
14251 .update_in(cx, |workspace, window, cx| {
14252 workspace.open_resolved_path(path, window, cx)
14253 })?
14254 .await?;
14255 }
14256 anyhow::Ok(())
14257 })
14258 .detach();
14259 }
14260
14261 pub(crate) fn navigate_to_hover_links(
14262 &mut self,
14263 kind: Option<GotoDefinitionKind>,
14264 mut definitions: Vec<HoverLink>,
14265 split: bool,
14266 window: &mut Window,
14267 cx: &mut Context<Editor>,
14268 ) -> Task<Result<Navigated>> {
14269 // If there is one definition, just open it directly
14270 if definitions.len() == 1 {
14271 let definition = definitions.pop().unwrap();
14272
14273 enum TargetTaskResult {
14274 Location(Option<Location>),
14275 AlreadyNavigated,
14276 }
14277
14278 let target_task = match definition {
14279 HoverLink::Text(link) => {
14280 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14281 }
14282 HoverLink::InlayHint(lsp_location, server_id) => {
14283 let computation =
14284 self.compute_target_location(lsp_location, server_id, window, cx);
14285 cx.background_spawn(async move {
14286 let location = computation.await?;
14287 Ok(TargetTaskResult::Location(location))
14288 })
14289 }
14290 HoverLink::Url(url) => {
14291 cx.open_url(&url);
14292 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14293 }
14294 HoverLink::File(path) => {
14295 if let Some(workspace) = self.workspace() {
14296 cx.spawn_in(window, async move |_, cx| {
14297 workspace
14298 .update_in(cx, |workspace, window, cx| {
14299 workspace.open_resolved_path(path, window, cx)
14300 })?
14301 .await
14302 .map(|_| TargetTaskResult::AlreadyNavigated)
14303 })
14304 } else {
14305 Task::ready(Ok(TargetTaskResult::Location(None)))
14306 }
14307 }
14308 };
14309 cx.spawn_in(window, async move |editor, cx| {
14310 let target = match target_task.await.context("target resolution task")? {
14311 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14312 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14313 TargetTaskResult::Location(Some(target)) => target,
14314 };
14315
14316 editor.update_in(cx, |editor, window, cx| {
14317 let Some(workspace) = editor.workspace() else {
14318 return Navigated::No;
14319 };
14320 let pane = workspace.read(cx).active_pane().clone();
14321
14322 let range = target.range.to_point(target.buffer.read(cx));
14323 let range = editor.range_for_match(&range);
14324 let range = collapse_multiline_range(range);
14325
14326 if !split
14327 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14328 {
14329 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14330 } else {
14331 window.defer(cx, move |window, cx| {
14332 let target_editor: Entity<Self> =
14333 workspace.update(cx, |workspace, cx| {
14334 let pane = if split {
14335 workspace.adjacent_pane(window, cx)
14336 } else {
14337 workspace.active_pane().clone()
14338 };
14339
14340 workspace.open_project_item(
14341 pane,
14342 target.buffer.clone(),
14343 true,
14344 true,
14345 window,
14346 cx,
14347 )
14348 });
14349 target_editor.update(cx, |target_editor, cx| {
14350 // When selecting a definition in a different buffer, disable the nav history
14351 // to avoid creating a history entry at the previous cursor location.
14352 pane.update(cx, |pane, _| pane.disable_history());
14353 target_editor.go_to_singleton_buffer_range(range, window, cx);
14354 pane.update(cx, |pane, _| pane.enable_history());
14355 });
14356 });
14357 }
14358 Navigated::Yes
14359 })
14360 })
14361 } else if !definitions.is_empty() {
14362 cx.spawn_in(window, async move |editor, cx| {
14363 let (title, location_tasks, workspace) = editor
14364 .update_in(cx, |editor, window, cx| {
14365 let tab_kind = match kind {
14366 Some(GotoDefinitionKind::Implementation) => "Implementations",
14367 _ => "Definitions",
14368 };
14369 let title = definitions
14370 .iter()
14371 .find_map(|definition| match definition {
14372 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14373 let buffer = origin.buffer.read(cx);
14374 format!(
14375 "{} for {}",
14376 tab_kind,
14377 buffer
14378 .text_for_range(origin.range.clone())
14379 .collect::<String>()
14380 )
14381 }),
14382 HoverLink::InlayHint(_, _) => None,
14383 HoverLink::Url(_) => None,
14384 HoverLink::File(_) => None,
14385 })
14386 .unwrap_or(tab_kind.to_string());
14387 let location_tasks = definitions
14388 .into_iter()
14389 .map(|definition| match definition {
14390 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14391 HoverLink::InlayHint(lsp_location, server_id) => editor
14392 .compute_target_location(lsp_location, server_id, window, cx),
14393 HoverLink::Url(_) => Task::ready(Ok(None)),
14394 HoverLink::File(_) => Task::ready(Ok(None)),
14395 })
14396 .collect::<Vec<_>>();
14397 (title, location_tasks, editor.workspace().clone())
14398 })
14399 .context("location tasks preparation")?;
14400
14401 let locations = future::join_all(location_tasks)
14402 .await
14403 .into_iter()
14404 .filter_map(|location| location.transpose())
14405 .collect::<Result<_>>()
14406 .context("location tasks")?;
14407
14408 let Some(workspace) = workspace else {
14409 return Ok(Navigated::No);
14410 };
14411 let opened = workspace
14412 .update_in(cx, |workspace, window, cx| {
14413 Self::open_locations_in_multibuffer(
14414 workspace,
14415 locations,
14416 title,
14417 split,
14418 MultibufferSelectionMode::First,
14419 window,
14420 cx,
14421 )
14422 })
14423 .ok();
14424
14425 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14426 })
14427 } else {
14428 Task::ready(Ok(Navigated::No))
14429 }
14430 }
14431
14432 fn compute_target_location(
14433 &self,
14434 lsp_location: lsp::Location,
14435 server_id: LanguageServerId,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) -> Task<anyhow::Result<Option<Location>>> {
14439 let Some(project) = self.project.clone() else {
14440 return Task::ready(Ok(None));
14441 };
14442
14443 cx.spawn_in(window, async move |editor, cx| {
14444 let location_task = editor.update(cx, |_, cx| {
14445 project.update(cx, |project, cx| {
14446 let language_server_name = project
14447 .language_server_statuses(cx)
14448 .find(|(id, _)| server_id == *id)
14449 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14450 language_server_name.map(|language_server_name| {
14451 project.open_local_buffer_via_lsp(
14452 lsp_location.uri.clone(),
14453 server_id,
14454 language_server_name,
14455 cx,
14456 )
14457 })
14458 })
14459 })?;
14460 let location = match location_task {
14461 Some(task) => Some({
14462 let target_buffer_handle = task.await.context("open local buffer")?;
14463 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14464 let target_start = target_buffer
14465 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14466 let target_end = target_buffer
14467 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14468 target_buffer.anchor_after(target_start)
14469 ..target_buffer.anchor_before(target_end)
14470 })?;
14471 Location {
14472 buffer: target_buffer_handle,
14473 range,
14474 }
14475 }),
14476 None => None,
14477 };
14478 Ok(location)
14479 })
14480 }
14481
14482 pub fn find_all_references(
14483 &mut self,
14484 _: &FindAllReferences,
14485 window: &mut Window,
14486 cx: &mut Context<Self>,
14487 ) -> Option<Task<Result<Navigated>>> {
14488 let selection = self.selections.newest::<usize>(cx);
14489 let multi_buffer = self.buffer.read(cx);
14490 let head = selection.head();
14491
14492 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14493 let head_anchor = multi_buffer_snapshot.anchor_at(
14494 head,
14495 if head < selection.tail() {
14496 Bias::Right
14497 } else {
14498 Bias::Left
14499 },
14500 );
14501
14502 match self
14503 .find_all_references_task_sources
14504 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14505 {
14506 Ok(_) => {
14507 log::info!(
14508 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14509 );
14510 return None;
14511 }
14512 Err(i) => {
14513 self.find_all_references_task_sources.insert(i, head_anchor);
14514 }
14515 }
14516
14517 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14518 let workspace = self.workspace()?;
14519 let project = workspace.read(cx).project().clone();
14520 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14521 Some(cx.spawn_in(window, async move |editor, cx| {
14522 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14523 if let Ok(i) = editor
14524 .find_all_references_task_sources
14525 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14526 {
14527 editor.find_all_references_task_sources.remove(i);
14528 }
14529 });
14530
14531 let locations = references.await?;
14532 if locations.is_empty() {
14533 return anyhow::Ok(Navigated::No);
14534 }
14535
14536 workspace.update_in(cx, |workspace, window, cx| {
14537 let title = locations
14538 .first()
14539 .as_ref()
14540 .map(|location| {
14541 let buffer = location.buffer.read(cx);
14542 format!(
14543 "References to `{}`",
14544 buffer
14545 .text_for_range(location.range.clone())
14546 .collect::<String>()
14547 )
14548 })
14549 .unwrap();
14550 Self::open_locations_in_multibuffer(
14551 workspace,
14552 locations,
14553 title,
14554 false,
14555 MultibufferSelectionMode::First,
14556 window,
14557 cx,
14558 );
14559 Navigated::Yes
14560 })
14561 }))
14562 }
14563
14564 /// Opens a multibuffer with the given project locations in it
14565 pub fn open_locations_in_multibuffer(
14566 workspace: &mut Workspace,
14567 mut locations: Vec<Location>,
14568 title: String,
14569 split: bool,
14570 multibuffer_selection_mode: MultibufferSelectionMode,
14571 window: &mut Window,
14572 cx: &mut Context<Workspace>,
14573 ) {
14574 // If there are multiple definitions, open them in a multibuffer
14575 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14576 let mut locations = locations.into_iter().peekable();
14577 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14578 let capability = workspace.project().read(cx).capability();
14579
14580 let excerpt_buffer = cx.new(|cx| {
14581 let mut multibuffer = MultiBuffer::new(capability);
14582 while let Some(location) = locations.next() {
14583 let buffer = location.buffer.read(cx);
14584 let mut ranges_for_buffer = Vec::new();
14585 let range = location.range.to_point(buffer);
14586 ranges_for_buffer.push(range.clone());
14587
14588 while let Some(next_location) = locations.peek() {
14589 if next_location.buffer == location.buffer {
14590 ranges_for_buffer.push(next_location.range.to_point(buffer));
14591 locations.next();
14592 } else {
14593 break;
14594 }
14595 }
14596
14597 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14598 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14599 PathKey::for_buffer(&location.buffer, cx),
14600 location.buffer.clone(),
14601 ranges_for_buffer,
14602 DEFAULT_MULTIBUFFER_CONTEXT,
14603 cx,
14604 );
14605 ranges.extend(new_ranges)
14606 }
14607
14608 multibuffer.with_title(title)
14609 });
14610
14611 let editor = cx.new(|cx| {
14612 Editor::for_multibuffer(
14613 excerpt_buffer,
14614 Some(workspace.project().clone()),
14615 window,
14616 cx,
14617 )
14618 });
14619 editor.update(cx, |editor, cx| {
14620 match multibuffer_selection_mode {
14621 MultibufferSelectionMode::First => {
14622 if let Some(first_range) = ranges.first() {
14623 editor.change_selections(None, window, cx, |selections| {
14624 selections.clear_disjoint();
14625 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14626 });
14627 }
14628 editor.highlight_background::<Self>(
14629 &ranges,
14630 |theme| theme.editor_highlighted_line_background,
14631 cx,
14632 );
14633 }
14634 MultibufferSelectionMode::All => {
14635 editor.change_selections(None, window, cx, |selections| {
14636 selections.clear_disjoint();
14637 selections.select_anchor_ranges(ranges);
14638 });
14639 }
14640 }
14641 editor.register_buffers_with_language_servers(cx);
14642 });
14643
14644 let item = Box::new(editor);
14645 let item_id = item.item_id();
14646
14647 if split {
14648 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14649 } else {
14650 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14651 let (preview_item_id, preview_item_idx) =
14652 workspace.active_pane().update(cx, |pane, _| {
14653 (pane.preview_item_id(), pane.preview_item_idx())
14654 });
14655
14656 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14657
14658 if let Some(preview_item_id) = preview_item_id {
14659 workspace.active_pane().update(cx, |pane, cx| {
14660 pane.remove_item(preview_item_id, false, false, window, cx);
14661 });
14662 }
14663 } else {
14664 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14665 }
14666 }
14667 workspace.active_pane().update(cx, |pane, cx| {
14668 pane.set_preview_item_id(Some(item_id), cx);
14669 });
14670 }
14671
14672 pub fn rename(
14673 &mut self,
14674 _: &Rename,
14675 window: &mut Window,
14676 cx: &mut Context<Self>,
14677 ) -> Option<Task<Result<()>>> {
14678 use language::ToOffset as _;
14679
14680 let provider = self.semantics_provider.clone()?;
14681 let selection = self.selections.newest_anchor().clone();
14682 let (cursor_buffer, cursor_buffer_position) = self
14683 .buffer
14684 .read(cx)
14685 .text_anchor_for_position(selection.head(), cx)?;
14686 let (tail_buffer, cursor_buffer_position_end) = self
14687 .buffer
14688 .read(cx)
14689 .text_anchor_for_position(selection.tail(), cx)?;
14690 if tail_buffer != cursor_buffer {
14691 return None;
14692 }
14693
14694 let snapshot = cursor_buffer.read(cx).snapshot();
14695 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14696 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14697 let prepare_rename = provider
14698 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14699 .unwrap_or_else(|| Task::ready(Ok(None)));
14700 drop(snapshot);
14701
14702 Some(cx.spawn_in(window, async move |this, cx| {
14703 let rename_range = if let Some(range) = prepare_rename.await? {
14704 Some(range)
14705 } else {
14706 this.update(cx, |this, cx| {
14707 let buffer = this.buffer.read(cx).snapshot(cx);
14708 let mut buffer_highlights = this
14709 .document_highlights_for_position(selection.head(), &buffer)
14710 .filter(|highlight| {
14711 highlight.start.excerpt_id == selection.head().excerpt_id
14712 && highlight.end.excerpt_id == selection.head().excerpt_id
14713 });
14714 buffer_highlights
14715 .next()
14716 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14717 })?
14718 };
14719 if let Some(rename_range) = rename_range {
14720 this.update_in(cx, |this, window, cx| {
14721 let snapshot = cursor_buffer.read(cx).snapshot();
14722 let rename_buffer_range = rename_range.to_offset(&snapshot);
14723 let cursor_offset_in_rename_range =
14724 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14725 let cursor_offset_in_rename_range_end =
14726 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14727
14728 this.take_rename(false, window, cx);
14729 let buffer = this.buffer.read(cx).read(cx);
14730 let cursor_offset = selection.head().to_offset(&buffer);
14731 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14732 let rename_end = rename_start + rename_buffer_range.len();
14733 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14734 let mut old_highlight_id = None;
14735 let old_name: Arc<str> = buffer
14736 .chunks(rename_start..rename_end, true)
14737 .map(|chunk| {
14738 if old_highlight_id.is_none() {
14739 old_highlight_id = chunk.syntax_highlight_id;
14740 }
14741 chunk.text
14742 })
14743 .collect::<String>()
14744 .into();
14745
14746 drop(buffer);
14747
14748 // Position the selection in the rename editor so that it matches the current selection.
14749 this.show_local_selections = false;
14750 let rename_editor = cx.new(|cx| {
14751 let mut editor = Editor::single_line(window, cx);
14752 editor.buffer.update(cx, |buffer, cx| {
14753 buffer.edit([(0..0, old_name.clone())], None, cx)
14754 });
14755 let rename_selection_range = match cursor_offset_in_rename_range
14756 .cmp(&cursor_offset_in_rename_range_end)
14757 {
14758 Ordering::Equal => {
14759 editor.select_all(&SelectAll, window, cx);
14760 return editor;
14761 }
14762 Ordering::Less => {
14763 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14764 }
14765 Ordering::Greater => {
14766 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14767 }
14768 };
14769 if rename_selection_range.end > old_name.len() {
14770 editor.select_all(&SelectAll, window, cx);
14771 } else {
14772 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14773 s.select_ranges([rename_selection_range]);
14774 });
14775 }
14776 editor
14777 });
14778 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14779 if e == &EditorEvent::Focused {
14780 cx.emit(EditorEvent::FocusedIn)
14781 }
14782 })
14783 .detach();
14784
14785 let write_highlights =
14786 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14787 let read_highlights =
14788 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14789 let ranges = write_highlights
14790 .iter()
14791 .flat_map(|(_, ranges)| ranges.iter())
14792 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14793 .cloned()
14794 .collect();
14795
14796 this.highlight_text::<Rename>(
14797 ranges,
14798 HighlightStyle {
14799 fade_out: Some(0.6),
14800 ..Default::default()
14801 },
14802 cx,
14803 );
14804 let rename_focus_handle = rename_editor.focus_handle(cx);
14805 window.focus(&rename_focus_handle);
14806 let block_id = this.insert_blocks(
14807 [BlockProperties {
14808 style: BlockStyle::Flex,
14809 placement: BlockPlacement::Below(range.start),
14810 height: Some(1),
14811 render: Arc::new({
14812 let rename_editor = rename_editor.clone();
14813 move |cx: &mut BlockContext| {
14814 let mut text_style = cx.editor_style.text.clone();
14815 if let Some(highlight_style) = old_highlight_id
14816 .and_then(|h| h.style(&cx.editor_style.syntax))
14817 {
14818 text_style = text_style.highlight(highlight_style);
14819 }
14820 div()
14821 .block_mouse_down()
14822 .pl(cx.anchor_x)
14823 .child(EditorElement::new(
14824 &rename_editor,
14825 EditorStyle {
14826 background: cx.theme().system().transparent,
14827 local_player: cx.editor_style.local_player,
14828 text: text_style,
14829 scrollbar_width: cx.editor_style.scrollbar_width,
14830 syntax: cx.editor_style.syntax.clone(),
14831 status: cx.editor_style.status.clone(),
14832 inlay_hints_style: HighlightStyle {
14833 font_weight: Some(FontWeight::BOLD),
14834 ..make_inlay_hints_style(cx.app)
14835 },
14836 inline_completion_styles: make_suggestion_styles(
14837 cx.app,
14838 ),
14839 ..EditorStyle::default()
14840 },
14841 ))
14842 .into_any_element()
14843 }
14844 }),
14845 priority: 0,
14846 render_in_minimap: true,
14847 }],
14848 Some(Autoscroll::fit()),
14849 cx,
14850 )[0];
14851 this.pending_rename = Some(RenameState {
14852 range,
14853 old_name,
14854 editor: rename_editor,
14855 block_id,
14856 });
14857 })?;
14858 }
14859
14860 Ok(())
14861 }))
14862 }
14863
14864 pub fn confirm_rename(
14865 &mut self,
14866 _: &ConfirmRename,
14867 window: &mut Window,
14868 cx: &mut Context<Self>,
14869 ) -> Option<Task<Result<()>>> {
14870 let rename = self.take_rename(false, window, cx)?;
14871 let workspace = self.workspace()?.downgrade();
14872 let (buffer, start) = self
14873 .buffer
14874 .read(cx)
14875 .text_anchor_for_position(rename.range.start, cx)?;
14876 let (end_buffer, _) = self
14877 .buffer
14878 .read(cx)
14879 .text_anchor_for_position(rename.range.end, cx)?;
14880 if buffer != end_buffer {
14881 return None;
14882 }
14883
14884 let old_name = rename.old_name;
14885 let new_name = rename.editor.read(cx).text(cx);
14886
14887 let rename = self.semantics_provider.as_ref()?.perform_rename(
14888 &buffer,
14889 start,
14890 new_name.clone(),
14891 cx,
14892 )?;
14893
14894 Some(cx.spawn_in(window, async move |editor, cx| {
14895 let project_transaction = rename.await?;
14896 Self::open_project_transaction(
14897 &editor,
14898 workspace,
14899 project_transaction,
14900 format!("Rename: {} → {}", old_name, new_name),
14901 cx,
14902 )
14903 .await?;
14904
14905 editor.update(cx, |editor, cx| {
14906 editor.refresh_document_highlights(cx);
14907 })?;
14908 Ok(())
14909 }))
14910 }
14911
14912 fn take_rename(
14913 &mut self,
14914 moving_cursor: bool,
14915 window: &mut Window,
14916 cx: &mut Context<Self>,
14917 ) -> Option<RenameState> {
14918 let rename = self.pending_rename.take()?;
14919 if rename.editor.focus_handle(cx).is_focused(window) {
14920 window.focus(&self.focus_handle);
14921 }
14922
14923 self.remove_blocks(
14924 [rename.block_id].into_iter().collect(),
14925 Some(Autoscroll::fit()),
14926 cx,
14927 );
14928 self.clear_highlights::<Rename>(cx);
14929 self.show_local_selections = true;
14930
14931 if moving_cursor {
14932 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14933 editor.selections.newest::<usize>(cx).head()
14934 });
14935
14936 // Update the selection to match the position of the selection inside
14937 // the rename editor.
14938 let snapshot = self.buffer.read(cx).read(cx);
14939 let rename_range = rename.range.to_offset(&snapshot);
14940 let cursor_in_editor = snapshot
14941 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14942 .min(rename_range.end);
14943 drop(snapshot);
14944
14945 self.change_selections(None, window, cx, |s| {
14946 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14947 });
14948 } else {
14949 self.refresh_document_highlights(cx);
14950 }
14951
14952 Some(rename)
14953 }
14954
14955 pub fn pending_rename(&self) -> Option<&RenameState> {
14956 self.pending_rename.as_ref()
14957 }
14958
14959 fn format(
14960 &mut self,
14961 _: &Format,
14962 window: &mut Window,
14963 cx: &mut Context<Self>,
14964 ) -> Option<Task<Result<()>>> {
14965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14966
14967 let project = match &self.project {
14968 Some(project) => project.clone(),
14969 None => return None,
14970 };
14971
14972 Some(self.perform_format(
14973 project,
14974 FormatTrigger::Manual,
14975 FormatTarget::Buffers,
14976 window,
14977 cx,
14978 ))
14979 }
14980
14981 fn format_selections(
14982 &mut self,
14983 _: &FormatSelections,
14984 window: &mut Window,
14985 cx: &mut Context<Self>,
14986 ) -> Option<Task<Result<()>>> {
14987 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14988
14989 let project = match &self.project {
14990 Some(project) => project.clone(),
14991 None => return None,
14992 };
14993
14994 let ranges = self
14995 .selections
14996 .all_adjusted(cx)
14997 .into_iter()
14998 .map(|selection| selection.range())
14999 .collect_vec();
15000
15001 Some(self.perform_format(
15002 project,
15003 FormatTrigger::Manual,
15004 FormatTarget::Ranges(ranges),
15005 window,
15006 cx,
15007 ))
15008 }
15009
15010 fn perform_format(
15011 &mut self,
15012 project: Entity<Project>,
15013 trigger: FormatTrigger,
15014 target: FormatTarget,
15015 window: &mut Window,
15016 cx: &mut Context<Self>,
15017 ) -> Task<Result<()>> {
15018 let buffer = self.buffer.clone();
15019 let (buffers, target) = match target {
15020 FormatTarget::Buffers => {
15021 let mut buffers = buffer.read(cx).all_buffers();
15022 if trigger == FormatTrigger::Save {
15023 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15024 }
15025 (buffers, LspFormatTarget::Buffers)
15026 }
15027 FormatTarget::Ranges(selection_ranges) => {
15028 let multi_buffer = buffer.read(cx);
15029 let snapshot = multi_buffer.read(cx);
15030 let mut buffers = HashSet::default();
15031 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15032 BTreeMap::new();
15033 for selection_range in selection_ranges {
15034 for (buffer, buffer_range, _) in
15035 snapshot.range_to_buffer_ranges(selection_range)
15036 {
15037 let buffer_id = buffer.remote_id();
15038 let start = buffer.anchor_before(buffer_range.start);
15039 let end = buffer.anchor_after(buffer_range.end);
15040 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15041 buffer_id_to_ranges
15042 .entry(buffer_id)
15043 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15044 .or_insert_with(|| vec![start..end]);
15045 }
15046 }
15047 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15048 }
15049 };
15050
15051 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15052 let selections_prev = transaction_id_prev
15053 .and_then(|transaction_id_prev| {
15054 // default to selections as they were after the last edit, if we have them,
15055 // instead of how they are now.
15056 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15057 // will take you back to where you made the last edit, instead of staying where you scrolled
15058 self.selection_history
15059 .transaction(transaction_id_prev)
15060 .map(|t| t.0.clone())
15061 })
15062 .unwrap_or_else(|| {
15063 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15064 self.selections.disjoint_anchors()
15065 });
15066
15067 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15068 let format = project.update(cx, |project, cx| {
15069 project.format(buffers, target, true, trigger, cx)
15070 });
15071
15072 cx.spawn_in(window, async move |editor, cx| {
15073 let transaction = futures::select_biased! {
15074 transaction = format.log_err().fuse() => transaction,
15075 () = timeout => {
15076 log::warn!("timed out waiting for formatting");
15077 None
15078 }
15079 };
15080
15081 buffer
15082 .update(cx, |buffer, cx| {
15083 if let Some(transaction) = transaction {
15084 if !buffer.is_singleton() {
15085 buffer.push_transaction(&transaction.0, cx);
15086 }
15087 }
15088 cx.notify();
15089 })
15090 .ok();
15091
15092 if let Some(transaction_id_now) =
15093 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15094 {
15095 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15096 if has_new_transaction {
15097 _ = editor.update(cx, |editor, _| {
15098 editor
15099 .selection_history
15100 .insert_transaction(transaction_id_now, selections_prev);
15101 });
15102 }
15103 }
15104
15105 Ok(())
15106 })
15107 }
15108
15109 fn organize_imports(
15110 &mut self,
15111 _: &OrganizeImports,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) -> Option<Task<Result<()>>> {
15115 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15116 let project = match &self.project {
15117 Some(project) => project.clone(),
15118 None => return None,
15119 };
15120 Some(self.perform_code_action_kind(
15121 project,
15122 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15123 window,
15124 cx,
15125 ))
15126 }
15127
15128 fn perform_code_action_kind(
15129 &mut self,
15130 project: Entity<Project>,
15131 kind: CodeActionKind,
15132 window: &mut Window,
15133 cx: &mut Context<Self>,
15134 ) -> Task<Result<()>> {
15135 let buffer = self.buffer.clone();
15136 let buffers = buffer.read(cx).all_buffers();
15137 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15138 let apply_action = project.update(cx, |project, cx| {
15139 project.apply_code_action_kind(buffers, kind, true, cx)
15140 });
15141 cx.spawn_in(window, async move |_, cx| {
15142 let transaction = futures::select_biased! {
15143 () = timeout => {
15144 log::warn!("timed out waiting for executing code action");
15145 None
15146 }
15147 transaction = apply_action.log_err().fuse() => transaction,
15148 };
15149 buffer
15150 .update(cx, |buffer, cx| {
15151 // check if we need this
15152 if let Some(transaction) = transaction {
15153 if !buffer.is_singleton() {
15154 buffer.push_transaction(&transaction.0, cx);
15155 }
15156 }
15157 cx.notify();
15158 })
15159 .ok();
15160 Ok(())
15161 })
15162 }
15163
15164 fn restart_language_server(
15165 &mut self,
15166 _: &RestartLanguageServer,
15167 _: &mut Window,
15168 cx: &mut Context<Self>,
15169 ) {
15170 if let Some(project) = self.project.clone() {
15171 self.buffer.update(cx, |multi_buffer, cx| {
15172 project.update(cx, |project, cx| {
15173 project.restart_language_servers_for_buffers(
15174 multi_buffer.all_buffers().into_iter().collect(),
15175 cx,
15176 );
15177 });
15178 })
15179 }
15180 }
15181
15182 fn stop_language_server(
15183 &mut self,
15184 _: &StopLanguageServer,
15185 _: &mut Window,
15186 cx: &mut Context<Self>,
15187 ) {
15188 if let Some(project) = self.project.clone() {
15189 self.buffer.update(cx, |multi_buffer, cx| {
15190 project.update(cx, |project, cx| {
15191 project.stop_language_servers_for_buffers(
15192 multi_buffer.all_buffers().into_iter().collect(),
15193 cx,
15194 );
15195 cx.emit(project::Event::RefreshInlayHints);
15196 });
15197 });
15198 }
15199 }
15200
15201 fn cancel_language_server_work(
15202 workspace: &mut Workspace,
15203 _: &actions::CancelLanguageServerWork,
15204 _: &mut Window,
15205 cx: &mut Context<Workspace>,
15206 ) {
15207 let project = workspace.project();
15208 let buffers = workspace
15209 .active_item(cx)
15210 .and_then(|item| item.act_as::<Editor>(cx))
15211 .map_or(HashSet::default(), |editor| {
15212 editor.read(cx).buffer.read(cx).all_buffers()
15213 });
15214 project.update(cx, |project, cx| {
15215 project.cancel_language_server_work_for_buffers(buffers, cx);
15216 });
15217 }
15218
15219 fn show_character_palette(
15220 &mut self,
15221 _: &ShowCharacterPalette,
15222 window: &mut Window,
15223 _: &mut Context<Self>,
15224 ) {
15225 window.show_character_palette();
15226 }
15227
15228 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15229 if self.mode.is_minimap() {
15230 return;
15231 }
15232
15233 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15234 let buffer = self.buffer.read(cx).snapshot(cx);
15235 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15236 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15237 let is_valid = buffer
15238 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15239 .any(|entry| {
15240 entry.diagnostic.is_primary
15241 && !entry.range.is_empty()
15242 && entry.range.start == primary_range_start
15243 && entry.diagnostic.message == active_diagnostics.active_message
15244 });
15245
15246 if !is_valid {
15247 self.dismiss_diagnostics(cx);
15248 }
15249 }
15250 }
15251
15252 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15253 match &self.active_diagnostics {
15254 ActiveDiagnostic::Group(group) => Some(group),
15255 _ => None,
15256 }
15257 }
15258
15259 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15260 self.dismiss_diagnostics(cx);
15261 self.active_diagnostics = ActiveDiagnostic::All;
15262 }
15263
15264 fn activate_diagnostics(
15265 &mut self,
15266 buffer_id: BufferId,
15267 diagnostic: DiagnosticEntry<usize>,
15268 window: &mut Window,
15269 cx: &mut Context<Self>,
15270 ) {
15271 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15272 return;
15273 }
15274 self.dismiss_diagnostics(cx);
15275 let snapshot = self.snapshot(window, cx);
15276 let buffer = self.buffer.read(cx).snapshot(cx);
15277 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15278 return;
15279 };
15280
15281 let diagnostic_group = buffer
15282 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15283 .collect::<Vec<_>>();
15284
15285 let blocks =
15286 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15287
15288 let blocks = self.display_map.update(cx, |display_map, cx| {
15289 display_map.insert_blocks(blocks, cx).into_iter().collect()
15290 });
15291 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15292 active_range: buffer.anchor_before(diagnostic.range.start)
15293 ..buffer.anchor_after(diagnostic.range.end),
15294 active_message: diagnostic.diagnostic.message.clone(),
15295 group_id: diagnostic.diagnostic.group_id,
15296 blocks,
15297 });
15298 cx.notify();
15299 }
15300
15301 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15302 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15303 return;
15304 };
15305
15306 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15307 if let ActiveDiagnostic::Group(group) = prev {
15308 self.display_map.update(cx, |display_map, cx| {
15309 display_map.remove_blocks(group.blocks, cx);
15310 });
15311 cx.notify();
15312 }
15313 }
15314
15315 /// Disable inline diagnostics rendering for this editor.
15316 pub fn disable_inline_diagnostics(&mut self) {
15317 self.inline_diagnostics_enabled = false;
15318 self.inline_diagnostics_update = Task::ready(());
15319 self.inline_diagnostics.clear();
15320 }
15321
15322 pub fn diagnostics_enabled(&self) -> bool {
15323 self.mode.is_full()
15324 }
15325
15326 pub fn inline_diagnostics_enabled(&self) -> bool {
15327 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15328 }
15329
15330 pub fn show_inline_diagnostics(&self) -> bool {
15331 self.show_inline_diagnostics
15332 }
15333
15334 pub fn toggle_inline_diagnostics(
15335 &mut self,
15336 _: &ToggleInlineDiagnostics,
15337 window: &mut Window,
15338 cx: &mut Context<Editor>,
15339 ) {
15340 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15341 self.refresh_inline_diagnostics(false, window, cx);
15342 }
15343
15344 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15345 self.diagnostics_max_severity = severity;
15346 self.display_map.update(cx, |display_map, _| {
15347 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15348 });
15349 }
15350
15351 pub fn toggle_diagnostics(
15352 &mut self,
15353 _: &ToggleDiagnostics,
15354 window: &mut Window,
15355 cx: &mut Context<Editor>,
15356 ) {
15357 if !self.diagnostics_enabled() {
15358 return;
15359 }
15360
15361 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15362 EditorSettings::get_global(cx)
15363 .diagnostics_max_severity
15364 .filter(|severity| severity != &DiagnosticSeverity::Off)
15365 .unwrap_or(DiagnosticSeverity::Hint)
15366 } else {
15367 DiagnosticSeverity::Off
15368 };
15369 self.set_max_diagnostics_severity(new_severity, cx);
15370 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15371 self.active_diagnostics = ActiveDiagnostic::None;
15372 self.inline_diagnostics_update = Task::ready(());
15373 self.inline_diagnostics.clear();
15374 } else {
15375 self.refresh_inline_diagnostics(false, window, cx);
15376 }
15377
15378 cx.notify();
15379 }
15380
15381 pub fn toggle_minimap(
15382 &mut self,
15383 _: &ToggleMinimap,
15384 window: &mut Window,
15385 cx: &mut Context<Editor>,
15386 ) {
15387 if self.supports_minimap(cx) {
15388 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15389 }
15390 }
15391
15392 fn refresh_inline_diagnostics(
15393 &mut self,
15394 debounce: bool,
15395 window: &mut Window,
15396 cx: &mut Context<Self>,
15397 ) {
15398 let max_severity = ProjectSettings::get_global(cx)
15399 .diagnostics
15400 .inline
15401 .max_severity
15402 .unwrap_or(self.diagnostics_max_severity);
15403
15404 if self.mode.is_minimap()
15405 || !self.inline_diagnostics_enabled()
15406 || !self.show_inline_diagnostics
15407 || max_severity == DiagnosticSeverity::Off
15408 {
15409 self.inline_diagnostics_update = Task::ready(());
15410 self.inline_diagnostics.clear();
15411 return;
15412 }
15413
15414 let debounce_ms = ProjectSettings::get_global(cx)
15415 .diagnostics
15416 .inline
15417 .update_debounce_ms;
15418 let debounce = if debounce && debounce_ms > 0 {
15419 Some(Duration::from_millis(debounce_ms))
15420 } else {
15421 None
15422 };
15423 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15424 let editor = editor.upgrade().unwrap();
15425
15426 if let Some(debounce) = debounce {
15427 cx.background_executor().timer(debounce).await;
15428 }
15429 let Some(snapshot) = editor
15430 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15431 .ok()
15432 else {
15433 return;
15434 };
15435
15436 let new_inline_diagnostics = cx
15437 .background_spawn(async move {
15438 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15439 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15440 let message = diagnostic_entry
15441 .diagnostic
15442 .message
15443 .split_once('\n')
15444 .map(|(line, _)| line)
15445 .map(SharedString::new)
15446 .unwrap_or_else(|| {
15447 SharedString::from(diagnostic_entry.diagnostic.message)
15448 });
15449 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15450 let (Ok(i) | Err(i)) = inline_diagnostics
15451 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15452 inline_diagnostics.insert(
15453 i,
15454 (
15455 start_anchor,
15456 InlineDiagnostic {
15457 message,
15458 group_id: diagnostic_entry.diagnostic.group_id,
15459 start: diagnostic_entry.range.start.to_point(&snapshot),
15460 is_primary: diagnostic_entry.diagnostic.is_primary,
15461 severity: diagnostic_entry.diagnostic.severity,
15462 },
15463 ),
15464 );
15465 }
15466 inline_diagnostics
15467 })
15468 .await;
15469
15470 editor
15471 .update(cx, |editor, cx| {
15472 editor.inline_diagnostics = new_inline_diagnostics;
15473 cx.notify();
15474 })
15475 .ok();
15476 });
15477 }
15478
15479 pub fn set_selections_from_remote(
15480 &mut self,
15481 selections: Vec<Selection<Anchor>>,
15482 pending_selection: Option<Selection<Anchor>>,
15483 window: &mut Window,
15484 cx: &mut Context<Self>,
15485 ) {
15486 let old_cursor_position = self.selections.newest_anchor().head();
15487 self.selections.change_with(cx, |s| {
15488 s.select_anchors(selections);
15489 if let Some(pending_selection) = pending_selection {
15490 s.set_pending(pending_selection, SelectMode::Character);
15491 } else {
15492 s.clear_pending();
15493 }
15494 });
15495 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15496 }
15497
15498 fn push_to_selection_history(&mut self) {
15499 self.selection_history.push(SelectionHistoryEntry {
15500 selections: self.selections.disjoint_anchors(),
15501 select_next_state: self.select_next_state.clone(),
15502 select_prev_state: self.select_prev_state.clone(),
15503 add_selections_state: self.add_selections_state.clone(),
15504 });
15505 }
15506
15507 pub fn transact(
15508 &mut self,
15509 window: &mut Window,
15510 cx: &mut Context<Self>,
15511 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15512 ) -> Option<TransactionId> {
15513 self.start_transaction_at(Instant::now(), window, cx);
15514 update(self, window, cx);
15515 self.end_transaction_at(Instant::now(), cx)
15516 }
15517
15518 pub fn start_transaction_at(
15519 &mut self,
15520 now: Instant,
15521 window: &mut Window,
15522 cx: &mut Context<Self>,
15523 ) {
15524 self.end_selection(window, cx);
15525 if let Some(tx_id) = self
15526 .buffer
15527 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15528 {
15529 self.selection_history
15530 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15531 cx.emit(EditorEvent::TransactionBegun {
15532 transaction_id: tx_id,
15533 })
15534 }
15535 }
15536
15537 pub fn end_transaction_at(
15538 &mut self,
15539 now: Instant,
15540 cx: &mut Context<Self>,
15541 ) -> Option<TransactionId> {
15542 if let Some(transaction_id) = self
15543 .buffer
15544 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15545 {
15546 if let Some((_, end_selections)) =
15547 self.selection_history.transaction_mut(transaction_id)
15548 {
15549 *end_selections = Some(self.selections.disjoint_anchors());
15550 } else {
15551 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15552 }
15553
15554 cx.emit(EditorEvent::Edited { transaction_id });
15555 Some(transaction_id)
15556 } else {
15557 None
15558 }
15559 }
15560
15561 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15562 if self.selection_mark_mode {
15563 self.change_selections(None, window, cx, |s| {
15564 s.move_with(|_, sel| {
15565 sel.collapse_to(sel.head(), SelectionGoal::None);
15566 });
15567 })
15568 }
15569 self.selection_mark_mode = true;
15570 cx.notify();
15571 }
15572
15573 pub fn swap_selection_ends(
15574 &mut self,
15575 _: &actions::SwapSelectionEnds,
15576 window: &mut Window,
15577 cx: &mut Context<Self>,
15578 ) {
15579 self.change_selections(None, window, cx, |s| {
15580 s.move_with(|_, sel| {
15581 if sel.start != sel.end {
15582 sel.reversed = !sel.reversed
15583 }
15584 });
15585 });
15586 self.request_autoscroll(Autoscroll::newest(), cx);
15587 cx.notify();
15588 }
15589
15590 pub fn toggle_fold(
15591 &mut self,
15592 _: &actions::ToggleFold,
15593 window: &mut Window,
15594 cx: &mut Context<Self>,
15595 ) {
15596 if self.is_singleton(cx) {
15597 let selection = self.selections.newest::<Point>(cx);
15598
15599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15600 let range = if selection.is_empty() {
15601 let point = selection.head().to_display_point(&display_map);
15602 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15603 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15604 .to_point(&display_map);
15605 start..end
15606 } else {
15607 selection.range()
15608 };
15609 if display_map.folds_in_range(range).next().is_some() {
15610 self.unfold_lines(&Default::default(), window, cx)
15611 } else {
15612 self.fold(&Default::default(), window, cx)
15613 }
15614 } else {
15615 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15616 let buffer_ids: HashSet<_> = self
15617 .selections
15618 .disjoint_anchor_ranges()
15619 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15620 .collect();
15621
15622 let should_unfold = buffer_ids
15623 .iter()
15624 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15625
15626 for buffer_id in buffer_ids {
15627 if should_unfold {
15628 self.unfold_buffer(buffer_id, cx);
15629 } else {
15630 self.fold_buffer(buffer_id, cx);
15631 }
15632 }
15633 }
15634 }
15635
15636 pub fn toggle_fold_recursive(
15637 &mut self,
15638 _: &actions::ToggleFoldRecursive,
15639 window: &mut Window,
15640 cx: &mut Context<Self>,
15641 ) {
15642 let selection = self.selections.newest::<Point>(cx);
15643
15644 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15645 let range = if selection.is_empty() {
15646 let point = selection.head().to_display_point(&display_map);
15647 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15648 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15649 .to_point(&display_map);
15650 start..end
15651 } else {
15652 selection.range()
15653 };
15654 if display_map.folds_in_range(range).next().is_some() {
15655 self.unfold_recursive(&Default::default(), window, cx)
15656 } else {
15657 self.fold_recursive(&Default::default(), window, cx)
15658 }
15659 }
15660
15661 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15662 if self.is_singleton(cx) {
15663 let mut to_fold = Vec::new();
15664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15665 let selections = self.selections.all_adjusted(cx);
15666
15667 for selection in selections {
15668 let range = selection.range().sorted();
15669 let buffer_start_row = range.start.row;
15670
15671 if range.start.row != range.end.row {
15672 let mut found = false;
15673 let mut row = range.start.row;
15674 while row <= range.end.row {
15675 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15676 {
15677 found = true;
15678 row = crease.range().end.row + 1;
15679 to_fold.push(crease);
15680 } else {
15681 row += 1
15682 }
15683 }
15684 if found {
15685 continue;
15686 }
15687 }
15688
15689 for row in (0..=range.start.row).rev() {
15690 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15691 if crease.range().end.row >= buffer_start_row {
15692 to_fold.push(crease);
15693 if row <= range.start.row {
15694 break;
15695 }
15696 }
15697 }
15698 }
15699 }
15700
15701 self.fold_creases(to_fold, true, window, cx);
15702 } else {
15703 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15704 let buffer_ids = self
15705 .selections
15706 .disjoint_anchor_ranges()
15707 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15708 .collect::<HashSet<_>>();
15709 for buffer_id in buffer_ids {
15710 self.fold_buffer(buffer_id, cx);
15711 }
15712 }
15713 }
15714
15715 fn fold_at_level(
15716 &mut self,
15717 fold_at: &FoldAtLevel,
15718 window: &mut Window,
15719 cx: &mut Context<Self>,
15720 ) {
15721 if !self.buffer.read(cx).is_singleton() {
15722 return;
15723 }
15724
15725 let fold_at_level = fold_at.0;
15726 let snapshot = self.buffer.read(cx).snapshot(cx);
15727 let mut to_fold = Vec::new();
15728 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15729
15730 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15731 while start_row < end_row {
15732 match self
15733 .snapshot(window, cx)
15734 .crease_for_buffer_row(MultiBufferRow(start_row))
15735 {
15736 Some(crease) => {
15737 let nested_start_row = crease.range().start.row + 1;
15738 let nested_end_row = crease.range().end.row;
15739
15740 if current_level < fold_at_level {
15741 stack.push((nested_start_row, nested_end_row, current_level + 1));
15742 } else if current_level == fold_at_level {
15743 to_fold.push(crease);
15744 }
15745
15746 start_row = nested_end_row + 1;
15747 }
15748 None => start_row += 1,
15749 }
15750 }
15751 }
15752
15753 self.fold_creases(to_fold, true, window, cx);
15754 }
15755
15756 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15757 if self.buffer.read(cx).is_singleton() {
15758 let mut fold_ranges = Vec::new();
15759 let snapshot = self.buffer.read(cx).snapshot(cx);
15760
15761 for row in 0..snapshot.max_row().0 {
15762 if let Some(foldable_range) = self
15763 .snapshot(window, cx)
15764 .crease_for_buffer_row(MultiBufferRow(row))
15765 {
15766 fold_ranges.push(foldable_range);
15767 }
15768 }
15769
15770 self.fold_creases(fold_ranges, true, window, cx);
15771 } else {
15772 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15773 editor
15774 .update_in(cx, |editor, _, cx| {
15775 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15776 editor.fold_buffer(buffer_id, cx);
15777 }
15778 })
15779 .ok();
15780 });
15781 }
15782 }
15783
15784 pub fn fold_function_bodies(
15785 &mut self,
15786 _: &actions::FoldFunctionBodies,
15787 window: &mut Window,
15788 cx: &mut Context<Self>,
15789 ) {
15790 let snapshot = self.buffer.read(cx).snapshot(cx);
15791
15792 let ranges = snapshot
15793 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15794 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15795 .collect::<Vec<_>>();
15796
15797 let creases = ranges
15798 .into_iter()
15799 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15800 .collect();
15801
15802 self.fold_creases(creases, true, window, cx);
15803 }
15804
15805 pub fn fold_recursive(
15806 &mut self,
15807 _: &actions::FoldRecursive,
15808 window: &mut Window,
15809 cx: &mut Context<Self>,
15810 ) {
15811 let mut to_fold = Vec::new();
15812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15813 let selections = self.selections.all_adjusted(cx);
15814
15815 for selection in selections {
15816 let range = selection.range().sorted();
15817 let buffer_start_row = range.start.row;
15818
15819 if range.start.row != range.end.row {
15820 let mut found = false;
15821 for row in range.start.row..=range.end.row {
15822 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15823 found = true;
15824 to_fold.push(crease);
15825 }
15826 }
15827 if found {
15828 continue;
15829 }
15830 }
15831
15832 for row in (0..=range.start.row).rev() {
15833 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15834 if crease.range().end.row >= buffer_start_row {
15835 to_fold.push(crease);
15836 } else {
15837 break;
15838 }
15839 }
15840 }
15841 }
15842
15843 self.fold_creases(to_fold, true, window, cx);
15844 }
15845
15846 pub fn fold_at(
15847 &mut self,
15848 buffer_row: MultiBufferRow,
15849 window: &mut Window,
15850 cx: &mut Context<Self>,
15851 ) {
15852 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15853
15854 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15855 let autoscroll = self
15856 .selections
15857 .all::<Point>(cx)
15858 .iter()
15859 .any(|selection| crease.range().overlaps(&selection.range()));
15860
15861 self.fold_creases(vec![crease], autoscroll, window, cx);
15862 }
15863 }
15864
15865 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15866 if self.is_singleton(cx) {
15867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15868 let buffer = &display_map.buffer_snapshot;
15869 let selections = self.selections.all::<Point>(cx);
15870 let ranges = selections
15871 .iter()
15872 .map(|s| {
15873 let range = s.display_range(&display_map).sorted();
15874 let mut start = range.start.to_point(&display_map);
15875 let mut end = range.end.to_point(&display_map);
15876 start.column = 0;
15877 end.column = buffer.line_len(MultiBufferRow(end.row));
15878 start..end
15879 })
15880 .collect::<Vec<_>>();
15881
15882 self.unfold_ranges(&ranges, true, true, cx);
15883 } else {
15884 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15885 let buffer_ids = self
15886 .selections
15887 .disjoint_anchor_ranges()
15888 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15889 .collect::<HashSet<_>>();
15890 for buffer_id in buffer_ids {
15891 self.unfold_buffer(buffer_id, cx);
15892 }
15893 }
15894 }
15895
15896 pub fn unfold_recursive(
15897 &mut self,
15898 _: &UnfoldRecursive,
15899 _window: &mut Window,
15900 cx: &mut Context<Self>,
15901 ) {
15902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15903 let selections = self.selections.all::<Point>(cx);
15904 let ranges = selections
15905 .iter()
15906 .map(|s| {
15907 let mut range = s.display_range(&display_map).sorted();
15908 *range.start.column_mut() = 0;
15909 *range.end.column_mut() = display_map.line_len(range.end.row());
15910 let start = range.start.to_point(&display_map);
15911 let end = range.end.to_point(&display_map);
15912 start..end
15913 })
15914 .collect::<Vec<_>>();
15915
15916 self.unfold_ranges(&ranges, true, true, cx);
15917 }
15918
15919 pub fn unfold_at(
15920 &mut self,
15921 buffer_row: MultiBufferRow,
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
15927 let intersection_range = Point::new(buffer_row.0, 0)
15928 ..Point::new(
15929 buffer_row.0,
15930 display_map.buffer_snapshot.line_len(buffer_row),
15931 );
15932
15933 let autoscroll = self
15934 .selections
15935 .all::<Point>(cx)
15936 .iter()
15937 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15938
15939 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15940 }
15941
15942 pub fn unfold_all(
15943 &mut self,
15944 _: &actions::UnfoldAll,
15945 _window: &mut Window,
15946 cx: &mut Context<Self>,
15947 ) {
15948 if self.buffer.read(cx).is_singleton() {
15949 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15950 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15951 } else {
15952 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15953 editor
15954 .update(cx, |editor, cx| {
15955 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15956 editor.unfold_buffer(buffer_id, cx);
15957 }
15958 })
15959 .ok();
15960 });
15961 }
15962 }
15963
15964 pub fn fold_selected_ranges(
15965 &mut self,
15966 _: &FoldSelectedRanges,
15967 window: &mut Window,
15968 cx: &mut Context<Self>,
15969 ) {
15970 let selections = self.selections.all_adjusted(cx);
15971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15972 let ranges = selections
15973 .into_iter()
15974 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15975 .collect::<Vec<_>>();
15976 self.fold_creases(ranges, true, window, cx);
15977 }
15978
15979 pub fn fold_ranges<T: ToOffset + Clone>(
15980 &mut self,
15981 ranges: Vec<Range<T>>,
15982 auto_scroll: bool,
15983 window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15987 let ranges = ranges
15988 .into_iter()
15989 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15990 .collect::<Vec<_>>();
15991 self.fold_creases(ranges, auto_scroll, window, cx);
15992 }
15993
15994 pub fn fold_creases<T: ToOffset + Clone>(
15995 &mut self,
15996 creases: Vec<Crease<T>>,
15997 auto_scroll: bool,
15998 _window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 if creases.is_empty() {
16002 return;
16003 }
16004
16005 let mut buffers_affected = HashSet::default();
16006 let multi_buffer = self.buffer().read(cx);
16007 for crease in &creases {
16008 if let Some((_, buffer, _)) =
16009 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16010 {
16011 buffers_affected.insert(buffer.read(cx).remote_id());
16012 };
16013 }
16014
16015 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16016
16017 if auto_scroll {
16018 self.request_autoscroll(Autoscroll::fit(), cx);
16019 }
16020
16021 cx.notify();
16022
16023 self.scrollbar_marker_state.dirty = true;
16024 self.folds_did_change(cx);
16025 }
16026
16027 /// Removes any folds whose ranges intersect any of the given ranges.
16028 pub fn unfold_ranges<T: ToOffset + Clone>(
16029 &mut self,
16030 ranges: &[Range<T>],
16031 inclusive: bool,
16032 auto_scroll: bool,
16033 cx: &mut Context<Self>,
16034 ) {
16035 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16036 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16037 });
16038 self.folds_did_change(cx);
16039 }
16040
16041 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16042 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16043 return;
16044 }
16045 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16046 self.display_map.update(cx, |display_map, cx| {
16047 display_map.fold_buffers([buffer_id], cx)
16048 });
16049 cx.emit(EditorEvent::BufferFoldToggled {
16050 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16051 folded: true,
16052 });
16053 cx.notify();
16054 }
16055
16056 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16057 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16058 return;
16059 }
16060 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16061 self.display_map.update(cx, |display_map, cx| {
16062 display_map.unfold_buffers([buffer_id], cx);
16063 });
16064 cx.emit(EditorEvent::BufferFoldToggled {
16065 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16066 folded: false,
16067 });
16068 cx.notify();
16069 }
16070
16071 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16072 self.display_map.read(cx).is_buffer_folded(buffer)
16073 }
16074
16075 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16076 self.display_map.read(cx).folded_buffers()
16077 }
16078
16079 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16080 self.display_map.update(cx, |display_map, cx| {
16081 display_map.disable_header_for_buffer(buffer_id, cx);
16082 });
16083 cx.notify();
16084 }
16085
16086 /// Removes any folds with the given ranges.
16087 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16088 &mut self,
16089 ranges: &[Range<T>],
16090 type_id: TypeId,
16091 auto_scroll: bool,
16092 cx: &mut Context<Self>,
16093 ) {
16094 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16095 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16096 });
16097 self.folds_did_change(cx);
16098 }
16099
16100 fn remove_folds_with<T: ToOffset + Clone>(
16101 &mut self,
16102 ranges: &[Range<T>],
16103 auto_scroll: bool,
16104 cx: &mut Context<Self>,
16105 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16106 ) {
16107 if ranges.is_empty() {
16108 return;
16109 }
16110
16111 let mut buffers_affected = HashSet::default();
16112 let multi_buffer = self.buffer().read(cx);
16113 for range in ranges {
16114 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16115 buffers_affected.insert(buffer.read(cx).remote_id());
16116 };
16117 }
16118
16119 self.display_map.update(cx, update);
16120
16121 if auto_scroll {
16122 self.request_autoscroll(Autoscroll::fit(), cx);
16123 }
16124
16125 cx.notify();
16126 self.scrollbar_marker_state.dirty = true;
16127 self.active_indent_guides_state.dirty = true;
16128 }
16129
16130 pub fn update_fold_widths(
16131 &mut self,
16132 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16133 cx: &mut Context<Self>,
16134 ) -> bool {
16135 self.display_map
16136 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16137 }
16138
16139 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16140 self.display_map.read(cx).fold_placeholder.clone()
16141 }
16142
16143 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16144 self.buffer.update(cx, |buffer, cx| {
16145 buffer.set_all_diff_hunks_expanded(cx);
16146 });
16147 }
16148
16149 pub fn expand_all_diff_hunks(
16150 &mut self,
16151 _: &ExpandAllDiffHunks,
16152 _window: &mut Window,
16153 cx: &mut Context<Self>,
16154 ) {
16155 self.buffer.update(cx, |buffer, cx| {
16156 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16157 });
16158 }
16159
16160 pub fn toggle_selected_diff_hunks(
16161 &mut self,
16162 _: &ToggleSelectedDiffHunks,
16163 _window: &mut Window,
16164 cx: &mut Context<Self>,
16165 ) {
16166 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16167 self.toggle_diff_hunks_in_ranges(ranges, cx);
16168 }
16169
16170 pub fn diff_hunks_in_ranges<'a>(
16171 &'a self,
16172 ranges: &'a [Range<Anchor>],
16173 buffer: &'a MultiBufferSnapshot,
16174 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16175 ranges.iter().flat_map(move |range| {
16176 let end_excerpt_id = range.end.excerpt_id;
16177 let range = range.to_point(buffer);
16178 let mut peek_end = range.end;
16179 if range.end.row < buffer.max_row().0 {
16180 peek_end = Point::new(range.end.row + 1, 0);
16181 }
16182 buffer
16183 .diff_hunks_in_range(range.start..peek_end)
16184 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16185 })
16186 }
16187
16188 pub fn has_stageable_diff_hunks_in_ranges(
16189 &self,
16190 ranges: &[Range<Anchor>],
16191 snapshot: &MultiBufferSnapshot,
16192 ) -> bool {
16193 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16194 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16195 }
16196
16197 pub fn toggle_staged_selected_diff_hunks(
16198 &mut self,
16199 _: &::git::ToggleStaged,
16200 _: &mut Window,
16201 cx: &mut Context<Self>,
16202 ) {
16203 let snapshot = self.buffer.read(cx).snapshot(cx);
16204 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16205 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16206 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16207 }
16208
16209 pub fn set_render_diff_hunk_controls(
16210 &mut self,
16211 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16212 cx: &mut Context<Self>,
16213 ) {
16214 self.render_diff_hunk_controls = render_diff_hunk_controls;
16215 cx.notify();
16216 }
16217
16218 pub fn stage_and_next(
16219 &mut self,
16220 _: &::git::StageAndNext,
16221 window: &mut Window,
16222 cx: &mut Context<Self>,
16223 ) {
16224 self.do_stage_or_unstage_and_next(true, window, cx);
16225 }
16226
16227 pub fn unstage_and_next(
16228 &mut self,
16229 _: &::git::UnstageAndNext,
16230 window: &mut Window,
16231 cx: &mut Context<Self>,
16232 ) {
16233 self.do_stage_or_unstage_and_next(false, window, cx);
16234 }
16235
16236 pub fn stage_or_unstage_diff_hunks(
16237 &mut self,
16238 stage: bool,
16239 ranges: Vec<Range<Anchor>>,
16240 cx: &mut Context<Self>,
16241 ) {
16242 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16243 cx.spawn(async move |this, cx| {
16244 task.await?;
16245 this.update(cx, |this, cx| {
16246 let snapshot = this.buffer.read(cx).snapshot(cx);
16247 let chunk_by = this
16248 .diff_hunks_in_ranges(&ranges, &snapshot)
16249 .chunk_by(|hunk| hunk.buffer_id);
16250 for (buffer_id, hunks) in &chunk_by {
16251 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16252 }
16253 })
16254 })
16255 .detach_and_log_err(cx);
16256 }
16257
16258 fn save_buffers_for_ranges_if_needed(
16259 &mut self,
16260 ranges: &[Range<Anchor>],
16261 cx: &mut Context<Editor>,
16262 ) -> Task<Result<()>> {
16263 let multibuffer = self.buffer.read(cx);
16264 let snapshot = multibuffer.read(cx);
16265 let buffer_ids: HashSet<_> = ranges
16266 .iter()
16267 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16268 .collect();
16269 drop(snapshot);
16270
16271 let mut buffers = HashSet::default();
16272 for buffer_id in buffer_ids {
16273 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16274 let buffer = buffer_entity.read(cx);
16275 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16276 {
16277 buffers.insert(buffer_entity);
16278 }
16279 }
16280 }
16281
16282 if let Some(project) = &self.project {
16283 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16284 } else {
16285 Task::ready(Ok(()))
16286 }
16287 }
16288
16289 fn do_stage_or_unstage_and_next(
16290 &mut self,
16291 stage: bool,
16292 window: &mut Window,
16293 cx: &mut Context<Self>,
16294 ) {
16295 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16296
16297 if ranges.iter().any(|range| range.start != range.end) {
16298 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16299 return;
16300 }
16301
16302 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16303 let snapshot = self.snapshot(window, cx);
16304 let position = self.selections.newest::<Point>(cx).head();
16305 let mut row = snapshot
16306 .buffer_snapshot
16307 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16308 .find(|hunk| hunk.row_range.start.0 > position.row)
16309 .map(|hunk| hunk.row_range.start);
16310
16311 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16312 // Outside of the project diff editor, wrap around to the beginning.
16313 if !all_diff_hunks_expanded {
16314 row = row.or_else(|| {
16315 snapshot
16316 .buffer_snapshot
16317 .diff_hunks_in_range(Point::zero()..position)
16318 .find(|hunk| hunk.row_range.end.0 < position.row)
16319 .map(|hunk| hunk.row_range.start)
16320 });
16321 }
16322
16323 if let Some(row) = row {
16324 let destination = Point::new(row.0, 0);
16325 let autoscroll = Autoscroll::center();
16326
16327 self.unfold_ranges(&[destination..destination], false, false, cx);
16328 self.change_selections(Some(autoscroll), window, cx, |s| {
16329 s.select_ranges([destination..destination]);
16330 });
16331 }
16332 }
16333
16334 fn do_stage_or_unstage(
16335 &self,
16336 stage: bool,
16337 buffer_id: BufferId,
16338 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16339 cx: &mut App,
16340 ) -> Option<()> {
16341 let project = self.project.as_ref()?;
16342 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16343 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16344 let buffer_snapshot = buffer.read(cx).snapshot();
16345 let file_exists = buffer_snapshot
16346 .file()
16347 .is_some_and(|file| file.disk_state().exists());
16348 diff.update(cx, |diff, cx| {
16349 diff.stage_or_unstage_hunks(
16350 stage,
16351 &hunks
16352 .map(|hunk| buffer_diff::DiffHunk {
16353 buffer_range: hunk.buffer_range,
16354 diff_base_byte_range: hunk.diff_base_byte_range,
16355 secondary_status: hunk.secondary_status,
16356 range: Point::zero()..Point::zero(), // unused
16357 })
16358 .collect::<Vec<_>>(),
16359 &buffer_snapshot,
16360 file_exists,
16361 cx,
16362 )
16363 });
16364 None
16365 }
16366
16367 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16368 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16369 self.buffer
16370 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16371 }
16372
16373 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16374 self.buffer.update(cx, |buffer, cx| {
16375 let ranges = vec![Anchor::min()..Anchor::max()];
16376 if !buffer.all_diff_hunks_expanded()
16377 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16378 {
16379 buffer.collapse_diff_hunks(ranges, cx);
16380 true
16381 } else {
16382 false
16383 }
16384 })
16385 }
16386
16387 fn toggle_diff_hunks_in_ranges(
16388 &mut self,
16389 ranges: Vec<Range<Anchor>>,
16390 cx: &mut Context<Editor>,
16391 ) {
16392 self.buffer.update(cx, |buffer, cx| {
16393 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16394 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16395 })
16396 }
16397
16398 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16399 self.buffer.update(cx, |buffer, cx| {
16400 let snapshot = buffer.snapshot(cx);
16401 let excerpt_id = range.end.excerpt_id;
16402 let point_range = range.to_point(&snapshot);
16403 let expand = !buffer.single_hunk_is_expanded(range, cx);
16404 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16405 })
16406 }
16407
16408 pub(crate) fn apply_all_diff_hunks(
16409 &mut self,
16410 _: &ApplyAllDiffHunks,
16411 window: &mut Window,
16412 cx: &mut Context<Self>,
16413 ) {
16414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16415
16416 let buffers = self.buffer.read(cx).all_buffers();
16417 for branch_buffer in buffers {
16418 branch_buffer.update(cx, |branch_buffer, cx| {
16419 branch_buffer.merge_into_base(Vec::new(), cx);
16420 });
16421 }
16422
16423 if let Some(project) = self.project.clone() {
16424 self.save(true, project, window, cx).detach_and_log_err(cx);
16425 }
16426 }
16427
16428 pub(crate) fn apply_selected_diff_hunks(
16429 &mut self,
16430 _: &ApplyDiffHunk,
16431 window: &mut Window,
16432 cx: &mut Context<Self>,
16433 ) {
16434 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16435 let snapshot = self.snapshot(window, cx);
16436 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16437 let mut ranges_by_buffer = HashMap::default();
16438 self.transact(window, cx, |editor, _window, cx| {
16439 for hunk in hunks {
16440 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16441 ranges_by_buffer
16442 .entry(buffer.clone())
16443 .or_insert_with(Vec::new)
16444 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16445 }
16446 }
16447
16448 for (buffer, ranges) in ranges_by_buffer {
16449 buffer.update(cx, |buffer, cx| {
16450 buffer.merge_into_base(ranges, cx);
16451 });
16452 }
16453 });
16454
16455 if let Some(project) = self.project.clone() {
16456 self.save(true, project, window, cx).detach_and_log_err(cx);
16457 }
16458 }
16459
16460 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16461 if hovered != self.gutter_hovered {
16462 self.gutter_hovered = hovered;
16463 cx.notify();
16464 }
16465 }
16466
16467 pub fn insert_blocks(
16468 &mut self,
16469 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16470 autoscroll: Option<Autoscroll>,
16471 cx: &mut Context<Self>,
16472 ) -> Vec<CustomBlockId> {
16473 let blocks = self
16474 .display_map
16475 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16476 if let Some(autoscroll) = autoscroll {
16477 self.request_autoscroll(autoscroll, cx);
16478 }
16479 cx.notify();
16480 blocks
16481 }
16482
16483 pub fn resize_blocks(
16484 &mut self,
16485 heights: HashMap<CustomBlockId, u32>,
16486 autoscroll: Option<Autoscroll>,
16487 cx: &mut Context<Self>,
16488 ) {
16489 self.display_map
16490 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16491 if let Some(autoscroll) = autoscroll {
16492 self.request_autoscroll(autoscroll, cx);
16493 }
16494 cx.notify();
16495 }
16496
16497 pub fn replace_blocks(
16498 &mut self,
16499 renderers: HashMap<CustomBlockId, RenderBlock>,
16500 autoscroll: Option<Autoscroll>,
16501 cx: &mut Context<Self>,
16502 ) {
16503 self.display_map
16504 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16505 if let Some(autoscroll) = autoscroll {
16506 self.request_autoscroll(autoscroll, cx);
16507 }
16508 cx.notify();
16509 }
16510
16511 pub fn remove_blocks(
16512 &mut self,
16513 block_ids: HashSet<CustomBlockId>,
16514 autoscroll: Option<Autoscroll>,
16515 cx: &mut Context<Self>,
16516 ) {
16517 self.display_map.update(cx, |display_map, cx| {
16518 display_map.remove_blocks(block_ids, cx)
16519 });
16520 if let Some(autoscroll) = autoscroll {
16521 self.request_autoscroll(autoscroll, cx);
16522 }
16523 cx.notify();
16524 }
16525
16526 pub fn row_for_block(
16527 &self,
16528 block_id: CustomBlockId,
16529 cx: &mut Context<Self>,
16530 ) -> Option<DisplayRow> {
16531 self.display_map
16532 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16533 }
16534
16535 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16536 self.focused_block = Some(focused_block);
16537 }
16538
16539 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16540 self.focused_block.take()
16541 }
16542
16543 pub fn insert_creases(
16544 &mut self,
16545 creases: impl IntoIterator<Item = Crease<Anchor>>,
16546 cx: &mut Context<Self>,
16547 ) -> Vec<CreaseId> {
16548 self.display_map
16549 .update(cx, |map, cx| map.insert_creases(creases, cx))
16550 }
16551
16552 pub fn remove_creases(
16553 &mut self,
16554 ids: impl IntoIterator<Item = CreaseId>,
16555 cx: &mut Context<Self>,
16556 ) -> Vec<(CreaseId, Range<Anchor>)> {
16557 self.display_map
16558 .update(cx, |map, cx| map.remove_creases(ids, cx))
16559 }
16560
16561 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16562 self.display_map
16563 .update(cx, |map, cx| map.snapshot(cx))
16564 .longest_row()
16565 }
16566
16567 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16568 self.display_map
16569 .update(cx, |map, cx| map.snapshot(cx))
16570 .max_point()
16571 }
16572
16573 pub fn text(&self, cx: &App) -> String {
16574 self.buffer.read(cx).read(cx).text()
16575 }
16576
16577 pub fn is_empty(&self, cx: &App) -> bool {
16578 self.buffer.read(cx).read(cx).is_empty()
16579 }
16580
16581 pub fn text_option(&self, cx: &App) -> Option<String> {
16582 let text = self.text(cx);
16583 let text = text.trim();
16584
16585 if text.is_empty() {
16586 return None;
16587 }
16588
16589 Some(text.to_string())
16590 }
16591
16592 pub fn set_text(
16593 &mut self,
16594 text: impl Into<Arc<str>>,
16595 window: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) {
16598 self.transact(window, cx, |this, _, cx| {
16599 this.buffer
16600 .read(cx)
16601 .as_singleton()
16602 .expect("you can only call set_text on editors for singleton buffers")
16603 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16604 });
16605 }
16606
16607 pub fn display_text(&self, cx: &mut App) -> String {
16608 self.display_map
16609 .update(cx, |map, cx| map.snapshot(cx))
16610 .text()
16611 }
16612
16613 fn create_minimap(
16614 &self,
16615 minimap_settings: MinimapSettings,
16616 window: &mut Window,
16617 cx: &mut Context<Self>,
16618 ) -> Option<Entity<Self>> {
16619 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16620 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16621 }
16622
16623 fn initialize_new_minimap(
16624 &self,
16625 minimap_settings: MinimapSettings,
16626 window: &mut Window,
16627 cx: &mut Context<Self>,
16628 ) -> Entity<Self> {
16629 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16630
16631 let mut minimap = Editor::new_internal(
16632 EditorMode::Minimap {
16633 parent: cx.weak_entity(),
16634 },
16635 self.buffer.clone(),
16636 self.project.clone(),
16637 Some(self.display_map.clone()),
16638 window,
16639 cx,
16640 );
16641 minimap.scroll_manager.clone_state(&self.scroll_manager);
16642 minimap.set_text_style_refinement(TextStyleRefinement {
16643 font_size: Some(MINIMAP_FONT_SIZE),
16644 font_weight: Some(MINIMAP_FONT_WEIGHT),
16645 ..Default::default()
16646 });
16647 minimap.update_minimap_configuration(minimap_settings, cx);
16648 cx.new(|_| minimap)
16649 }
16650
16651 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16652 let current_line_highlight = minimap_settings
16653 .current_line_highlight
16654 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16655 self.set_current_line_highlight(Some(current_line_highlight));
16656 }
16657
16658 pub fn minimap(&self) -> Option<&Entity<Self>> {
16659 self.minimap
16660 .as_ref()
16661 .filter(|_| self.minimap_visibility.visible())
16662 }
16663
16664 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16665 let mut wrap_guides = smallvec::smallvec![];
16666
16667 if self.show_wrap_guides == Some(false) {
16668 return wrap_guides;
16669 }
16670
16671 let settings = self.buffer.read(cx).language_settings(cx);
16672 if settings.show_wrap_guides {
16673 match self.soft_wrap_mode(cx) {
16674 SoftWrap::Column(soft_wrap) => {
16675 wrap_guides.push((soft_wrap as usize, true));
16676 }
16677 SoftWrap::Bounded(soft_wrap) => {
16678 wrap_guides.push((soft_wrap as usize, true));
16679 }
16680 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16681 }
16682 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16683 }
16684
16685 wrap_guides
16686 }
16687
16688 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16689 let settings = self.buffer.read(cx).language_settings(cx);
16690 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16691 match mode {
16692 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16693 SoftWrap::None
16694 }
16695 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16696 language_settings::SoftWrap::PreferredLineLength => {
16697 SoftWrap::Column(settings.preferred_line_length)
16698 }
16699 language_settings::SoftWrap::Bounded => {
16700 SoftWrap::Bounded(settings.preferred_line_length)
16701 }
16702 }
16703 }
16704
16705 pub fn set_soft_wrap_mode(
16706 &mut self,
16707 mode: language_settings::SoftWrap,
16708
16709 cx: &mut Context<Self>,
16710 ) {
16711 self.soft_wrap_mode_override = Some(mode);
16712 cx.notify();
16713 }
16714
16715 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16716 self.hard_wrap = hard_wrap;
16717 cx.notify();
16718 }
16719
16720 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16721 self.text_style_refinement = Some(style);
16722 }
16723
16724 /// called by the Element so we know what style we were most recently rendered with.
16725 pub(crate) fn set_style(
16726 &mut self,
16727 style: EditorStyle,
16728 window: &mut Window,
16729 cx: &mut Context<Self>,
16730 ) {
16731 // We intentionally do not inform the display map about the minimap style
16732 // so that wrapping is not recalculated and stays consistent for the editor
16733 // and its linked minimap.
16734 if !self.mode.is_minimap() {
16735 let rem_size = window.rem_size();
16736 self.display_map.update(cx, |map, cx| {
16737 map.set_font(
16738 style.text.font(),
16739 style.text.font_size.to_pixels(rem_size),
16740 cx,
16741 )
16742 });
16743 }
16744 self.style = Some(style);
16745 }
16746
16747 pub fn style(&self) -> Option<&EditorStyle> {
16748 self.style.as_ref()
16749 }
16750
16751 // Called by the element. This method is not designed to be called outside of the editor
16752 // element's layout code because it does not notify when rewrapping is computed synchronously.
16753 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16754 self.display_map
16755 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16756 }
16757
16758 pub fn set_soft_wrap(&mut self) {
16759 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16760 }
16761
16762 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16763 if self.soft_wrap_mode_override.is_some() {
16764 self.soft_wrap_mode_override.take();
16765 } else {
16766 let soft_wrap = match self.soft_wrap_mode(cx) {
16767 SoftWrap::GitDiff => return,
16768 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16769 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16770 language_settings::SoftWrap::None
16771 }
16772 };
16773 self.soft_wrap_mode_override = Some(soft_wrap);
16774 }
16775 cx.notify();
16776 }
16777
16778 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16779 let Some(workspace) = self.workspace() else {
16780 return;
16781 };
16782 let fs = workspace.read(cx).app_state().fs.clone();
16783 let current_show = TabBarSettings::get_global(cx).show;
16784 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16785 setting.show = Some(!current_show);
16786 });
16787 }
16788
16789 pub fn toggle_indent_guides(
16790 &mut self,
16791 _: &ToggleIndentGuides,
16792 _: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) {
16795 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16796 self.buffer
16797 .read(cx)
16798 .language_settings(cx)
16799 .indent_guides
16800 .enabled
16801 });
16802 self.show_indent_guides = Some(!currently_enabled);
16803 cx.notify();
16804 }
16805
16806 fn should_show_indent_guides(&self) -> Option<bool> {
16807 self.show_indent_guides
16808 }
16809
16810 pub fn toggle_line_numbers(
16811 &mut self,
16812 _: &ToggleLineNumbers,
16813 _: &mut Window,
16814 cx: &mut Context<Self>,
16815 ) {
16816 let mut editor_settings = EditorSettings::get_global(cx).clone();
16817 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16818 EditorSettings::override_global(editor_settings, cx);
16819 }
16820
16821 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16822 if let Some(show_line_numbers) = self.show_line_numbers {
16823 return show_line_numbers;
16824 }
16825 EditorSettings::get_global(cx).gutter.line_numbers
16826 }
16827
16828 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16829 self.use_relative_line_numbers
16830 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16831 }
16832
16833 pub fn toggle_relative_line_numbers(
16834 &mut self,
16835 _: &ToggleRelativeLineNumbers,
16836 _: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) {
16839 let is_relative = self.should_use_relative_line_numbers(cx);
16840 self.set_relative_line_number(Some(!is_relative), cx)
16841 }
16842
16843 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16844 self.use_relative_line_numbers = is_relative;
16845 cx.notify();
16846 }
16847
16848 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16849 self.show_gutter = show_gutter;
16850 cx.notify();
16851 }
16852
16853 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16854 self.show_scrollbars = show_scrollbars;
16855 cx.notify();
16856 }
16857
16858 pub fn set_minimap_visibility(
16859 &mut self,
16860 minimap_visibility: MinimapVisibility,
16861 window: &mut Window,
16862 cx: &mut Context<Self>,
16863 ) {
16864 if self.minimap_visibility != minimap_visibility {
16865 if minimap_visibility.visible() && self.minimap.is_none() {
16866 let minimap_settings = EditorSettings::get_global(cx).minimap;
16867 self.minimap =
16868 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16869 }
16870 self.minimap_visibility = minimap_visibility;
16871 cx.notify();
16872 }
16873 }
16874
16875 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16876 self.set_show_scrollbars(false, cx);
16877 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16878 }
16879
16880 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16881 self.show_line_numbers = Some(show_line_numbers);
16882 cx.notify();
16883 }
16884
16885 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16886 self.disable_expand_excerpt_buttons = true;
16887 cx.notify();
16888 }
16889
16890 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16891 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16892 cx.notify();
16893 }
16894
16895 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16896 self.show_code_actions = Some(show_code_actions);
16897 cx.notify();
16898 }
16899
16900 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16901 self.show_runnables = Some(show_runnables);
16902 cx.notify();
16903 }
16904
16905 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16906 self.show_breakpoints = Some(show_breakpoints);
16907 cx.notify();
16908 }
16909
16910 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16911 if self.display_map.read(cx).masked != masked {
16912 self.display_map.update(cx, |map, _| map.masked = masked);
16913 }
16914 cx.notify()
16915 }
16916
16917 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16918 self.show_wrap_guides = Some(show_wrap_guides);
16919 cx.notify();
16920 }
16921
16922 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16923 self.show_indent_guides = Some(show_indent_guides);
16924 cx.notify();
16925 }
16926
16927 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16928 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16929 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16930 if let Some(dir) = file.abs_path(cx).parent() {
16931 return Some(dir.to_owned());
16932 }
16933 }
16934
16935 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16936 return Some(project_path.path.to_path_buf());
16937 }
16938 }
16939
16940 None
16941 }
16942
16943 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16944 self.active_excerpt(cx)?
16945 .1
16946 .read(cx)
16947 .file()
16948 .and_then(|f| f.as_local())
16949 }
16950
16951 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16952 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16953 let buffer = buffer.read(cx);
16954 if let Some(project_path) = buffer.project_path(cx) {
16955 let project = self.project.as_ref()?.read(cx);
16956 project.absolute_path(&project_path, cx)
16957 } else {
16958 buffer
16959 .file()
16960 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16961 }
16962 })
16963 }
16964
16965 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16966 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16967 let project_path = buffer.read(cx).project_path(cx)?;
16968 let project = self.project.as_ref()?.read(cx);
16969 let entry = project.entry_for_path(&project_path, cx)?;
16970 let path = entry.path.to_path_buf();
16971 Some(path)
16972 })
16973 }
16974
16975 pub fn reveal_in_finder(
16976 &mut self,
16977 _: &RevealInFileManager,
16978 _window: &mut Window,
16979 cx: &mut Context<Self>,
16980 ) {
16981 if let Some(target) = self.target_file(cx) {
16982 cx.reveal_path(&target.abs_path(cx));
16983 }
16984 }
16985
16986 pub fn copy_path(
16987 &mut self,
16988 _: &zed_actions::workspace::CopyPath,
16989 _window: &mut Window,
16990 cx: &mut Context<Self>,
16991 ) {
16992 if let Some(path) = self.target_file_abs_path(cx) {
16993 if let Some(path) = path.to_str() {
16994 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16995 }
16996 }
16997 }
16998
16999 pub fn copy_relative_path(
17000 &mut self,
17001 _: &zed_actions::workspace::CopyRelativePath,
17002 _window: &mut Window,
17003 cx: &mut Context<Self>,
17004 ) {
17005 if let Some(path) = self.target_file_path(cx) {
17006 if let Some(path) = path.to_str() {
17007 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17008 }
17009 }
17010 }
17011
17012 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17013 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17014 buffer.read(cx).project_path(cx)
17015 } else {
17016 None
17017 }
17018 }
17019
17020 // Returns true if the editor handled a go-to-line request
17021 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17022 maybe!({
17023 let breakpoint_store = self.breakpoint_store.as_ref()?;
17024
17025 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17026 else {
17027 self.clear_row_highlights::<ActiveDebugLine>();
17028 return None;
17029 };
17030
17031 let position = active_stack_frame.position;
17032 let buffer_id = position.buffer_id?;
17033 let snapshot = self
17034 .project
17035 .as_ref()?
17036 .read(cx)
17037 .buffer_for_id(buffer_id, cx)?
17038 .read(cx)
17039 .snapshot();
17040
17041 let mut handled = false;
17042 for (id, ExcerptRange { context, .. }) in
17043 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17044 {
17045 if context.start.cmp(&position, &snapshot).is_ge()
17046 || context.end.cmp(&position, &snapshot).is_lt()
17047 {
17048 continue;
17049 }
17050 let snapshot = self.buffer.read(cx).snapshot(cx);
17051 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17052
17053 handled = true;
17054 self.clear_row_highlights::<ActiveDebugLine>();
17055
17056 self.go_to_line::<ActiveDebugLine>(
17057 multibuffer_anchor,
17058 Some(cx.theme().colors().editor_debugger_active_line_background),
17059 window,
17060 cx,
17061 );
17062
17063 cx.notify();
17064 }
17065
17066 handled.then_some(())
17067 })
17068 .is_some()
17069 }
17070
17071 pub fn copy_file_name_without_extension(
17072 &mut self,
17073 _: &CopyFileNameWithoutExtension,
17074 _: &mut Window,
17075 cx: &mut Context<Self>,
17076 ) {
17077 if let Some(file) = self.target_file(cx) {
17078 if let Some(file_stem) = file.path().file_stem() {
17079 if let Some(name) = file_stem.to_str() {
17080 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17081 }
17082 }
17083 }
17084 }
17085
17086 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17087 if let Some(file) = self.target_file(cx) {
17088 if let Some(file_name) = file.path().file_name() {
17089 if let Some(name) = file_name.to_str() {
17090 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17091 }
17092 }
17093 }
17094 }
17095
17096 pub fn toggle_git_blame(
17097 &mut self,
17098 _: &::git::Blame,
17099 window: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) {
17102 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17103
17104 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17105 self.start_git_blame(true, window, cx);
17106 }
17107
17108 cx.notify();
17109 }
17110
17111 pub fn toggle_git_blame_inline(
17112 &mut self,
17113 _: &ToggleGitBlameInline,
17114 window: &mut Window,
17115 cx: &mut Context<Self>,
17116 ) {
17117 self.toggle_git_blame_inline_internal(true, window, cx);
17118 cx.notify();
17119 }
17120
17121 pub fn open_git_blame_commit(
17122 &mut self,
17123 _: &OpenGitBlameCommit,
17124 window: &mut Window,
17125 cx: &mut Context<Self>,
17126 ) {
17127 self.open_git_blame_commit_internal(window, cx);
17128 }
17129
17130 fn open_git_blame_commit_internal(
17131 &mut self,
17132 window: &mut Window,
17133 cx: &mut Context<Self>,
17134 ) -> Option<()> {
17135 let blame = self.blame.as_ref()?;
17136 let snapshot = self.snapshot(window, cx);
17137 let cursor = self.selections.newest::<Point>(cx).head();
17138 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17139 let blame_entry = blame
17140 .update(cx, |blame, cx| {
17141 blame
17142 .blame_for_rows(
17143 &[RowInfo {
17144 buffer_id: Some(buffer.remote_id()),
17145 buffer_row: Some(point.row),
17146 ..Default::default()
17147 }],
17148 cx,
17149 )
17150 .next()
17151 })
17152 .flatten()?;
17153 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17154 let repo = blame.read(cx).repository(cx)?;
17155 let workspace = self.workspace()?.downgrade();
17156 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17157 None
17158 }
17159
17160 pub fn git_blame_inline_enabled(&self) -> bool {
17161 self.git_blame_inline_enabled
17162 }
17163
17164 pub fn toggle_selection_menu(
17165 &mut self,
17166 _: &ToggleSelectionMenu,
17167 _: &mut Window,
17168 cx: &mut Context<Self>,
17169 ) {
17170 self.show_selection_menu = self
17171 .show_selection_menu
17172 .map(|show_selections_menu| !show_selections_menu)
17173 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17174
17175 cx.notify();
17176 }
17177
17178 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17179 self.show_selection_menu
17180 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17181 }
17182
17183 fn start_git_blame(
17184 &mut self,
17185 user_triggered: bool,
17186 window: &mut Window,
17187 cx: &mut Context<Self>,
17188 ) {
17189 if let Some(project) = self.project.as_ref() {
17190 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17191 return;
17192 };
17193
17194 if buffer.read(cx).file().is_none() {
17195 return;
17196 }
17197
17198 let focused = self.focus_handle(cx).contains_focused(window, cx);
17199
17200 let project = project.clone();
17201 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17202 self.blame_subscription =
17203 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17204 self.blame = Some(blame);
17205 }
17206 }
17207
17208 fn toggle_git_blame_inline_internal(
17209 &mut self,
17210 user_triggered: bool,
17211 window: &mut Window,
17212 cx: &mut Context<Self>,
17213 ) {
17214 if self.git_blame_inline_enabled {
17215 self.git_blame_inline_enabled = false;
17216 self.show_git_blame_inline = false;
17217 self.show_git_blame_inline_delay_task.take();
17218 } else {
17219 self.git_blame_inline_enabled = true;
17220 self.start_git_blame_inline(user_triggered, window, cx);
17221 }
17222
17223 cx.notify();
17224 }
17225
17226 fn start_git_blame_inline(
17227 &mut self,
17228 user_triggered: bool,
17229 window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) {
17232 self.start_git_blame(user_triggered, window, cx);
17233
17234 if ProjectSettings::get_global(cx)
17235 .git
17236 .inline_blame_delay()
17237 .is_some()
17238 {
17239 self.start_inline_blame_timer(window, cx);
17240 } else {
17241 self.show_git_blame_inline = true
17242 }
17243 }
17244
17245 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17246 self.blame.as_ref()
17247 }
17248
17249 pub fn show_git_blame_gutter(&self) -> bool {
17250 self.show_git_blame_gutter
17251 }
17252
17253 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17254 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17255 }
17256
17257 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17258 self.show_git_blame_inline
17259 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17260 && !self.newest_selection_head_on_empty_line(cx)
17261 && self.has_blame_entries(cx)
17262 }
17263
17264 fn has_blame_entries(&self, cx: &App) -> bool {
17265 self.blame()
17266 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17267 }
17268
17269 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17270 let cursor_anchor = self.selections.newest_anchor().head();
17271
17272 let snapshot = self.buffer.read(cx).snapshot(cx);
17273 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17274
17275 snapshot.line_len(buffer_row) == 0
17276 }
17277
17278 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17279 let buffer_and_selection = maybe!({
17280 let selection = self.selections.newest::<Point>(cx);
17281 let selection_range = selection.range();
17282
17283 let multi_buffer = self.buffer().read(cx);
17284 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17285 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17286
17287 let (buffer, range, _) = if selection.reversed {
17288 buffer_ranges.first()
17289 } else {
17290 buffer_ranges.last()
17291 }?;
17292
17293 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17294 ..text::ToPoint::to_point(&range.end, &buffer).row;
17295 Some((
17296 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17297 selection,
17298 ))
17299 });
17300
17301 let Some((buffer, selection)) = buffer_and_selection else {
17302 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17303 };
17304
17305 let Some(project) = self.project.as_ref() else {
17306 return Task::ready(Err(anyhow!("editor does not have project")));
17307 };
17308
17309 project.update(cx, |project, cx| {
17310 project.get_permalink_to_line(&buffer, selection, cx)
17311 })
17312 }
17313
17314 pub fn copy_permalink_to_line(
17315 &mut self,
17316 _: &CopyPermalinkToLine,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) {
17320 let permalink_task = self.get_permalink_to_line(cx);
17321 let workspace = self.workspace();
17322
17323 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17324 Ok(permalink) => {
17325 cx.update(|_, cx| {
17326 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17327 })
17328 .ok();
17329 }
17330 Err(err) => {
17331 let message = format!("Failed to copy permalink: {err}");
17332
17333 anyhow::Result::<()>::Err(err).log_err();
17334
17335 if let Some(workspace) = workspace {
17336 workspace
17337 .update_in(cx, |workspace, _, cx| {
17338 struct CopyPermalinkToLine;
17339
17340 workspace.show_toast(
17341 Toast::new(
17342 NotificationId::unique::<CopyPermalinkToLine>(),
17343 message,
17344 ),
17345 cx,
17346 )
17347 })
17348 .ok();
17349 }
17350 }
17351 })
17352 .detach();
17353 }
17354
17355 pub fn copy_file_location(
17356 &mut self,
17357 _: &CopyFileLocation,
17358 _: &mut Window,
17359 cx: &mut Context<Self>,
17360 ) {
17361 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17362 if let Some(file) = self.target_file(cx) {
17363 if let Some(path) = file.path().to_str() {
17364 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17365 }
17366 }
17367 }
17368
17369 pub fn open_permalink_to_line(
17370 &mut self,
17371 _: &OpenPermalinkToLine,
17372 window: &mut Window,
17373 cx: &mut Context<Self>,
17374 ) {
17375 let permalink_task = self.get_permalink_to_line(cx);
17376 let workspace = self.workspace();
17377
17378 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17379 Ok(permalink) => {
17380 cx.update(|_, cx| {
17381 cx.open_url(permalink.as_ref());
17382 })
17383 .ok();
17384 }
17385 Err(err) => {
17386 let message = format!("Failed to open permalink: {err}");
17387
17388 anyhow::Result::<()>::Err(err).log_err();
17389
17390 if let Some(workspace) = workspace {
17391 workspace
17392 .update(cx, |workspace, cx| {
17393 struct OpenPermalinkToLine;
17394
17395 workspace.show_toast(
17396 Toast::new(
17397 NotificationId::unique::<OpenPermalinkToLine>(),
17398 message,
17399 ),
17400 cx,
17401 )
17402 })
17403 .ok();
17404 }
17405 }
17406 })
17407 .detach();
17408 }
17409
17410 pub fn insert_uuid_v4(
17411 &mut self,
17412 _: &InsertUuidV4,
17413 window: &mut Window,
17414 cx: &mut Context<Self>,
17415 ) {
17416 self.insert_uuid(UuidVersion::V4, window, cx);
17417 }
17418
17419 pub fn insert_uuid_v7(
17420 &mut self,
17421 _: &InsertUuidV7,
17422 window: &mut Window,
17423 cx: &mut Context<Self>,
17424 ) {
17425 self.insert_uuid(UuidVersion::V7, window, cx);
17426 }
17427
17428 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17429 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17430 self.transact(window, cx, |this, window, cx| {
17431 let edits = this
17432 .selections
17433 .all::<Point>(cx)
17434 .into_iter()
17435 .map(|selection| {
17436 let uuid = match version {
17437 UuidVersion::V4 => uuid::Uuid::new_v4(),
17438 UuidVersion::V7 => uuid::Uuid::now_v7(),
17439 };
17440
17441 (selection.range(), uuid.to_string())
17442 });
17443 this.edit(edits, cx);
17444 this.refresh_inline_completion(true, false, window, cx);
17445 });
17446 }
17447
17448 pub fn open_selections_in_multibuffer(
17449 &mut self,
17450 _: &OpenSelectionsInMultibuffer,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) {
17454 let multibuffer = self.buffer.read(cx);
17455
17456 let Some(buffer) = multibuffer.as_singleton() else {
17457 return;
17458 };
17459
17460 let Some(workspace) = self.workspace() else {
17461 return;
17462 };
17463
17464 let locations = self
17465 .selections
17466 .disjoint_anchors()
17467 .iter()
17468 .map(|range| Location {
17469 buffer: buffer.clone(),
17470 range: range.start.text_anchor..range.end.text_anchor,
17471 })
17472 .collect::<Vec<_>>();
17473
17474 let title = multibuffer.title(cx).to_string();
17475
17476 cx.spawn_in(window, async move |_, cx| {
17477 workspace.update_in(cx, |workspace, window, cx| {
17478 Self::open_locations_in_multibuffer(
17479 workspace,
17480 locations,
17481 format!("Selections for '{title}'"),
17482 false,
17483 MultibufferSelectionMode::All,
17484 window,
17485 cx,
17486 );
17487 })
17488 })
17489 .detach();
17490 }
17491
17492 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17493 /// last highlight added will be used.
17494 ///
17495 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17496 pub fn highlight_rows<T: 'static>(
17497 &mut self,
17498 range: Range<Anchor>,
17499 color: Hsla,
17500 options: RowHighlightOptions,
17501 cx: &mut Context<Self>,
17502 ) {
17503 let snapshot = self.buffer().read(cx).snapshot(cx);
17504 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17505 let ix = row_highlights.binary_search_by(|highlight| {
17506 Ordering::Equal
17507 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17508 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17509 });
17510
17511 if let Err(mut ix) = ix {
17512 let index = post_inc(&mut self.highlight_order);
17513
17514 // If this range intersects with the preceding highlight, then merge it with
17515 // the preceding highlight. Otherwise insert a new highlight.
17516 let mut merged = false;
17517 if ix > 0 {
17518 let prev_highlight = &mut row_highlights[ix - 1];
17519 if prev_highlight
17520 .range
17521 .end
17522 .cmp(&range.start, &snapshot)
17523 .is_ge()
17524 {
17525 ix -= 1;
17526 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17527 prev_highlight.range.end = range.end;
17528 }
17529 merged = true;
17530 prev_highlight.index = index;
17531 prev_highlight.color = color;
17532 prev_highlight.options = options;
17533 }
17534 }
17535
17536 if !merged {
17537 row_highlights.insert(
17538 ix,
17539 RowHighlight {
17540 range: range.clone(),
17541 index,
17542 color,
17543 options,
17544 type_id: TypeId::of::<T>(),
17545 },
17546 );
17547 }
17548
17549 // If any of the following highlights intersect with this one, merge them.
17550 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17551 let highlight = &row_highlights[ix];
17552 if next_highlight
17553 .range
17554 .start
17555 .cmp(&highlight.range.end, &snapshot)
17556 .is_le()
17557 {
17558 if next_highlight
17559 .range
17560 .end
17561 .cmp(&highlight.range.end, &snapshot)
17562 .is_gt()
17563 {
17564 row_highlights[ix].range.end = next_highlight.range.end;
17565 }
17566 row_highlights.remove(ix + 1);
17567 } else {
17568 break;
17569 }
17570 }
17571 }
17572 }
17573
17574 /// Remove any highlighted row ranges of the given type that intersect the
17575 /// given ranges.
17576 pub fn remove_highlighted_rows<T: 'static>(
17577 &mut self,
17578 ranges_to_remove: Vec<Range<Anchor>>,
17579 cx: &mut Context<Self>,
17580 ) {
17581 let snapshot = self.buffer().read(cx).snapshot(cx);
17582 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17583 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17584 row_highlights.retain(|highlight| {
17585 while let Some(range_to_remove) = ranges_to_remove.peek() {
17586 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17587 Ordering::Less | Ordering::Equal => {
17588 ranges_to_remove.next();
17589 }
17590 Ordering::Greater => {
17591 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17592 Ordering::Less | Ordering::Equal => {
17593 return false;
17594 }
17595 Ordering::Greater => break,
17596 }
17597 }
17598 }
17599 }
17600
17601 true
17602 })
17603 }
17604
17605 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17606 pub fn clear_row_highlights<T: 'static>(&mut self) {
17607 self.highlighted_rows.remove(&TypeId::of::<T>());
17608 }
17609
17610 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17611 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17612 self.highlighted_rows
17613 .get(&TypeId::of::<T>())
17614 .map_or(&[] as &[_], |vec| vec.as_slice())
17615 .iter()
17616 .map(|highlight| (highlight.range.clone(), highlight.color))
17617 }
17618
17619 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17620 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17621 /// Allows to ignore certain kinds of highlights.
17622 pub fn highlighted_display_rows(
17623 &self,
17624 window: &mut Window,
17625 cx: &mut App,
17626 ) -> BTreeMap<DisplayRow, LineHighlight> {
17627 let snapshot = self.snapshot(window, cx);
17628 let mut used_highlight_orders = HashMap::default();
17629 self.highlighted_rows
17630 .iter()
17631 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17632 .fold(
17633 BTreeMap::<DisplayRow, LineHighlight>::new(),
17634 |mut unique_rows, highlight| {
17635 let start = highlight.range.start.to_display_point(&snapshot);
17636 let end = highlight.range.end.to_display_point(&snapshot);
17637 let start_row = start.row().0;
17638 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17639 && end.column() == 0
17640 {
17641 end.row().0.saturating_sub(1)
17642 } else {
17643 end.row().0
17644 };
17645 for row in start_row..=end_row {
17646 let used_index =
17647 used_highlight_orders.entry(row).or_insert(highlight.index);
17648 if highlight.index >= *used_index {
17649 *used_index = highlight.index;
17650 unique_rows.insert(
17651 DisplayRow(row),
17652 LineHighlight {
17653 include_gutter: highlight.options.include_gutter,
17654 border: None,
17655 background: highlight.color.into(),
17656 type_id: Some(highlight.type_id),
17657 },
17658 );
17659 }
17660 }
17661 unique_rows
17662 },
17663 )
17664 }
17665
17666 pub fn highlighted_display_row_for_autoscroll(
17667 &self,
17668 snapshot: &DisplaySnapshot,
17669 ) -> Option<DisplayRow> {
17670 self.highlighted_rows
17671 .values()
17672 .flat_map(|highlighted_rows| highlighted_rows.iter())
17673 .filter_map(|highlight| {
17674 if highlight.options.autoscroll {
17675 Some(highlight.range.start.to_display_point(snapshot).row())
17676 } else {
17677 None
17678 }
17679 })
17680 .min()
17681 }
17682
17683 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17684 self.highlight_background::<SearchWithinRange>(
17685 ranges,
17686 |colors| colors.editor_document_highlight_read_background,
17687 cx,
17688 )
17689 }
17690
17691 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17692 self.breadcrumb_header = Some(new_header);
17693 }
17694
17695 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17696 self.clear_background_highlights::<SearchWithinRange>(cx);
17697 }
17698
17699 pub fn highlight_background<T: 'static>(
17700 &mut self,
17701 ranges: &[Range<Anchor>],
17702 color_fetcher: fn(&ThemeColors) -> Hsla,
17703 cx: &mut Context<Self>,
17704 ) {
17705 self.background_highlights
17706 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17707 self.scrollbar_marker_state.dirty = true;
17708 cx.notify();
17709 }
17710
17711 pub fn clear_background_highlights<T: 'static>(
17712 &mut self,
17713 cx: &mut Context<Self>,
17714 ) -> Option<BackgroundHighlight> {
17715 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17716 if !text_highlights.1.is_empty() {
17717 self.scrollbar_marker_state.dirty = true;
17718 cx.notify();
17719 }
17720 Some(text_highlights)
17721 }
17722
17723 pub fn highlight_gutter<T: 'static>(
17724 &mut self,
17725 ranges: &[Range<Anchor>],
17726 color_fetcher: fn(&App) -> Hsla,
17727 cx: &mut Context<Self>,
17728 ) {
17729 self.gutter_highlights
17730 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17731 cx.notify();
17732 }
17733
17734 pub fn clear_gutter_highlights<T: 'static>(
17735 &mut self,
17736 cx: &mut Context<Self>,
17737 ) -> Option<GutterHighlight> {
17738 cx.notify();
17739 self.gutter_highlights.remove(&TypeId::of::<T>())
17740 }
17741
17742 #[cfg(feature = "test-support")]
17743 pub fn all_text_background_highlights(
17744 &self,
17745 window: &mut Window,
17746 cx: &mut Context<Self>,
17747 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17748 let snapshot = self.snapshot(window, cx);
17749 let buffer = &snapshot.buffer_snapshot;
17750 let start = buffer.anchor_before(0);
17751 let end = buffer.anchor_after(buffer.len());
17752 let theme = cx.theme().colors();
17753 self.background_highlights_in_range(start..end, &snapshot, theme)
17754 }
17755
17756 #[cfg(feature = "test-support")]
17757 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17758 let snapshot = self.buffer().read(cx).snapshot(cx);
17759
17760 let highlights = self
17761 .background_highlights
17762 .get(&TypeId::of::<items::BufferSearchHighlights>());
17763
17764 if let Some((_color, ranges)) = highlights {
17765 ranges
17766 .iter()
17767 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17768 .collect_vec()
17769 } else {
17770 vec![]
17771 }
17772 }
17773
17774 fn document_highlights_for_position<'a>(
17775 &'a self,
17776 position: Anchor,
17777 buffer: &'a MultiBufferSnapshot,
17778 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17779 let read_highlights = self
17780 .background_highlights
17781 .get(&TypeId::of::<DocumentHighlightRead>())
17782 .map(|h| &h.1);
17783 let write_highlights = self
17784 .background_highlights
17785 .get(&TypeId::of::<DocumentHighlightWrite>())
17786 .map(|h| &h.1);
17787 let left_position = position.bias_left(buffer);
17788 let right_position = position.bias_right(buffer);
17789 read_highlights
17790 .into_iter()
17791 .chain(write_highlights)
17792 .flat_map(move |ranges| {
17793 let start_ix = match ranges.binary_search_by(|probe| {
17794 let cmp = probe.end.cmp(&left_position, buffer);
17795 if cmp.is_ge() {
17796 Ordering::Greater
17797 } else {
17798 Ordering::Less
17799 }
17800 }) {
17801 Ok(i) | Err(i) => i,
17802 };
17803
17804 ranges[start_ix..]
17805 .iter()
17806 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17807 })
17808 }
17809
17810 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17811 self.background_highlights
17812 .get(&TypeId::of::<T>())
17813 .map_or(false, |(_, highlights)| !highlights.is_empty())
17814 }
17815
17816 pub fn background_highlights_in_range(
17817 &self,
17818 search_range: Range<Anchor>,
17819 display_snapshot: &DisplaySnapshot,
17820 theme: &ThemeColors,
17821 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17822 let mut results = Vec::new();
17823 for (color_fetcher, ranges) in self.background_highlights.values() {
17824 let color = color_fetcher(theme);
17825 let start_ix = match ranges.binary_search_by(|probe| {
17826 let cmp = probe
17827 .end
17828 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17829 if cmp.is_gt() {
17830 Ordering::Greater
17831 } else {
17832 Ordering::Less
17833 }
17834 }) {
17835 Ok(i) | Err(i) => i,
17836 };
17837 for range in &ranges[start_ix..] {
17838 if range
17839 .start
17840 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17841 .is_ge()
17842 {
17843 break;
17844 }
17845
17846 let start = range.start.to_display_point(display_snapshot);
17847 let end = range.end.to_display_point(display_snapshot);
17848 results.push((start..end, color))
17849 }
17850 }
17851 results
17852 }
17853
17854 pub fn background_highlight_row_ranges<T: 'static>(
17855 &self,
17856 search_range: Range<Anchor>,
17857 display_snapshot: &DisplaySnapshot,
17858 count: usize,
17859 ) -> Vec<RangeInclusive<DisplayPoint>> {
17860 let mut results = Vec::new();
17861 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17862 return vec![];
17863 };
17864
17865 let start_ix = match ranges.binary_search_by(|probe| {
17866 let cmp = probe
17867 .end
17868 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17869 if cmp.is_gt() {
17870 Ordering::Greater
17871 } else {
17872 Ordering::Less
17873 }
17874 }) {
17875 Ok(i) | Err(i) => i,
17876 };
17877 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17878 if let (Some(start_display), Some(end_display)) = (start, end) {
17879 results.push(
17880 start_display.to_display_point(display_snapshot)
17881 ..=end_display.to_display_point(display_snapshot),
17882 );
17883 }
17884 };
17885 let mut start_row: Option<Point> = None;
17886 let mut end_row: Option<Point> = None;
17887 if ranges.len() > count {
17888 return Vec::new();
17889 }
17890 for range in &ranges[start_ix..] {
17891 if range
17892 .start
17893 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17894 .is_ge()
17895 {
17896 break;
17897 }
17898 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17899 if let Some(current_row) = &end_row {
17900 if end.row == current_row.row {
17901 continue;
17902 }
17903 }
17904 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17905 if start_row.is_none() {
17906 assert_eq!(end_row, None);
17907 start_row = Some(start);
17908 end_row = Some(end);
17909 continue;
17910 }
17911 if let Some(current_end) = end_row.as_mut() {
17912 if start.row > current_end.row + 1 {
17913 push_region(start_row, end_row);
17914 start_row = Some(start);
17915 end_row = Some(end);
17916 } else {
17917 // Merge two hunks.
17918 *current_end = end;
17919 }
17920 } else {
17921 unreachable!();
17922 }
17923 }
17924 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17925 push_region(start_row, end_row);
17926 results
17927 }
17928
17929 pub fn gutter_highlights_in_range(
17930 &self,
17931 search_range: Range<Anchor>,
17932 display_snapshot: &DisplaySnapshot,
17933 cx: &App,
17934 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17935 let mut results = Vec::new();
17936 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17937 let color = color_fetcher(cx);
17938 let start_ix = match ranges.binary_search_by(|probe| {
17939 let cmp = probe
17940 .end
17941 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17942 if cmp.is_gt() {
17943 Ordering::Greater
17944 } else {
17945 Ordering::Less
17946 }
17947 }) {
17948 Ok(i) | Err(i) => i,
17949 };
17950 for range in &ranges[start_ix..] {
17951 if range
17952 .start
17953 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17954 .is_ge()
17955 {
17956 break;
17957 }
17958
17959 let start = range.start.to_display_point(display_snapshot);
17960 let end = range.end.to_display_point(display_snapshot);
17961 results.push((start..end, color))
17962 }
17963 }
17964 results
17965 }
17966
17967 /// Get the text ranges corresponding to the redaction query
17968 pub fn redacted_ranges(
17969 &self,
17970 search_range: Range<Anchor>,
17971 display_snapshot: &DisplaySnapshot,
17972 cx: &App,
17973 ) -> Vec<Range<DisplayPoint>> {
17974 display_snapshot
17975 .buffer_snapshot
17976 .redacted_ranges(search_range, |file| {
17977 if let Some(file) = file {
17978 file.is_private()
17979 && EditorSettings::get(
17980 Some(SettingsLocation {
17981 worktree_id: file.worktree_id(cx),
17982 path: file.path().as_ref(),
17983 }),
17984 cx,
17985 )
17986 .redact_private_values
17987 } else {
17988 false
17989 }
17990 })
17991 .map(|range| {
17992 range.start.to_display_point(display_snapshot)
17993 ..range.end.to_display_point(display_snapshot)
17994 })
17995 .collect()
17996 }
17997
17998 pub fn highlight_text<T: 'static>(
17999 &mut self,
18000 ranges: Vec<Range<Anchor>>,
18001 style: HighlightStyle,
18002 cx: &mut Context<Self>,
18003 ) {
18004 self.display_map.update(cx, |map, _| {
18005 map.highlight_text(TypeId::of::<T>(), ranges, style)
18006 });
18007 cx.notify();
18008 }
18009
18010 pub(crate) fn highlight_inlays<T: 'static>(
18011 &mut self,
18012 highlights: Vec<InlayHighlight>,
18013 style: HighlightStyle,
18014 cx: &mut Context<Self>,
18015 ) {
18016 self.display_map.update(cx, |map, _| {
18017 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18018 });
18019 cx.notify();
18020 }
18021
18022 pub fn text_highlights<'a, T: 'static>(
18023 &'a self,
18024 cx: &'a App,
18025 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18026 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18027 }
18028
18029 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18030 let cleared = self
18031 .display_map
18032 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18033 if cleared {
18034 cx.notify();
18035 }
18036 }
18037
18038 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18039 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18040 && self.focus_handle.is_focused(window)
18041 }
18042
18043 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18044 self.show_cursor_when_unfocused = is_enabled;
18045 cx.notify();
18046 }
18047
18048 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18049 cx.notify();
18050 }
18051
18052 fn on_debug_session_event(
18053 &mut self,
18054 _session: Entity<Session>,
18055 event: &SessionEvent,
18056 cx: &mut Context<Self>,
18057 ) {
18058 match event {
18059 SessionEvent::InvalidateInlineValue => {
18060 self.refresh_inline_values(cx);
18061 }
18062 _ => {}
18063 }
18064 }
18065
18066 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18067 let Some(project) = self.project.clone() else {
18068 return;
18069 };
18070
18071 if !self.inline_value_cache.enabled {
18072 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18073 self.splice_inlays(&inlays, Vec::new(), cx);
18074 return;
18075 }
18076
18077 let current_execution_position = self
18078 .highlighted_rows
18079 .get(&TypeId::of::<ActiveDebugLine>())
18080 .and_then(|lines| lines.last().map(|line| line.range.start));
18081
18082 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18083 let inline_values = editor
18084 .update(cx, |editor, cx| {
18085 let Some(current_execution_position) = current_execution_position else {
18086 return Some(Task::ready(Ok(Vec::new())));
18087 };
18088
18089 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18090 let snapshot = buffer.snapshot(cx);
18091
18092 let excerpt = snapshot.excerpt_containing(
18093 current_execution_position..current_execution_position,
18094 )?;
18095
18096 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18097 })?;
18098
18099 let range =
18100 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18101
18102 project.inline_values(buffer, range, cx)
18103 })
18104 .ok()
18105 .flatten()?
18106 .await
18107 .context("refreshing debugger inlays")
18108 .log_err()?;
18109
18110 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18111
18112 for (buffer_id, inline_value) in inline_values
18113 .into_iter()
18114 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18115 {
18116 buffer_inline_values
18117 .entry(buffer_id)
18118 .or_default()
18119 .push(inline_value);
18120 }
18121
18122 editor
18123 .update(cx, |editor, cx| {
18124 let snapshot = editor.buffer.read(cx).snapshot(cx);
18125 let mut new_inlays = Vec::default();
18126
18127 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18128 let buffer_id = buffer_snapshot.remote_id();
18129 buffer_inline_values
18130 .get(&buffer_id)
18131 .into_iter()
18132 .flatten()
18133 .for_each(|hint| {
18134 let inlay = Inlay::debugger_hint(
18135 post_inc(&mut editor.next_inlay_id),
18136 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18137 hint.text(),
18138 );
18139
18140 new_inlays.push(inlay);
18141 });
18142 }
18143
18144 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18145 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18146
18147 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18148 })
18149 .ok()?;
18150 Some(())
18151 });
18152 }
18153
18154 fn on_buffer_event(
18155 &mut self,
18156 multibuffer: &Entity<MultiBuffer>,
18157 event: &multi_buffer::Event,
18158 window: &mut Window,
18159 cx: &mut Context<Self>,
18160 ) {
18161 match event {
18162 multi_buffer::Event::Edited {
18163 singleton_buffer_edited,
18164 edited_buffer: buffer_edited,
18165 } => {
18166 self.scrollbar_marker_state.dirty = true;
18167 self.active_indent_guides_state.dirty = true;
18168 self.refresh_active_diagnostics(cx);
18169 self.refresh_code_actions(window, cx);
18170 self.refresh_selected_text_highlights(true, window, cx);
18171 refresh_matching_bracket_highlights(self, window, cx);
18172 if self.has_active_inline_completion() {
18173 self.update_visible_inline_completion(window, cx);
18174 }
18175 if let Some(buffer) = buffer_edited {
18176 let buffer_id = buffer.read(cx).remote_id();
18177 if !self.registered_buffers.contains_key(&buffer_id) {
18178 if let Some(project) = self.project.as_ref() {
18179 project.update(cx, |project, cx| {
18180 self.registered_buffers.insert(
18181 buffer_id,
18182 project.register_buffer_with_language_servers(&buffer, cx),
18183 );
18184 })
18185 }
18186 }
18187 }
18188 cx.emit(EditorEvent::BufferEdited);
18189 cx.emit(SearchEvent::MatchesInvalidated);
18190 if *singleton_buffer_edited {
18191 if let Some(project) = &self.project {
18192 #[allow(clippy::mutable_key_type)]
18193 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18194 multibuffer
18195 .all_buffers()
18196 .into_iter()
18197 .filter_map(|buffer| {
18198 buffer.update(cx, |buffer, cx| {
18199 let language = buffer.language()?;
18200 let should_discard = project.update(cx, |project, cx| {
18201 project.is_local()
18202 && !project.has_language_servers_for(buffer, cx)
18203 });
18204 should_discard.not().then_some(language.clone())
18205 })
18206 })
18207 .collect::<HashSet<_>>()
18208 });
18209 if !languages_affected.is_empty() {
18210 self.refresh_inlay_hints(
18211 InlayHintRefreshReason::BufferEdited(languages_affected),
18212 cx,
18213 );
18214 }
18215 }
18216 }
18217
18218 let Some(project) = &self.project else { return };
18219 let (telemetry, is_via_ssh) = {
18220 let project = project.read(cx);
18221 let telemetry = project.client().telemetry().clone();
18222 let is_via_ssh = project.is_via_ssh();
18223 (telemetry, is_via_ssh)
18224 };
18225 refresh_linked_ranges(self, window, cx);
18226 telemetry.log_edit_event("editor", is_via_ssh);
18227 }
18228 multi_buffer::Event::ExcerptsAdded {
18229 buffer,
18230 predecessor,
18231 excerpts,
18232 } => {
18233 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18234 let buffer_id = buffer.read(cx).remote_id();
18235 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18236 if let Some(project) = &self.project {
18237 update_uncommitted_diff_for_buffer(
18238 cx.entity(),
18239 project,
18240 [buffer.clone()],
18241 self.buffer.clone(),
18242 cx,
18243 )
18244 .detach();
18245 }
18246 }
18247 cx.emit(EditorEvent::ExcerptsAdded {
18248 buffer: buffer.clone(),
18249 predecessor: *predecessor,
18250 excerpts: excerpts.clone(),
18251 });
18252 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18253 }
18254 multi_buffer::Event::ExcerptsRemoved {
18255 ids,
18256 removed_buffer_ids,
18257 } => {
18258 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18259 let buffer = self.buffer.read(cx);
18260 self.registered_buffers
18261 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18262 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18263 cx.emit(EditorEvent::ExcerptsRemoved {
18264 ids: ids.clone(),
18265 removed_buffer_ids: removed_buffer_ids.clone(),
18266 })
18267 }
18268 multi_buffer::Event::ExcerptsEdited {
18269 excerpt_ids,
18270 buffer_ids,
18271 } => {
18272 self.display_map.update(cx, |map, cx| {
18273 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18274 });
18275 cx.emit(EditorEvent::ExcerptsEdited {
18276 ids: excerpt_ids.clone(),
18277 })
18278 }
18279 multi_buffer::Event::ExcerptsExpanded { ids } => {
18280 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18281 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18282 }
18283 multi_buffer::Event::Reparsed(buffer_id) => {
18284 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18285 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18286
18287 cx.emit(EditorEvent::Reparsed(*buffer_id));
18288 }
18289 multi_buffer::Event::DiffHunksToggled => {
18290 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18291 }
18292 multi_buffer::Event::LanguageChanged(buffer_id) => {
18293 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18294 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18295 cx.emit(EditorEvent::Reparsed(*buffer_id));
18296 cx.notify();
18297 }
18298 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18299 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18300 multi_buffer::Event::FileHandleChanged
18301 | multi_buffer::Event::Reloaded
18302 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18303 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18304 multi_buffer::Event::DiagnosticsUpdated => {
18305 self.refresh_active_diagnostics(cx);
18306 self.refresh_inline_diagnostics(true, window, cx);
18307 self.scrollbar_marker_state.dirty = true;
18308 cx.notify();
18309 }
18310 _ => {}
18311 };
18312 }
18313
18314 pub fn start_temporary_diff_override(&mut self) {
18315 self.load_diff_task.take();
18316 self.temporary_diff_override = true;
18317 }
18318
18319 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18320 self.temporary_diff_override = false;
18321 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18322 self.buffer.update(cx, |buffer, cx| {
18323 buffer.set_all_diff_hunks_collapsed(cx);
18324 });
18325
18326 if let Some(project) = self.project.clone() {
18327 self.load_diff_task = Some(
18328 update_uncommitted_diff_for_buffer(
18329 cx.entity(),
18330 &project,
18331 self.buffer.read(cx).all_buffers(),
18332 self.buffer.clone(),
18333 cx,
18334 )
18335 .shared(),
18336 );
18337 }
18338 }
18339
18340 fn on_display_map_changed(
18341 &mut self,
18342 _: Entity<DisplayMap>,
18343 _: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 cx.notify();
18347 }
18348
18349 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18350 let new_severity = if self.diagnostics_enabled() {
18351 EditorSettings::get_global(cx)
18352 .diagnostics_max_severity
18353 .unwrap_or(DiagnosticSeverity::Hint)
18354 } else {
18355 DiagnosticSeverity::Off
18356 };
18357 self.set_max_diagnostics_severity(new_severity, cx);
18358 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18359 self.update_edit_prediction_settings(cx);
18360 self.refresh_inline_completion(true, false, window, cx);
18361 self.refresh_inlay_hints(
18362 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18363 self.selections.newest_anchor().head(),
18364 &self.buffer.read(cx).snapshot(cx),
18365 cx,
18366 )),
18367 cx,
18368 );
18369
18370 let old_cursor_shape = self.cursor_shape;
18371
18372 {
18373 let editor_settings = EditorSettings::get_global(cx);
18374 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18375 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18376 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18377 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18378 }
18379
18380 if old_cursor_shape != self.cursor_shape {
18381 cx.emit(EditorEvent::CursorShapeChanged);
18382 }
18383
18384 let project_settings = ProjectSettings::get_global(cx);
18385 self.serialize_dirty_buffers =
18386 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18387
18388 if self.mode.is_full() {
18389 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18390 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18391 if self.show_inline_diagnostics != show_inline_diagnostics {
18392 self.show_inline_diagnostics = show_inline_diagnostics;
18393 self.refresh_inline_diagnostics(false, window, cx);
18394 }
18395
18396 if self.git_blame_inline_enabled != inline_blame_enabled {
18397 self.toggle_git_blame_inline_internal(false, window, cx);
18398 }
18399
18400 let minimap_settings = EditorSettings::get_global(cx).minimap;
18401 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18402 self.set_minimap_visibility(
18403 self.minimap_visibility.toggle_visibility(),
18404 window,
18405 cx,
18406 );
18407 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18408 minimap_entity.update(cx, |minimap_editor, cx| {
18409 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18410 })
18411 }
18412 }
18413
18414 cx.notify();
18415 }
18416
18417 pub fn set_searchable(&mut self, searchable: bool) {
18418 self.searchable = searchable;
18419 }
18420
18421 pub fn searchable(&self) -> bool {
18422 self.searchable
18423 }
18424
18425 fn open_proposed_changes_editor(
18426 &mut self,
18427 _: &OpenProposedChangesEditor,
18428 window: &mut Window,
18429 cx: &mut Context<Self>,
18430 ) {
18431 let Some(workspace) = self.workspace() else {
18432 cx.propagate();
18433 return;
18434 };
18435
18436 let selections = self.selections.all::<usize>(cx);
18437 let multi_buffer = self.buffer.read(cx);
18438 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18439 let mut new_selections_by_buffer = HashMap::default();
18440 for selection in selections {
18441 for (buffer, range, _) in
18442 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18443 {
18444 let mut range = range.to_point(buffer);
18445 range.start.column = 0;
18446 range.end.column = buffer.line_len(range.end.row);
18447 new_selections_by_buffer
18448 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18449 .or_insert(Vec::new())
18450 .push(range)
18451 }
18452 }
18453
18454 let proposed_changes_buffers = new_selections_by_buffer
18455 .into_iter()
18456 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18457 .collect::<Vec<_>>();
18458 let proposed_changes_editor = cx.new(|cx| {
18459 ProposedChangesEditor::new(
18460 "Proposed changes",
18461 proposed_changes_buffers,
18462 self.project.clone(),
18463 window,
18464 cx,
18465 )
18466 });
18467
18468 window.defer(cx, move |window, cx| {
18469 workspace.update(cx, |workspace, cx| {
18470 workspace.active_pane().update(cx, |pane, cx| {
18471 pane.add_item(
18472 Box::new(proposed_changes_editor),
18473 true,
18474 true,
18475 None,
18476 window,
18477 cx,
18478 );
18479 });
18480 });
18481 });
18482 }
18483
18484 pub fn open_excerpts_in_split(
18485 &mut self,
18486 _: &OpenExcerptsSplit,
18487 window: &mut Window,
18488 cx: &mut Context<Self>,
18489 ) {
18490 self.open_excerpts_common(None, true, window, cx)
18491 }
18492
18493 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18494 self.open_excerpts_common(None, false, window, cx)
18495 }
18496
18497 fn open_excerpts_common(
18498 &mut self,
18499 jump_data: Option<JumpData>,
18500 split: bool,
18501 window: &mut Window,
18502 cx: &mut Context<Self>,
18503 ) {
18504 let Some(workspace) = self.workspace() else {
18505 cx.propagate();
18506 return;
18507 };
18508
18509 if self.buffer.read(cx).is_singleton() {
18510 cx.propagate();
18511 return;
18512 }
18513
18514 let mut new_selections_by_buffer = HashMap::default();
18515 match &jump_data {
18516 Some(JumpData::MultiBufferPoint {
18517 excerpt_id,
18518 position,
18519 anchor,
18520 line_offset_from_top,
18521 }) => {
18522 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18523 if let Some(buffer) = multi_buffer_snapshot
18524 .buffer_id_for_excerpt(*excerpt_id)
18525 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18526 {
18527 let buffer_snapshot = buffer.read(cx).snapshot();
18528 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18529 language::ToPoint::to_point(anchor, &buffer_snapshot)
18530 } else {
18531 buffer_snapshot.clip_point(*position, Bias::Left)
18532 };
18533 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18534 new_selections_by_buffer.insert(
18535 buffer,
18536 (
18537 vec![jump_to_offset..jump_to_offset],
18538 Some(*line_offset_from_top),
18539 ),
18540 );
18541 }
18542 }
18543 Some(JumpData::MultiBufferRow {
18544 row,
18545 line_offset_from_top,
18546 }) => {
18547 let point = MultiBufferPoint::new(row.0, 0);
18548 if let Some((buffer, buffer_point, _)) =
18549 self.buffer.read(cx).point_to_buffer_point(point, cx)
18550 {
18551 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18552 new_selections_by_buffer
18553 .entry(buffer)
18554 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18555 .0
18556 .push(buffer_offset..buffer_offset)
18557 }
18558 }
18559 None => {
18560 let selections = self.selections.all::<usize>(cx);
18561 let multi_buffer = self.buffer.read(cx);
18562 for selection in selections {
18563 for (snapshot, range, _, anchor) in multi_buffer
18564 .snapshot(cx)
18565 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18566 {
18567 if let Some(anchor) = anchor {
18568 // selection is in a deleted hunk
18569 let Some(buffer_id) = anchor.buffer_id else {
18570 continue;
18571 };
18572 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18573 continue;
18574 };
18575 let offset = text::ToOffset::to_offset(
18576 &anchor.text_anchor,
18577 &buffer_handle.read(cx).snapshot(),
18578 );
18579 let range = offset..offset;
18580 new_selections_by_buffer
18581 .entry(buffer_handle)
18582 .or_insert((Vec::new(), None))
18583 .0
18584 .push(range)
18585 } else {
18586 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18587 else {
18588 continue;
18589 };
18590 new_selections_by_buffer
18591 .entry(buffer_handle)
18592 .or_insert((Vec::new(), None))
18593 .0
18594 .push(range)
18595 }
18596 }
18597 }
18598 }
18599 }
18600
18601 new_selections_by_buffer
18602 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18603
18604 if new_selections_by_buffer.is_empty() {
18605 return;
18606 }
18607
18608 // We defer the pane interaction because we ourselves are a workspace item
18609 // and activating a new item causes the pane to call a method on us reentrantly,
18610 // which panics if we're on the stack.
18611 window.defer(cx, move |window, cx| {
18612 workspace.update(cx, |workspace, cx| {
18613 let pane = if split {
18614 workspace.adjacent_pane(window, cx)
18615 } else {
18616 workspace.active_pane().clone()
18617 };
18618
18619 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18620 let editor = buffer
18621 .read(cx)
18622 .file()
18623 .is_none()
18624 .then(|| {
18625 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18626 // so `workspace.open_project_item` will never find them, always opening a new editor.
18627 // Instead, we try to activate the existing editor in the pane first.
18628 let (editor, pane_item_index) =
18629 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18630 let editor = item.downcast::<Editor>()?;
18631 let singleton_buffer =
18632 editor.read(cx).buffer().read(cx).as_singleton()?;
18633 if singleton_buffer == buffer {
18634 Some((editor, i))
18635 } else {
18636 None
18637 }
18638 })?;
18639 pane.update(cx, |pane, cx| {
18640 pane.activate_item(pane_item_index, true, true, window, cx)
18641 });
18642 Some(editor)
18643 })
18644 .flatten()
18645 .unwrap_or_else(|| {
18646 workspace.open_project_item::<Self>(
18647 pane.clone(),
18648 buffer,
18649 true,
18650 true,
18651 window,
18652 cx,
18653 )
18654 });
18655
18656 editor.update(cx, |editor, cx| {
18657 let autoscroll = match scroll_offset {
18658 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18659 None => Autoscroll::newest(),
18660 };
18661 let nav_history = editor.nav_history.take();
18662 editor.change_selections(Some(autoscroll), window, cx, |s| {
18663 s.select_ranges(ranges);
18664 });
18665 editor.nav_history = nav_history;
18666 });
18667 }
18668 })
18669 });
18670 }
18671
18672 // For now, don't allow opening excerpts in buffers that aren't backed by
18673 // regular project files.
18674 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18675 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18676 }
18677
18678 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18679 let snapshot = self.buffer.read(cx).read(cx);
18680 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18681 Some(
18682 ranges
18683 .iter()
18684 .map(move |range| {
18685 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18686 })
18687 .collect(),
18688 )
18689 }
18690
18691 fn selection_replacement_ranges(
18692 &self,
18693 range: Range<OffsetUtf16>,
18694 cx: &mut App,
18695 ) -> Vec<Range<OffsetUtf16>> {
18696 let selections = self.selections.all::<OffsetUtf16>(cx);
18697 let newest_selection = selections
18698 .iter()
18699 .max_by_key(|selection| selection.id)
18700 .unwrap();
18701 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18702 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18703 let snapshot = self.buffer.read(cx).read(cx);
18704 selections
18705 .into_iter()
18706 .map(|mut selection| {
18707 selection.start.0 =
18708 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18709 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18710 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18711 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18712 })
18713 .collect()
18714 }
18715
18716 fn report_editor_event(
18717 &self,
18718 event_type: &'static str,
18719 file_extension: Option<String>,
18720 cx: &App,
18721 ) {
18722 if cfg!(any(test, feature = "test-support")) {
18723 return;
18724 }
18725
18726 let Some(project) = &self.project else { return };
18727
18728 // If None, we are in a file without an extension
18729 let file = self
18730 .buffer
18731 .read(cx)
18732 .as_singleton()
18733 .and_then(|b| b.read(cx).file());
18734 let file_extension = file_extension.or(file
18735 .as_ref()
18736 .and_then(|file| Path::new(file.file_name(cx)).extension())
18737 .and_then(|e| e.to_str())
18738 .map(|a| a.to_string()));
18739
18740 let vim_mode = vim_enabled(cx);
18741
18742 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18743 let copilot_enabled = edit_predictions_provider
18744 == language::language_settings::EditPredictionProvider::Copilot;
18745 let copilot_enabled_for_language = self
18746 .buffer
18747 .read(cx)
18748 .language_settings(cx)
18749 .show_edit_predictions;
18750
18751 let project = project.read(cx);
18752 telemetry::event!(
18753 event_type,
18754 file_extension,
18755 vim_mode,
18756 copilot_enabled,
18757 copilot_enabled_for_language,
18758 edit_predictions_provider,
18759 is_via_ssh = project.is_via_ssh(),
18760 );
18761 }
18762
18763 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18764 /// with each line being an array of {text, highlight} objects.
18765 fn copy_highlight_json(
18766 &mut self,
18767 _: &CopyHighlightJson,
18768 window: &mut Window,
18769 cx: &mut Context<Self>,
18770 ) {
18771 #[derive(Serialize)]
18772 struct Chunk<'a> {
18773 text: String,
18774 highlight: Option<&'a str>,
18775 }
18776
18777 let snapshot = self.buffer.read(cx).snapshot(cx);
18778 let range = self
18779 .selected_text_range(false, window, cx)
18780 .and_then(|selection| {
18781 if selection.range.is_empty() {
18782 None
18783 } else {
18784 Some(selection.range)
18785 }
18786 })
18787 .unwrap_or_else(|| 0..snapshot.len());
18788
18789 let chunks = snapshot.chunks(range, true);
18790 let mut lines = Vec::new();
18791 let mut line: VecDeque<Chunk> = VecDeque::new();
18792
18793 let Some(style) = self.style.as_ref() else {
18794 return;
18795 };
18796
18797 for chunk in chunks {
18798 let highlight = chunk
18799 .syntax_highlight_id
18800 .and_then(|id| id.name(&style.syntax));
18801 let mut chunk_lines = chunk.text.split('\n').peekable();
18802 while let Some(text) = chunk_lines.next() {
18803 let mut merged_with_last_token = false;
18804 if let Some(last_token) = line.back_mut() {
18805 if last_token.highlight == highlight {
18806 last_token.text.push_str(text);
18807 merged_with_last_token = true;
18808 }
18809 }
18810
18811 if !merged_with_last_token {
18812 line.push_back(Chunk {
18813 text: text.into(),
18814 highlight,
18815 });
18816 }
18817
18818 if chunk_lines.peek().is_some() {
18819 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18820 line.pop_front();
18821 }
18822 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18823 line.pop_back();
18824 }
18825
18826 lines.push(mem::take(&mut line));
18827 }
18828 }
18829 }
18830
18831 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18832 return;
18833 };
18834 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18835 }
18836
18837 pub fn open_context_menu(
18838 &mut self,
18839 _: &OpenContextMenu,
18840 window: &mut Window,
18841 cx: &mut Context<Self>,
18842 ) {
18843 self.request_autoscroll(Autoscroll::newest(), cx);
18844 let position = self.selections.newest_display(cx).start;
18845 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18846 }
18847
18848 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18849 &self.inlay_hint_cache
18850 }
18851
18852 pub fn replay_insert_event(
18853 &mut self,
18854 text: &str,
18855 relative_utf16_range: Option<Range<isize>>,
18856 window: &mut Window,
18857 cx: &mut Context<Self>,
18858 ) {
18859 if !self.input_enabled {
18860 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18861 return;
18862 }
18863 if let Some(relative_utf16_range) = relative_utf16_range {
18864 let selections = self.selections.all::<OffsetUtf16>(cx);
18865 self.change_selections(None, window, cx, |s| {
18866 let new_ranges = selections.into_iter().map(|range| {
18867 let start = OffsetUtf16(
18868 range
18869 .head()
18870 .0
18871 .saturating_add_signed(relative_utf16_range.start),
18872 );
18873 let end = OffsetUtf16(
18874 range
18875 .head()
18876 .0
18877 .saturating_add_signed(relative_utf16_range.end),
18878 );
18879 start..end
18880 });
18881 s.select_ranges(new_ranges);
18882 });
18883 }
18884
18885 self.handle_input(text, window, cx);
18886 }
18887
18888 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18889 let Some(provider) = self.semantics_provider.as_ref() else {
18890 return false;
18891 };
18892
18893 let mut supports = false;
18894 self.buffer().update(cx, |this, cx| {
18895 this.for_each_buffer(|buffer| {
18896 supports |= provider.supports_inlay_hints(buffer, cx);
18897 });
18898 });
18899
18900 supports
18901 }
18902
18903 pub fn is_focused(&self, window: &Window) -> bool {
18904 self.focus_handle.is_focused(window)
18905 }
18906
18907 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18908 cx.emit(EditorEvent::Focused);
18909
18910 if let Some(descendant) = self
18911 .last_focused_descendant
18912 .take()
18913 .and_then(|descendant| descendant.upgrade())
18914 {
18915 window.focus(&descendant);
18916 } else {
18917 if let Some(blame) = self.blame.as_ref() {
18918 blame.update(cx, GitBlame::focus)
18919 }
18920
18921 self.blink_manager.update(cx, BlinkManager::enable);
18922 self.show_cursor_names(window, cx);
18923 self.buffer.update(cx, |buffer, cx| {
18924 buffer.finalize_last_transaction(cx);
18925 if self.leader_id.is_none() {
18926 buffer.set_active_selections(
18927 &self.selections.disjoint_anchors(),
18928 self.selections.line_mode,
18929 self.cursor_shape,
18930 cx,
18931 );
18932 }
18933 });
18934 }
18935 }
18936
18937 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18938 cx.emit(EditorEvent::FocusedIn)
18939 }
18940
18941 fn handle_focus_out(
18942 &mut self,
18943 event: FocusOutEvent,
18944 _window: &mut Window,
18945 cx: &mut Context<Self>,
18946 ) {
18947 if event.blurred != self.focus_handle {
18948 self.last_focused_descendant = Some(event.blurred);
18949 }
18950 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18951 }
18952
18953 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18954 self.blink_manager.update(cx, BlinkManager::disable);
18955 self.buffer
18956 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18957
18958 if let Some(blame) = self.blame.as_ref() {
18959 blame.update(cx, GitBlame::blur)
18960 }
18961 if !self.hover_state.focused(window, cx) {
18962 hide_hover(self, cx);
18963 }
18964 if !self
18965 .context_menu
18966 .borrow()
18967 .as_ref()
18968 .is_some_and(|context_menu| context_menu.focused(window, cx))
18969 {
18970 self.hide_context_menu(window, cx);
18971 }
18972 self.discard_inline_completion(false, cx);
18973 cx.emit(EditorEvent::Blurred);
18974 cx.notify();
18975 }
18976
18977 pub fn register_action<A: Action>(
18978 &mut self,
18979 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18980 ) -> Subscription {
18981 let id = self.next_editor_action_id.post_inc();
18982 let listener = Arc::new(listener);
18983 self.editor_actions.borrow_mut().insert(
18984 id,
18985 Box::new(move |window, _| {
18986 let listener = listener.clone();
18987 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18988 let action = action.downcast_ref().unwrap();
18989 if phase == DispatchPhase::Bubble {
18990 listener(action, window, cx)
18991 }
18992 })
18993 }),
18994 );
18995
18996 let editor_actions = self.editor_actions.clone();
18997 Subscription::new(move || {
18998 editor_actions.borrow_mut().remove(&id);
18999 })
19000 }
19001
19002 pub fn file_header_size(&self) -> u32 {
19003 FILE_HEADER_HEIGHT
19004 }
19005
19006 pub fn restore(
19007 &mut self,
19008 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19009 window: &mut Window,
19010 cx: &mut Context<Self>,
19011 ) {
19012 let workspace = self.workspace();
19013 let project = self.project.as_ref();
19014 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19015 let mut tasks = Vec::new();
19016 for (buffer_id, changes) in revert_changes {
19017 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19018 buffer.update(cx, |buffer, cx| {
19019 buffer.edit(
19020 changes
19021 .into_iter()
19022 .map(|(range, text)| (range, text.to_string())),
19023 None,
19024 cx,
19025 );
19026 });
19027
19028 if let Some(project) =
19029 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19030 {
19031 project.update(cx, |project, cx| {
19032 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19033 })
19034 }
19035 }
19036 }
19037 tasks
19038 });
19039 cx.spawn_in(window, async move |_, cx| {
19040 for (buffer, task) in save_tasks {
19041 let result = task.await;
19042 if result.is_err() {
19043 let Some(path) = buffer
19044 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19045 .ok()
19046 else {
19047 continue;
19048 };
19049 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19050 let Some(task) = cx
19051 .update_window_entity(&workspace, |workspace, window, cx| {
19052 workspace
19053 .open_path_preview(path, None, false, false, false, window, cx)
19054 })
19055 .ok()
19056 else {
19057 continue;
19058 };
19059 task.await.log_err();
19060 }
19061 }
19062 }
19063 })
19064 .detach();
19065 self.change_selections(None, window, cx, |selections| selections.refresh());
19066 }
19067
19068 pub fn to_pixel_point(
19069 &self,
19070 source: multi_buffer::Anchor,
19071 editor_snapshot: &EditorSnapshot,
19072 window: &mut Window,
19073 ) -> Option<gpui::Point<Pixels>> {
19074 let source_point = source.to_display_point(editor_snapshot);
19075 self.display_to_pixel_point(source_point, editor_snapshot, window)
19076 }
19077
19078 pub fn display_to_pixel_point(
19079 &self,
19080 source: DisplayPoint,
19081 editor_snapshot: &EditorSnapshot,
19082 window: &mut Window,
19083 ) -> Option<gpui::Point<Pixels>> {
19084 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19085 let text_layout_details = self.text_layout_details(window);
19086 let scroll_top = text_layout_details
19087 .scroll_anchor
19088 .scroll_position(editor_snapshot)
19089 .y;
19090
19091 if source.row().as_f32() < scroll_top.floor() {
19092 return None;
19093 }
19094 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19095 let source_y = line_height * (source.row().as_f32() - scroll_top);
19096 Some(gpui::Point::new(source_x, source_y))
19097 }
19098
19099 pub fn has_visible_completions_menu(&self) -> bool {
19100 !self.edit_prediction_preview_is_active()
19101 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19102 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19103 })
19104 }
19105
19106 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19107 if self.mode.is_minimap() {
19108 return;
19109 }
19110 self.addons
19111 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19112 }
19113
19114 pub fn unregister_addon<T: Addon>(&mut self) {
19115 self.addons.remove(&std::any::TypeId::of::<T>());
19116 }
19117
19118 pub fn addon<T: Addon>(&self) -> Option<&T> {
19119 let type_id = std::any::TypeId::of::<T>();
19120 self.addons
19121 .get(&type_id)
19122 .and_then(|item| item.to_any().downcast_ref::<T>())
19123 }
19124
19125 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19126 let type_id = std::any::TypeId::of::<T>();
19127 self.addons
19128 .get_mut(&type_id)
19129 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19130 }
19131
19132 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19133 let text_layout_details = self.text_layout_details(window);
19134 let style = &text_layout_details.editor_style;
19135 let font_id = window.text_system().resolve_font(&style.text.font());
19136 let font_size = style.text.font_size.to_pixels(window.rem_size());
19137 let line_height = style.text.line_height_in_pixels(window.rem_size());
19138 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19139
19140 gpui::Size::new(em_width, line_height)
19141 }
19142
19143 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19144 self.load_diff_task.clone()
19145 }
19146
19147 fn read_metadata_from_db(
19148 &mut self,
19149 item_id: u64,
19150 workspace_id: WorkspaceId,
19151 window: &mut Window,
19152 cx: &mut Context<Editor>,
19153 ) {
19154 if self.is_singleton(cx)
19155 && !self.mode.is_minimap()
19156 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19157 {
19158 let buffer_snapshot = OnceCell::new();
19159
19160 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19161 if !folds.is_empty() {
19162 let snapshot =
19163 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19164 self.fold_ranges(
19165 folds
19166 .into_iter()
19167 .map(|(start, end)| {
19168 snapshot.clip_offset(start, Bias::Left)
19169 ..snapshot.clip_offset(end, Bias::Right)
19170 })
19171 .collect(),
19172 false,
19173 window,
19174 cx,
19175 );
19176 }
19177 }
19178
19179 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19180 if !selections.is_empty() {
19181 let snapshot =
19182 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19183 self.change_selections(None, window, cx, |s| {
19184 s.select_ranges(selections.into_iter().map(|(start, end)| {
19185 snapshot.clip_offset(start, Bias::Left)
19186 ..snapshot.clip_offset(end, Bias::Right)
19187 }));
19188 });
19189 }
19190 };
19191 }
19192
19193 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19194 }
19195}
19196
19197fn vim_enabled(cx: &App) -> bool {
19198 cx.global::<SettingsStore>()
19199 .raw_user_settings()
19200 .get("vim_mode")
19201 == Some(&serde_json::Value::Bool(true))
19202}
19203
19204// Consider user intent and default settings
19205fn choose_completion_range(
19206 completion: &Completion,
19207 intent: CompletionIntent,
19208 buffer: &Entity<Buffer>,
19209 cx: &mut Context<Editor>,
19210) -> Range<usize> {
19211 fn should_replace(
19212 completion: &Completion,
19213 insert_range: &Range<text::Anchor>,
19214 intent: CompletionIntent,
19215 completion_mode_setting: LspInsertMode,
19216 buffer: &Buffer,
19217 ) -> bool {
19218 // specific actions take precedence over settings
19219 match intent {
19220 CompletionIntent::CompleteWithInsert => return false,
19221 CompletionIntent::CompleteWithReplace => return true,
19222 CompletionIntent::Complete | CompletionIntent::Compose => {}
19223 }
19224
19225 match completion_mode_setting {
19226 LspInsertMode::Insert => false,
19227 LspInsertMode::Replace => true,
19228 LspInsertMode::ReplaceSubsequence => {
19229 let mut text_to_replace = buffer.chars_for_range(
19230 buffer.anchor_before(completion.replace_range.start)
19231 ..buffer.anchor_after(completion.replace_range.end),
19232 );
19233 let mut completion_text = completion.new_text.chars();
19234
19235 // is `text_to_replace` a subsequence of `completion_text`
19236 text_to_replace
19237 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19238 }
19239 LspInsertMode::ReplaceSuffix => {
19240 let range_after_cursor = insert_range.end..completion.replace_range.end;
19241
19242 let text_after_cursor = buffer
19243 .text_for_range(
19244 buffer.anchor_before(range_after_cursor.start)
19245 ..buffer.anchor_after(range_after_cursor.end),
19246 )
19247 .collect::<String>();
19248 completion.new_text.ends_with(&text_after_cursor)
19249 }
19250 }
19251 }
19252
19253 let buffer = buffer.read(cx);
19254
19255 if let CompletionSource::Lsp {
19256 insert_range: Some(insert_range),
19257 ..
19258 } = &completion.source
19259 {
19260 let completion_mode_setting =
19261 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19262 .completions
19263 .lsp_insert_mode;
19264
19265 if !should_replace(
19266 completion,
19267 &insert_range,
19268 intent,
19269 completion_mode_setting,
19270 buffer,
19271 ) {
19272 return insert_range.to_offset(buffer);
19273 }
19274 }
19275
19276 completion.replace_range.to_offset(buffer)
19277}
19278
19279fn insert_extra_newline_brackets(
19280 buffer: &MultiBufferSnapshot,
19281 range: Range<usize>,
19282 language: &language::LanguageScope,
19283) -> bool {
19284 let leading_whitespace_len = buffer
19285 .reversed_chars_at(range.start)
19286 .take_while(|c| c.is_whitespace() && *c != '\n')
19287 .map(|c| c.len_utf8())
19288 .sum::<usize>();
19289 let trailing_whitespace_len = buffer
19290 .chars_at(range.end)
19291 .take_while(|c| c.is_whitespace() && *c != '\n')
19292 .map(|c| c.len_utf8())
19293 .sum::<usize>();
19294 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19295
19296 language.brackets().any(|(pair, enabled)| {
19297 let pair_start = pair.start.trim_end();
19298 let pair_end = pair.end.trim_start();
19299
19300 enabled
19301 && pair.newline
19302 && buffer.contains_str_at(range.end, pair_end)
19303 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19304 })
19305}
19306
19307fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19308 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19309 [(buffer, range, _)] => (*buffer, range.clone()),
19310 _ => return false,
19311 };
19312 let pair = {
19313 let mut result: Option<BracketMatch> = None;
19314
19315 for pair in buffer
19316 .all_bracket_ranges(range.clone())
19317 .filter(move |pair| {
19318 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19319 })
19320 {
19321 let len = pair.close_range.end - pair.open_range.start;
19322
19323 if let Some(existing) = &result {
19324 let existing_len = existing.close_range.end - existing.open_range.start;
19325 if len > existing_len {
19326 continue;
19327 }
19328 }
19329
19330 result = Some(pair);
19331 }
19332
19333 result
19334 };
19335 let Some(pair) = pair else {
19336 return false;
19337 };
19338 pair.newline_only
19339 && buffer
19340 .chars_for_range(pair.open_range.end..range.start)
19341 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19342 .all(|c| c.is_whitespace() && c != '\n')
19343}
19344
19345fn update_uncommitted_diff_for_buffer(
19346 editor: Entity<Editor>,
19347 project: &Entity<Project>,
19348 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19349 buffer: Entity<MultiBuffer>,
19350 cx: &mut App,
19351) -> Task<()> {
19352 let mut tasks = Vec::new();
19353 project.update(cx, |project, cx| {
19354 for buffer in buffers {
19355 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19356 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19357 }
19358 }
19359 });
19360 cx.spawn(async move |cx| {
19361 let diffs = future::join_all(tasks).await;
19362 if editor
19363 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19364 .unwrap_or(false)
19365 {
19366 return;
19367 }
19368
19369 buffer
19370 .update(cx, |buffer, cx| {
19371 for diff in diffs.into_iter().flatten() {
19372 buffer.add_diff(diff, cx);
19373 }
19374 })
19375 .ok();
19376 })
19377}
19378
19379fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19380 let tab_size = tab_size.get() as usize;
19381 let mut width = offset;
19382
19383 for ch in text.chars() {
19384 width += if ch == '\t' {
19385 tab_size - (width % tab_size)
19386 } else {
19387 1
19388 };
19389 }
19390
19391 width - offset
19392}
19393
19394#[cfg(test)]
19395mod tests {
19396 use super::*;
19397
19398 #[test]
19399 fn test_string_size_with_expanded_tabs() {
19400 let nz = |val| NonZeroU32::new(val).unwrap();
19401 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19402 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19403 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19404 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19405 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19406 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19407 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19408 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19409 }
19410}
19411
19412/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19413struct WordBreakingTokenizer<'a> {
19414 input: &'a str,
19415}
19416
19417impl<'a> WordBreakingTokenizer<'a> {
19418 fn new(input: &'a str) -> Self {
19419 Self { input }
19420 }
19421}
19422
19423fn is_char_ideographic(ch: char) -> bool {
19424 use unicode_script::Script::*;
19425 use unicode_script::UnicodeScript;
19426 matches!(ch.script(), Han | Tangut | Yi)
19427}
19428
19429fn is_grapheme_ideographic(text: &str) -> bool {
19430 text.chars().any(is_char_ideographic)
19431}
19432
19433fn is_grapheme_whitespace(text: &str) -> bool {
19434 text.chars().any(|x| x.is_whitespace())
19435}
19436
19437fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19438 text.chars().next().map_or(false, |ch| {
19439 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19440 })
19441}
19442
19443#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19444enum WordBreakToken<'a> {
19445 Word { token: &'a str, grapheme_len: usize },
19446 InlineWhitespace { token: &'a str, grapheme_len: usize },
19447 Newline,
19448}
19449
19450impl<'a> Iterator for WordBreakingTokenizer<'a> {
19451 /// Yields a span, the count of graphemes in the token, and whether it was
19452 /// whitespace. Note that it also breaks at word boundaries.
19453 type Item = WordBreakToken<'a>;
19454
19455 fn next(&mut self) -> Option<Self::Item> {
19456 use unicode_segmentation::UnicodeSegmentation;
19457 if self.input.is_empty() {
19458 return None;
19459 }
19460
19461 let mut iter = self.input.graphemes(true).peekable();
19462 let mut offset = 0;
19463 let mut grapheme_len = 0;
19464 if let Some(first_grapheme) = iter.next() {
19465 let is_newline = first_grapheme == "\n";
19466 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19467 offset += first_grapheme.len();
19468 grapheme_len += 1;
19469 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19470 if let Some(grapheme) = iter.peek().copied() {
19471 if should_stay_with_preceding_ideograph(grapheme) {
19472 offset += grapheme.len();
19473 grapheme_len += 1;
19474 }
19475 }
19476 } else {
19477 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19478 let mut next_word_bound = words.peek().copied();
19479 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19480 next_word_bound = words.next();
19481 }
19482 while let Some(grapheme) = iter.peek().copied() {
19483 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19484 break;
19485 };
19486 if is_grapheme_whitespace(grapheme) != is_whitespace
19487 || (grapheme == "\n") != is_newline
19488 {
19489 break;
19490 };
19491 offset += grapheme.len();
19492 grapheme_len += 1;
19493 iter.next();
19494 }
19495 }
19496 let token = &self.input[..offset];
19497 self.input = &self.input[offset..];
19498 if token == "\n" {
19499 Some(WordBreakToken::Newline)
19500 } else if is_whitespace {
19501 Some(WordBreakToken::InlineWhitespace {
19502 token,
19503 grapheme_len,
19504 })
19505 } else {
19506 Some(WordBreakToken::Word {
19507 token,
19508 grapheme_len,
19509 })
19510 }
19511 } else {
19512 None
19513 }
19514 }
19515}
19516
19517#[test]
19518fn test_word_breaking_tokenizer() {
19519 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19520 ("", &[]),
19521 (" ", &[whitespace(" ", 2)]),
19522 ("Ʒ", &[word("Ʒ", 1)]),
19523 ("Ǽ", &[word("Ǽ", 1)]),
19524 ("⋑", &[word("⋑", 1)]),
19525 ("⋑⋑", &[word("⋑⋑", 2)]),
19526 (
19527 "原理,进而",
19528 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19529 ),
19530 (
19531 "hello world",
19532 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19533 ),
19534 (
19535 "hello, world",
19536 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19537 ),
19538 (
19539 " hello world",
19540 &[
19541 whitespace(" ", 2),
19542 word("hello", 5),
19543 whitespace(" ", 1),
19544 word("world", 5),
19545 ],
19546 ),
19547 (
19548 "这是什么 \n 钢笔",
19549 &[
19550 word("这", 1),
19551 word("是", 1),
19552 word("什", 1),
19553 word("么", 1),
19554 whitespace(" ", 1),
19555 newline(),
19556 whitespace(" ", 1),
19557 word("钢", 1),
19558 word("笔", 1),
19559 ],
19560 ),
19561 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19562 ];
19563
19564 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19565 WordBreakToken::Word {
19566 token,
19567 grapheme_len,
19568 }
19569 }
19570
19571 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19572 WordBreakToken::InlineWhitespace {
19573 token,
19574 grapheme_len,
19575 }
19576 }
19577
19578 fn newline() -> WordBreakToken<'static> {
19579 WordBreakToken::Newline
19580 }
19581
19582 for (input, result) in tests {
19583 assert_eq!(
19584 WordBreakingTokenizer::new(input)
19585 .collect::<Vec<_>>()
19586 .as_slice(),
19587 *result,
19588 );
19589 }
19590}
19591
19592fn wrap_with_prefix(
19593 line_prefix: String,
19594 unwrapped_text: String,
19595 wrap_column: usize,
19596 tab_size: NonZeroU32,
19597 preserve_existing_whitespace: bool,
19598) -> String {
19599 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19600 let mut wrapped_text = String::new();
19601 let mut current_line = line_prefix.clone();
19602
19603 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19604 let mut current_line_len = line_prefix_len;
19605 let mut in_whitespace = false;
19606 for token in tokenizer {
19607 let have_preceding_whitespace = in_whitespace;
19608 match token {
19609 WordBreakToken::Word {
19610 token,
19611 grapheme_len,
19612 } => {
19613 in_whitespace = false;
19614 if current_line_len + grapheme_len > wrap_column
19615 && current_line_len != line_prefix_len
19616 {
19617 wrapped_text.push_str(current_line.trim_end());
19618 wrapped_text.push('\n');
19619 current_line.truncate(line_prefix.len());
19620 current_line_len = line_prefix_len;
19621 }
19622 current_line.push_str(token);
19623 current_line_len += grapheme_len;
19624 }
19625 WordBreakToken::InlineWhitespace {
19626 mut token,
19627 mut grapheme_len,
19628 } => {
19629 in_whitespace = true;
19630 if have_preceding_whitespace && !preserve_existing_whitespace {
19631 continue;
19632 }
19633 if !preserve_existing_whitespace {
19634 token = " ";
19635 grapheme_len = 1;
19636 }
19637 if current_line_len + grapheme_len > wrap_column {
19638 wrapped_text.push_str(current_line.trim_end());
19639 wrapped_text.push('\n');
19640 current_line.truncate(line_prefix.len());
19641 current_line_len = line_prefix_len;
19642 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19643 current_line.push_str(token);
19644 current_line_len += grapheme_len;
19645 }
19646 }
19647 WordBreakToken::Newline => {
19648 in_whitespace = true;
19649 if preserve_existing_whitespace {
19650 wrapped_text.push_str(current_line.trim_end());
19651 wrapped_text.push('\n');
19652 current_line.truncate(line_prefix.len());
19653 current_line_len = line_prefix_len;
19654 } else if have_preceding_whitespace {
19655 continue;
19656 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19657 {
19658 wrapped_text.push_str(current_line.trim_end());
19659 wrapped_text.push('\n');
19660 current_line.truncate(line_prefix.len());
19661 current_line_len = line_prefix_len;
19662 } else if current_line_len != line_prefix_len {
19663 current_line.push(' ');
19664 current_line_len += 1;
19665 }
19666 }
19667 }
19668 }
19669
19670 if !current_line.is_empty() {
19671 wrapped_text.push_str(¤t_line);
19672 }
19673 wrapped_text
19674}
19675
19676#[test]
19677fn test_wrap_with_prefix() {
19678 assert_eq!(
19679 wrap_with_prefix(
19680 "# ".to_string(),
19681 "abcdefg".to_string(),
19682 4,
19683 NonZeroU32::new(4).unwrap(),
19684 false,
19685 ),
19686 "# abcdefg"
19687 );
19688 assert_eq!(
19689 wrap_with_prefix(
19690 "".to_string(),
19691 "\thello world".to_string(),
19692 8,
19693 NonZeroU32::new(4).unwrap(),
19694 false,
19695 ),
19696 "hello\nworld"
19697 );
19698 assert_eq!(
19699 wrap_with_prefix(
19700 "// ".to_string(),
19701 "xx \nyy zz aa bb cc".to_string(),
19702 12,
19703 NonZeroU32::new(4).unwrap(),
19704 false,
19705 ),
19706 "// xx yy zz\n// aa bb cc"
19707 );
19708 assert_eq!(
19709 wrap_with_prefix(
19710 String::new(),
19711 "这是什么 \n 钢笔".to_string(),
19712 3,
19713 NonZeroU32::new(4).unwrap(),
19714 false,
19715 ),
19716 "这是什\n么 钢\n笔"
19717 );
19718}
19719
19720pub trait CollaborationHub {
19721 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19722 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19723 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19724}
19725
19726impl CollaborationHub for Entity<Project> {
19727 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19728 self.read(cx).collaborators()
19729 }
19730
19731 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19732 self.read(cx).user_store().read(cx).participant_indices()
19733 }
19734
19735 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19736 let this = self.read(cx);
19737 let user_ids = this.collaborators().values().map(|c| c.user_id);
19738 this.user_store().read_with(cx, |user_store, cx| {
19739 user_store.participant_names(user_ids, cx)
19740 })
19741 }
19742}
19743
19744pub trait SemanticsProvider {
19745 fn hover(
19746 &self,
19747 buffer: &Entity<Buffer>,
19748 position: text::Anchor,
19749 cx: &mut App,
19750 ) -> Option<Task<Vec<project::Hover>>>;
19751
19752 fn inline_values(
19753 &self,
19754 buffer_handle: Entity<Buffer>,
19755 range: Range<text::Anchor>,
19756 cx: &mut App,
19757 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19758
19759 fn inlay_hints(
19760 &self,
19761 buffer_handle: Entity<Buffer>,
19762 range: Range<text::Anchor>,
19763 cx: &mut App,
19764 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19765
19766 fn resolve_inlay_hint(
19767 &self,
19768 hint: InlayHint,
19769 buffer_handle: Entity<Buffer>,
19770 server_id: LanguageServerId,
19771 cx: &mut App,
19772 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19773
19774 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19775
19776 fn document_highlights(
19777 &self,
19778 buffer: &Entity<Buffer>,
19779 position: text::Anchor,
19780 cx: &mut App,
19781 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19782
19783 fn definitions(
19784 &self,
19785 buffer: &Entity<Buffer>,
19786 position: text::Anchor,
19787 kind: GotoDefinitionKind,
19788 cx: &mut App,
19789 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19790
19791 fn range_for_rename(
19792 &self,
19793 buffer: &Entity<Buffer>,
19794 position: text::Anchor,
19795 cx: &mut App,
19796 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19797
19798 fn perform_rename(
19799 &self,
19800 buffer: &Entity<Buffer>,
19801 position: text::Anchor,
19802 new_name: String,
19803 cx: &mut App,
19804 ) -> Option<Task<Result<ProjectTransaction>>>;
19805}
19806
19807pub trait CompletionProvider {
19808 fn completions(
19809 &self,
19810 excerpt_id: ExcerptId,
19811 buffer: &Entity<Buffer>,
19812 buffer_position: text::Anchor,
19813 trigger: CompletionContext,
19814 window: &mut Window,
19815 cx: &mut Context<Editor>,
19816 ) -> Task<Result<Option<Vec<Completion>>>>;
19817
19818 fn resolve_completions(
19819 &self,
19820 buffer: Entity<Buffer>,
19821 completion_indices: Vec<usize>,
19822 completions: Rc<RefCell<Box<[Completion]>>>,
19823 cx: &mut Context<Editor>,
19824 ) -> Task<Result<bool>>;
19825
19826 fn apply_additional_edits_for_completion(
19827 &self,
19828 _buffer: Entity<Buffer>,
19829 _completions: Rc<RefCell<Box<[Completion]>>>,
19830 _completion_index: usize,
19831 _push_to_history: bool,
19832 _cx: &mut Context<Editor>,
19833 ) -> Task<Result<Option<language::Transaction>>> {
19834 Task::ready(Ok(None))
19835 }
19836
19837 fn is_completion_trigger(
19838 &self,
19839 buffer: &Entity<Buffer>,
19840 position: language::Anchor,
19841 text: &str,
19842 trigger_in_words: bool,
19843 cx: &mut Context<Editor>,
19844 ) -> bool;
19845
19846 fn sort_completions(&self) -> bool {
19847 true
19848 }
19849
19850 fn filter_completions(&self) -> bool {
19851 true
19852 }
19853}
19854
19855pub trait CodeActionProvider {
19856 fn id(&self) -> Arc<str>;
19857
19858 fn code_actions(
19859 &self,
19860 buffer: &Entity<Buffer>,
19861 range: Range<text::Anchor>,
19862 window: &mut Window,
19863 cx: &mut App,
19864 ) -> Task<Result<Vec<CodeAction>>>;
19865
19866 fn apply_code_action(
19867 &self,
19868 buffer_handle: Entity<Buffer>,
19869 action: CodeAction,
19870 excerpt_id: ExcerptId,
19871 push_to_history: bool,
19872 window: &mut Window,
19873 cx: &mut App,
19874 ) -> Task<Result<ProjectTransaction>>;
19875}
19876
19877impl CodeActionProvider for Entity<Project> {
19878 fn id(&self) -> Arc<str> {
19879 "project".into()
19880 }
19881
19882 fn code_actions(
19883 &self,
19884 buffer: &Entity<Buffer>,
19885 range: Range<text::Anchor>,
19886 _window: &mut Window,
19887 cx: &mut App,
19888 ) -> Task<Result<Vec<CodeAction>>> {
19889 self.update(cx, |project, cx| {
19890 let code_lens = project.code_lens(buffer, range.clone(), cx);
19891 let code_actions = project.code_actions(buffer, range, None, cx);
19892 cx.background_spawn(async move {
19893 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19894 Ok(code_lens
19895 .context("code lens fetch")?
19896 .into_iter()
19897 .chain(code_actions.context("code action fetch")?)
19898 .collect())
19899 })
19900 })
19901 }
19902
19903 fn apply_code_action(
19904 &self,
19905 buffer_handle: Entity<Buffer>,
19906 action: CodeAction,
19907 _excerpt_id: ExcerptId,
19908 push_to_history: bool,
19909 _window: &mut Window,
19910 cx: &mut App,
19911 ) -> Task<Result<ProjectTransaction>> {
19912 self.update(cx, |project, cx| {
19913 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19914 })
19915 }
19916}
19917
19918fn snippet_completions(
19919 project: &Project,
19920 buffer: &Entity<Buffer>,
19921 buffer_position: text::Anchor,
19922 cx: &mut App,
19923) -> Task<Result<Vec<Completion>>> {
19924 let languages = buffer.read(cx).languages_at(buffer_position);
19925 let snippet_store = project.snippets().read(cx);
19926
19927 let scopes: Vec<_> = languages
19928 .iter()
19929 .filter_map(|language| {
19930 let language_name = language.lsp_id();
19931 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19932
19933 if snippets.is_empty() {
19934 None
19935 } else {
19936 Some((language.default_scope(), snippets))
19937 }
19938 })
19939 .collect();
19940
19941 if scopes.is_empty() {
19942 return Task::ready(Ok(vec![]));
19943 }
19944
19945 let snapshot = buffer.read(cx).text_snapshot();
19946 let chars: String = snapshot
19947 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19948 .collect();
19949 let executor = cx.background_executor().clone();
19950
19951 cx.background_spawn(async move {
19952 let mut all_results: Vec<Completion> = Vec::new();
19953 for (scope, snippets) in scopes.into_iter() {
19954 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19955 let mut last_word = chars
19956 .chars()
19957 .take_while(|c| classifier.is_word(*c))
19958 .collect::<String>();
19959 last_word = last_word.chars().rev().collect();
19960
19961 if last_word.is_empty() {
19962 return Ok(vec![]);
19963 }
19964
19965 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19966 let to_lsp = |point: &text::Anchor| {
19967 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19968 point_to_lsp(end)
19969 };
19970 let lsp_end = to_lsp(&buffer_position);
19971
19972 let candidates = snippets
19973 .iter()
19974 .enumerate()
19975 .flat_map(|(ix, snippet)| {
19976 snippet
19977 .prefix
19978 .iter()
19979 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19980 })
19981 .collect::<Vec<StringMatchCandidate>>();
19982
19983 let mut matches = fuzzy::match_strings(
19984 &candidates,
19985 &last_word,
19986 last_word.chars().any(|c| c.is_uppercase()),
19987 100,
19988 &Default::default(),
19989 executor.clone(),
19990 )
19991 .await;
19992
19993 // Remove all candidates where the query's start does not match the start of any word in the candidate
19994 if let Some(query_start) = last_word.chars().next() {
19995 matches.retain(|string_match| {
19996 split_words(&string_match.string).any(|word| {
19997 // Check that the first codepoint of the word as lowercase matches the first
19998 // codepoint of the query as lowercase
19999 word.chars()
20000 .flat_map(|codepoint| codepoint.to_lowercase())
20001 .zip(query_start.to_lowercase())
20002 .all(|(word_cp, query_cp)| word_cp == query_cp)
20003 })
20004 });
20005 }
20006
20007 let matched_strings = matches
20008 .into_iter()
20009 .map(|m| m.string)
20010 .collect::<HashSet<_>>();
20011
20012 let mut result: Vec<Completion> = snippets
20013 .iter()
20014 .filter_map(|snippet| {
20015 let matching_prefix = snippet
20016 .prefix
20017 .iter()
20018 .find(|prefix| matched_strings.contains(*prefix))?;
20019 let start = as_offset - last_word.len();
20020 let start = snapshot.anchor_before(start);
20021 let range = start..buffer_position;
20022 let lsp_start = to_lsp(&start);
20023 let lsp_range = lsp::Range {
20024 start: lsp_start,
20025 end: lsp_end,
20026 };
20027 Some(Completion {
20028 replace_range: range,
20029 new_text: snippet.body.clone(),
20030 source: CompletionSource::Lsp {
20031 insert_range: None,
20032 server_id: LanguageServerId(usize::MAX),
20033 resolved: true,
20034 lsp_completion: Box::new(lsp::CompletionItem {
20035 label: snippet.prefix.first().unwrap().clone(),
20036 kind: Some(CompletionItemKind::SNIPPET),
20037 label_details: snippet.description.as_ref().map(|description| {
20038 lsp::CompletionItemLabelDetails {
20039 detail: Some(description.clone()),
20040 description: None,
20041 }
20042 }),
20043 insert_text_format: Some(InsertTextFormat::SNIPPET),
20044 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20045 lsp::InsertReplaceEdit {
20046 new_text: snippet.body.clone(),
20047 insert: lsp_range,
20048 replace: lsp_range,
20049 },
20050 )),
20051 filter_text: Some(snippet.body.clone()),
20052 sort_text: Some(char::MAX.to_string()),
20053 ..lsp::CompletionItem::default()
20054 }),
20055 lsp_defaults: None,
20056 },
20057 label: CodeLabel {
20058 text: matching_prefix.clone(),
20059 runs: Vec::new(),
20060 filter_range: 0..matching_prefix.len(),
20061 },
20062 icon_path: None,
20063 documentation: Some(
20064 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20065 single_line: snippet.name.clone().into(),
20066 plain_text: snippet
20067 .description
20068 .clone()
20069 .map(|description| description.into()),
20070 },
20071 ),
20072 insert_text_mode: None,
20073 confirm: None,
20074 })
20075 })
20076 .collect();
20077
20078 all_results.append(&mut result);
20079 }
20080
20081 Ok(all_results)
20082 })
20083}
20084
20085impl CompletionProvider for Entity<Project> {
20086 fn completions(
20087 &self,
20088 _excerpt_id: ExcerptId,
20089 buffer: &Entity<Buffer>,
20090 buffer_position: text::Anchor,
20091 options: CompletionContext,
20092 _window: &mut Window,
20093 cx: &mut Context<Editor>,
20094 ) -> Task<Result<Option<Vec<Completion>>>> {
20095 self.update(cx, |project, cx| {
20096 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20097 let project_completions = project.completions(buffer, buffer_position, options, cx);
20098 cx.background_spawn(async move {
20099 let snippets_completions = snippets.await?;
20100 match project_completions.await? {
20101 Some(mut completions) => {
20102 completions.extend(snippets_completions);
20103 Ok(Some(completions))
20104 }
20105 None => {
20106 if snippets_completions.is_empty() {
20107 Ok(None)
20108 } else {
20109 Ok(Some(snippets_completions))
20110 }
20111 }
20112 }
20113 })
20114 })
20115 }
20116
20117 fn resolve_completions(
20118 &self,
20119 buffer: Entity<Buffer>,
20120 completion_indices: Vec<usize>,
20121 completions: Rc<RefCell<Box<[Completion]>>>,
20122 cx: &mut Context<Editor>,
20123 ) -> Task<Result<bool>> {
20124 self.update(cx, |project, cx| {
20125 project.lsp_store().update(cx, |lsp_store, cx| {
20126 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20127 })
20128 })
20129 }
20130
20131 fn apply_additional_edits_for_completion(
20132 &self,
20133 buffer: Entity<Buffer>,
20134 completions: Rc<RefCell<Box<[Completion]>>>,
20135 completion_index: usize,
20136 push_to_history: bool,
20137 cx: &mut Context<Editor>,
20138 ) -> Task<Result<Option<language::Transaction>>> {
20139 self.update(cx, |project, cx| {
20140 project.lsp_store().update(cx, |lsp_store, cx| {
20141 lsp_store.apply_additional_edits_for_completion(
20142 buffer,
20143 completions,
20144 completion_index,
20145 push_to_history,
20146 cx,
20147 )
20148 })
20149 })
20150 }
20151
20152 fn is_completion_trigger(
20153 &self,
20154 buffer: &Entity<Buffer>,
20155 position: language::Anchor,
20156 text: &str,
20157 trigger_in_words: bool,
20158 cx: &mut Context<Editor>,
20159 ) -> bool {
20160 let mut chars = text.chars();
20161 let char = if let Some(char) = chars.next() {
20162 char
20163 } else {
20164 return false;
20165 };
20166 if chars.next().is_some() {
20167 return false;
20168 }
20169
20170 let buffer = buffer.read(cx);
20171 let snapshot = buffer.snapshot();
20172 if !snapshot.settings_at(position, cx).show_completions_on_input {
20173 return false;
20174 }
20175 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20176 if trigger_in_words && classifier.is_word(char) {
20177 return true;
20178 }
20179
20180 buffer.completion_triggers().contains(text)
20181 }
20182}
20183
20184impl SemanticsProvider for Entity<Project> {
20185 fn hover(
20186 &self,
20187 buffer: &Entity<Buffer>,
20188 position: text::Anchor,
20189 cx: &mut App,
20190 ) -> Option<Task<Vec<project::Hover>>> {
20191 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20192 }
20193
20194 fn document_highlights(
20195 &self,
20196 buffer: &Entity<Buffer>,
20197 position: text::Anchor,
20198 cx: &mut App,
20199 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20200 Some(self.update(cx, |project, cx| {
20201 project.document_highlights(buffer, position, cx)
20202 }))
20203 }
20204
20205 fn definitions(
20206 &self,
20207 buffer: &Entity<Buffer>,
20208 position: text::Anchor,
20209 kind: GotoDefinitionKind,
20210 cx: &mut App,
20211 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20212 Some(self.update(cx, |project, cx| match kind {
20213 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20214 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20215 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20216 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20217 }))
20218 }
20219
20220 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20221 // TODO: make this work for remote projects
20222 self.update(cx, |project, cx| {
20223 if project
20224 .active_debug_session(cx)
20225 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20226 {
20227 return true;
20228 }
20229
20230 buffer.update(cx, |buffer, cx| {
20231 project.any_language_server_supports_inlay_hints(buffer, cx)
20232 })
20233 })
20234 }
20235
20236 fn inline_values(
20237 &self,
20238 buffer_handle: Entity<Buffer>,
20239 range: Range<text::Anchor>,
20240 cx: &mut App,
20241 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20242 self.update(cx, |project, cx| {
20243 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20244
20245 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20246 })
20247 }
20248
20249 fn inlay_hints(
20250 &self,
20251 buffer_handle: Entity<Buffer>,
20252 range: Range<text::Anchor>,
20253 cx: &mut App,
20254 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20255 Some(self.update(cx, |project, cx| {
20256 project.inlay_hints(buffer_handle, range, cx)
20257 }))
20258 }
20259
20260 fn resolve_inlay_hint(
20261 &self,
20262 hint: InlayHint,
20263 buffer_handle: Entity<Buffer>,
20264 server_id: LanguageServerId,
20265 cx: &mut App,
20266 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20267 Some(self.update(cx, |project, cx| {
20268 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20269 }))
20270 }
20271
20272 fn range_for_rename(
20273 &self,
20274 buffer: &Entity<Buffer>,
20275 position: text::Anchor,
20276 cx: &mut App,
20277 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20278 Some(self.update(cx, |project, cx| {
20279 let buffer = buffer.clone();
20280 let task = project.prepare_rename(buffer.clone(), position, cx);
20281 cx.spawn(async move |_, cx| {
20282 Ok(match task.await? {
20283 PrepareRenameResponse::Success(range) => Some(range),
20284 PrepareRenameResponse::InvalidPosition => None,
20285 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20286 // Fallback on using TreeSitter info to determine identifier range
20287 buffer.update(cx, |buffer, _| {
20288 let snapshot = buffer.snapshot();
20289 let (range, kind) = snapshot.surrounding_word(position);
20290 if kind != Some(CharKind::Word) {
20291 return None;
20292 }
20293 Some(
20294 snapshot.anchor_before(range.start)
20295 ..snapshot.anchor_after(range.end),
20296 )
20297 })?
20298 }
20299 })
20300 })
20301 }))
20302 }
20303
20304 fn perform_rename(
20305 &self,
20306 buffer: &Entity<Buffer>,
20307 position: text::Anchor,
20308 new_name: String,
20309 cx: &mut App,
20310 ) -> Option<Task<Result<ProjectTransaction>>> {
20311 Some(self.update(cx, |project, cx| {
20312 project.perform_rename(buffer.clone(), position, new_name, cx)
20313 }))
20314 }
20315}
20316
20317fn inlay_hint_settings(
20318 location: Anchor,
20319 snapshot: &MultiBufferSnapshot,
20320 cx: &mut Context<Editor>,
20321) -> InlayHintSettings {
20322 let file = snapshot.file_at(location);
20323 let language = snapshot.language_at(location).map(|l| l.name());
20324 language_settings(language, file, cx).inlay_hints
20325}
20326
20327fn consume_contiguous_rows(
20328 contiguous_row_selections: &mut Vec<Selection<Point>>,
20329 selection: &Selection<Point>,
20330 display_map: &DisplaySnapshot,
20331 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20332) -> (MultiBufferRow, MultiBufferRow) {
20333 contiguous_row_selections.push(selection.clone());
20334 let start_row = MultiBufferRow(selection.start.row);
20335 let mut end_row = ending_row(selection, display_map);
20336
20337 while let Some(next_selection) = selections.peek() {
20338 if next_selection.start.row <= end_row.0 {
20339 end_row = ending_row(next_selection, display_map);
20340 contiguous_row_selections.push(selections.next().unwrap().clone());
20341 } else {
20342 break;
20343 }
20344 }
20345 (start_row, end_row)
20346}
20347
20348fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20349 if next_selection.end.column > 0 || next_selection.is_empty() {
20350 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20351 } else {
20352 MultiBufferRow(next_selection.end.row)
20353 }
20354}
20355
20356impl EditorSnapshot {
20357 pub fn remote_selections_in_range<'a>(
20358 &'a self,
20359 range: &'a Range<Anchor>,
20360 collaboration_hub: &dyn CollaborationHub,
20361 cx: &'a App,
20362 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20363 let participant_names = collaboration_hub.user_names(cx);
20364 let participant_indices = collaboration_hub.user_participant_indices(cx);
20365 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20366 let collaborators_by_replica_id = collaborators_by_peer_id
20367 .values()
20368 .map(|collaborator| (collaborator.replica_id, collaborator))
20369 .collect::<HashMap<_, _>>();
20370 self.buffer_snapshot
20371 .selections_in_range(range, false)
20372 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20373 if replica_id == AGENT_REPLICA_ID {
20374 Some(RemoteSelection {
20375 replica_id,
20376 selection,
20377 cursor_shape,
20378 line_mode,
20379 collaborator_id: CollaboratorId::Agent,
20380 user_name: Some("Agent".into()),
20381 color: cx.theme().players().agent(),
20382 })
20383 } else {
20384 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20385 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20386 let user_name = participant_names.get(&collaborator.user_id).cloned();
20387 Some(RemoteSelection {
20388 replica_id,
20389 selection,
20390 cursor_shape,
20391 line_mode,
20392 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20393 user_name,
20394 color: if let Some(index) = participant_index {
20395 cx.theme().players().color_for_participant(index.0)
20396 } else {
20397 cx.theme().players().absent()
20398 },
20399 })
20400 }
20401 })
20402 }
20403
20404 pub fn hunks_for_ranges(
20405 &self,
20406 ranges: impl IntoIterator<Item = Range<Point>>,
20407 ) -> Vec<MultiBufferDiffHunk> {
20408 let mut hunks = Vec::new();
20409 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20410 HashMap::default();
20411 for query_range in ranges {
20412 let query_rows =
20413 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20414 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20415 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20416 ) {
20417 // Include deleted hunks that are adjacent to the query range, because
20418 // otherwise they would be missed.
20419 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20420 if hunk.status().is_deleted() {
20421 intersects_range |= hunk.row_range.start == query_rows.end;
20422 intersects_range |= hunk.row_range.end == query_rows.start;
20423 }
20424 if intersects_range {
20425 if !processed_buffer_rows
20426 .entry(hunk.buffer_id)
20427 .or_default()
20428 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20429 {
20430 continue;
20431 }
20432 hunks.push(hunk);
20433 }
20434 }
20435 }
20436
20437 hunks
20438 }
20439
20440 fn display_diff_hunks_for_rows<'a>(
20441 &'a self,
20442 display_rows: Range<DisplayRow>,
20443 folded_buffers: &'a HashSet<BufferId>,
20444 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20445 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20446 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20447
20448 self.buffer_snapshot
20449 .diff_hunks_in_range(buffer_start..buffer_end)
20450 .filter_map(|hunk| {
20451 if folded_buffers.contains(&hunk.buffer_id) {
20452 return None;
20453 }
20454
20455 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20456 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20457
20458 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20459 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20460
20461 let display_hunk = if hunk_display_start.column() != 0 {
20462 DisplayDiffHunk::Folded {
20463 display_row: hunk_display_start.row(),
20464 }
20465 } else {
20466 let mut end_row = hunk_display_end.row();
20467 if hunk_display_end.column() > 0 {
20468 end_row.0 += 1;
20469 }
20470 let is_created_file = hunk.is_created_file();
20471 DisplayDiffHunk::Unfolded {
20472 status: hunk.status(),
20473 diff_base_byte_range: hunk.diff_base_byte_range,
20474 display_row_range: hunk_display_start.row()..end_row,
20475 multi_buffer_range: Anchor::range_in_buffer(
20476 hunk.excerpt_id,
20477 hunk.buffer_id,
20478 hunk.buffer_range,
20479 ),
20480 is_created_file,
20481 }
20482 };
20483
20484 Some(display_hunk)
20485 })
20486 }
20487
20488 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20489 self.display_snapshot.buffer_snapshot.language_at(position)
20490 }
20491
20492 pub fn is_focused(&self) -> bool {
20493 self.is_focused
20494 }
20495
20496 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20497 self.placeholder_text.as_ref()
20498 }
20499
20500 pub fn scroll_position(&self) -> gpui::Point<f32> {
20501 self.scroll_anchor.scroll_position(&self.display_snapshot)
20502 }
20503
20504 fn gutter_dimensions(
20505 &self,
20506 font_id: FontId,
20507 font_size: Pixels,
20508 max_line_number_width: Pixels,
20509 cx: &App,
20510 ) -> Option<GutterDimensions> {
20511 if !self.show_gutter {
20512 return None;
20513 }
20514
20515 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20516 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20517
20518 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20519 matches!(
20520 ProjectSettings::get_global(cx).git.git_gutter,
20521 Some(GitGutterSetting::TrackedFiles)
20522 )
20523 });
20524 let gutter_settings = EditorSettings::get_global(cx).gutter;
20525 let show_line_numbers = self
20526 .show_line_numbers
20527 .unwrap_or(gutter_settings.line_numbers);
20528 let line_gutter_width = if show_line_numbers {
20529 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20530 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20531 max_line_number_width.max(min_width_for_number_on_gutter)
20532 } else {
20533 0.0.into()
20534 };
20535
20536 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20537 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20538
20539 let git_blame_entries_width =
20540 self.git_blame_gutter_max_author_length
20541 .map(|max_author_length| {
20542 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20543 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20544
20545 /// The number of characters to dedicate to gaps and margins.
20546 const SPACING_WIDTH: usize = 4;
20547
20548 let max_char_count = max_author_length.min(renderer.max_author_length())
20549 + ::git::SHORT_SHA_LENGTH
20550 + MAX_RELATIVE_TIMESTAMP.len()
20551 + SPACING_WIDTH;
20552
20553 em_advance * max_char_count
20554 });
20555
20556 let is_singleton = self.buffer_snapshot.is_singleton();
20557
20558 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20559 left_padding += if !is_singleton {
20560 em_width * 4.0
20561 } else if show_runnables || show_breakpoints {
20562 em_width * 3.0
20563 } else if show_git_gutter && show_line_numbers {
20564 em_width * 2.0
20565 } else if show_git_gutter || show_line_numbers {
20566 em_width
20567 } else {
20568 px(0.)
20569 };
20570
20571 let shows_folds = is_singleton && gutter_settings.folds;
20572
20573 let right_padding = if shows_folds && show_line_numbers {
20574 em_width * 4.0
20575 } else if shows_folds || (!is_singleton && show_line_numbers) {
20576 em_width * 3.0
20577 } else if show_line_numbers {
20578 em_width
20579 } else {
20580 px(0.)
20581 };
20582
20583 Some(GutterDimensions {
20584 left_padding,
20585 right_padding,
20586 width: line_gutter_width + left_padding + right_padding,
20587 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20588 git_blame_entries_width,
20589 })
20590 }
20591
20592 pub fn render_crease_toggle(
20593 &self,
20594 buffer_row: MultiBufferRow,
20595 row_contains_cursor: bool,
20596 editor: Entity<Editor>,
20597 window: &mut Window,
20598 cx: &mut App,
20599 ) -> Option<AnyElement> {
20600 let folded = self.is_line_folded(buffer_row);
20601 let mut is_foldable = false;
20602
20603 if let Some(crease) = self
20604 .crease_snapshot
20605 .query_row(buffer_row, &self.buffer_snapshot)
20606 {
20607 is_foldable = true;
20608 match crease {
20609 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20610 if let Some(render_toggle) = render_toggle {
20611 let toggle_callback =
20612 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20613 if folded {
20614 editor.update(cx, |editor, cx| {
20615 editor.fold_at(buffer_row, window, cx)
20616 });
20617 } else {
20618 editor.update(cx, |editor, cx| {
20619 editor.unfold_at(buffer_row, window, cx)
20620 });
20621 }
20622 });
20623 return Some((render_toggle)(
20624 buffer_row,
20625 folded,
20626 toggle_callback,
20627 window,
20628 cx,
20629 ));
20630 }
20631 }
20632 }
20633 }
20634
20635 is_foldable |= self.starts_indent(buffer_row);
20636
20637 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20638 Some(
20639 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20640 .toggle_state(folded)
20641 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20642 if folded {
20643 this.unfold_at(buffer_row, window, cx);
20644 } else {
20645 this.fold_at(buffer_row, window, cx);
20646 }
20647 }))
20648 .into_any_element(),
20649 )
20650 } else {
20651 None
20652 }
20653 }
20654
20655 pub fn render_crease_trailer(
20656 &self,
20657 buffer_row: MultiBufferRow,
20658 window: &mut Window,
20659 cx: &mut App,
20660 ) -> Option<AnyElement> {
20661 let folded = self.is_line_folded(buffer_row);
20662 if let Crease::Inline { render_trailer, .. } = self
20663 .crease_snapshot
20664 .query_row(buffer_row, &self.buffer_snapshot)?
20665 {
20666 let render_trailer = render_trailer.as_ref()?;
20667 Some(render_trailer(buffer_row, folded, window, cx))
20668 } else {
20669 None
20670 }
20671 }
20672}
20673
20674impl Deref for EditorSnapshot {
20675 type Target = DisplaySnapshot;
20676
20677 fn deref(&self) -> &Self::Target {
20678 &self.display_snapshot
20679 }
20680}
20681
20682#[derive(Clone, Debug, PartialEq, Eq)]
20683pub enum EditorEvent {
20684 InputIgnored {
20685 text: Arc<str>,
20686 },
20687 InputHandled {
20688 utf16_range_to_replace: Option<Range<isize>>,
20689 text: Arc<str>,
20690 },
20691 ExcerptsAdded {
20692 buffer: Entity<Buffer>,
20693 predecessor: ExcerptId,
20694 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20695 },
20696 ExcerptsRemoved {
20697 ids: Vec<ExcerptId>,
20698 removed_buffer_ids: Vec<BufferId>,
20699 },
20700 BufferFoldToggled {
20701 ids: Vec<ExcerptId>,
20702 folded: bool,
20703 },
20704 ExcerptsEdited {
20705 ids: Vec<ExcerptId>,
20706 },
20707 ExcerptsExpanded {
20708 ids: Vec<ExcerptId>,
20709 },
20710 BufferEdited,
20711 Edited {
20712 transaction_id: clock::Lamport,
20713 },
20714 Reparsed(BufferId),
20715 Focused,
20716 FocusedIn,
20717 Blurred,
20718 DirtyChanged,
20719 Saved,
20720 TitleChanged,
20721 DiffBaseChanged,
20722 SelectionsChanged {
20723 local: bool,
20724 },
20725 ScrollPositionChanged {
20726 local: bool,
20727 autoscroll: bool,
20728 },
20729 Closed,
20730 TransactionUndone {
20731 transaction_id: clock::Lamport,
20732 },
20733 TransactionBegun {
20734 transaction_id: clock::Lamport,
20735 },
20736 Reloaded,
20737 CursorShapeChanged,
20738 PushedToNavHistory {
20739 anchor: Anchor,
20740 is_deactivate: bool,
20741 },
20742}
20743
20744impl EventEmitter<EditorEvent> for Editor {}
20745
20746impl Focusable for Editor {
20747 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20748 self.focus_handle.clone()
20749 }
20750}
20751
20752impl Render for Editor {
20753 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20754 let settings = ThemeSettings::get_global(cx);
20755
20756 let mut text_style = match self.mode {
20757 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20758 color: cx.theme().colors().editor_foreground,
20759 font_family: settings.ui_font.family.clone(),
20760 font_features: settings.ui_font.features.clone(),
20761 font_fallbacks: settings.ui_font.fallbacks.clone(),
20762 font_size: rems(0.875).into(),
20763 font_weight: settings.ui_font.weight,
20764 line_height: relative(settings.buffer_line_height.value()),
20765 ..Default::default()
20766 },
20767 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20768 color: cx.theme().colors().editor_foreground,
20769 font_family: settings.buffer_font.family.clone(),
20770 font_features: settings.buffer_font.features.clone(),
20771 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20772 font_size: settings.buffer_font_size(cx).into(),
20773 font_weight: settings.buffer_font.weight,
20774 line_height: relative(settings.buffer_line_height.value()),
20775 ..Default::default()
20776 },
20777 };
20778 if let Some(text_style_refinement) = &self.text_style_refinement {
20779 text_style.refine(text_style_refinement)
20780 }
20781
20782 let background = match self.mode {
20783 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20784 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20785 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20786 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20787 };
20788
20789 EditorElement::new(
20790 &cx.entity(),
20791 EditorStyle {
20792 background,
20793 local_player: cx.theme().players().local(),
20794 text: text_style,
20795 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20796 syntax: cx.theme().syntax().clone(),
20797 status: cx.theme().status().clone(),
20798 inlay_hints_style: make_inlay_hints_style(cx),
20799 inline_completion_styles: make_suggestion_styles(cx),
20800 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20801 show_underlines: !self.mode.is_minimap(),
20802 },
20803 )
20804 }
20805}
20806
20807impl EntityInputHandler for Editor {
20808 fn text_for_range(
20809 &mut self,
20810 range_utf16: Range<usize>,
20811 adjusted_range: &mut Option<Range<usize>>,
20812 _: &mut Window,
20813 cx: &mut Context<Self>,
20814 ) -> Option<String> {
20815 let snapshot = self.buffer.read(cx).read(cx);
20816 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20817 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20818 if (start.0..end.0) != range_utf16 {
20819 adjusted_range.replace(start.0..end.0);
20820 }
20821 Some(snapshot.text_for_range(start..end).collect())
20822 }
20823
20824 fn selected_text_range(
20825 &mut self,
20826 ignore_disabled_input: bool,
20827 _: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) -> Option<UTF16Selection> {
20830 // Prevent the IME menu from appearing when holding down an alphabetic key
20831 // while input is disabled.
20832 if !ignore_disabled_input && !self.input_enabled {
20833 return None;
20834 }
20835
20836 let selection = self.selections.newest::<OffsetUtf16>(cx);
20837 let range = selection.range();
20838
20839 Some(UTF16Selection {
20840 range: range.start.0..range.end.0,
20841 reversed: selection.reversed,
20842 })
20843 }
20844
20845 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20846 let snapshot = self.buffer.read(cx).read(cx);
20847 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20848 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20849 }
20850
20851 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20852 self.clear_highlights::<InputComposition>(cx);
20853 self.ime_transaction.take();
20854 }
20855
20856 fn replace_text_in_range(
20857 &mut self,
20858 range_utf16: Option<Range<usize>>,
20859 text: &str,
20860 window: &mut Window,
20861 cx: &mut Context<Self>,
20862 ) {
20863 if !self.input_enabled {
20864 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20865 return;
20866 }
20867
20868 self.transact(window, cx, |this, window, cx| {
20869 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20870 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20871 Some(this.selection_replacement_ranges(range_utf16, cx))
20872 } else {
20873 this.marked_text_ranges(cx)
20874 };
20875
20876 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20877 let newest_selection_id = this.selections.newest_anchor().id;
20878 this.selections
20879 .all::<OffsetUtf16>(cx)
20880 .iter()
20881 .zip(ranges_to_replace.iter())
20882 .find_map(|(selection, range)| {
20883 if selection.id == newest_selection_id {
20884 Some(
20885 (range.start.0 as isize - selection.head().0 as isize)
20886 ..(range.end.0 as isize - selection.head().0 as isize),
20887 )
20888 } else {
20889 None
20890 }
20891 })
20892 });
20893
20894 cx.emit(EditorEvent::InputHandled {
20895 utf16_range_to_replace: range_to_replace,
20896 text: text.into(),
20897 });
20898
20899 if let Some(new_selected_ranges) = new_selected_ranges {
20900 this.change_selections(None, window, cx, |selections| {
20901 selections.select_ranges(new_selected_ranges)
20902 });
20903 this.backspace(&Default::default(), window, cx);
20904 }
20905
20906 this.handle_input(text, window, cx);
20907 });
20908
20909 if let Some(transaction) = self.ime_transaction {
20910 self.buffer.update(cx, |buffer, cx| {
20911 buffer.group_until_transaction(transaction, cx);
20912 });
20913 }
20914
20915 self.unmark_text(window, cx);
20916 }
20917
20918 fn replace_and_mark_text_in_range(
20919 &mut self,
20920 range_utf16: Option<Range<usize>>,
20921 text: &str,
20922 new_selected_range_utf16: Option<Range<usize>>,
20923 window: &mut Window,
20924 cx: &mut Context<Self>,
20925 ) {
20926 if !self.input_enabled {
20927 return;
20928 }
20929
20930 let transaction = self.transact(window, cx, |this, window, cx| {
20931 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20932 let snapshot = this.buffer.read(cx).read(cx);
20933 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20934 for marked_range in &mut marked_ranges {
20935 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20936 marked_range.start.0 += relative_range_utf16.start;
20937 marked_range.start =
20938 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20939 marked_range.end =
20940 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20941 }
20942 }
20943 Some(marked_ranges)
20944 } else if let Some(range_utf16) = range_utf16 {
20945 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20946 Some(this.selection_replacement_ranges(range_utf16, cx))
20947 } else {
20948 None
20949 };
20950
20951 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20952 let newest_selection_id = this.selections.newest_anchor().id;
20953 this.selections
20954 .all::<OffsetUtf16>(cx)
20955 .iter()
20956 .zip(ranges_to_replace.iter())
20957 .find_map(|(selection, range)| {
20958 if selection.id == newest_selection_id {
20959 Some(
20960 (range.start.0 as isize - selection.head().0 as isize)
20961 ..(range.end.0 as isize - selection.head().0 as isize),
20962 )
20963 } else {
20964 None
20965 }
20966 })
20967 });
20968
20969 cx.emit(EditorEvent::InputHandled {
20970 utf16_range_to_replace: range_to_replace,
20971 text: text.into(),
20972 });
20973
20974 if let Some(ranges) = ranges_to_replace {
20975 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20976 }
20977
20978 let marked_ranges = {
20979 let snapshot = this.buffer.read(cx).read(cx);
20980 this.selections
20981 .disjoint_anchors()
20982 .iter()
20983 .map(|selection| {
20984 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20985 })
20986 .collect::<Vec<_>>()
20987 };
20988
20989 if text.is_empty() {
20990 this.unmark_text(window, cx);
20991 } else {
20992 this.highlight_text::<InputComposition>(
20993 marked_ranges.clone(),
20994 HighlightStyle {
20995 underline: Some(UnderlineStyle {
20996 thickness: px(1.),
20997 color: None,
20998 wavy: false,
20999 }),
21000 ..Default::default()
21001 },
21002 cx,
21003 );
21004 }
21005
21006 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
21007 let use_autoclose = this.use_autoclose;
21008 let use_auto_surround = this.use_auto_surround;
21009 this.set_use_autoclose(false);
21010 this.set_use_auto_surround(false);
21011 this.handle_input(text, window, cx);
21012 this.set_use_autoclose(use_autoclose);
21013 this.set_use_auto_surround(use_auto_surround);
21014
21015 if let Some(new_selected_range) = new_selected_range_utf16 {
21016 let snapshot = this.buffer.read(cx).read(cx);
21017 let new_selected_ranges = marked_ranges
21018 .into_iter()
21019 .map(|marked_range| {
21020 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21021 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21022 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21023 snapshot.clip_offset_utf16(new_start, Bias::Left)
21024 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21025 })
21026 .collect::<Vec<_>>();
21027
21028 drop(snapshot);
21029 this.change_selections(None, window, cx, |selections| {
21030 selections.select_ranges(new_selected_ranges)
21031 });
21032 }
21033 });
21034
21035 self.ime_transaction = self.ime_transaction.or(transaction);
21036 if let Some(transaction) = self.ime_transaction {
21037 self.buffer.update(cx, |buffer, cx| {
21038 buffer.group_until_transaction(transaction, cx);
21039 });
21040 }
21041
21042 if self.text_highlights::<InputComposition>(cx).is_none() {
21043 self.ime_transaction.take();
21044 }
21045 }
21046
21047 fn bounds_for_range(
21048 &mut self,
21049 range_utf16: Range<usize>,
21050 element_bounds: gpui::Bounds<Pixels>,
21051 window: &mut Window,
21052 cx: &mut Context<Self>,
21053 ) -> Option<gpui::Bounds<Pixels>> {
21054 let text_layout_details = self.text_layout_details(window);
21055 let gpui::Size {
21056 width: em_width,
21057 height: line_height,
21058 } = self.character_size(window);
21059
21060 let snapshot = self.snapshot(window, cx);
21061 let scroll_position = snapshot.scroll_position();
21062 let scroll_left = scroll_position.x * em_width;
21063
21064 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21065 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21066 + self.gutter_dimensions.width
21067 + self.gutter_dimensions.margin;
21068 let y = line_height * (start.row().as_f32() - scroll_position.y);
21069
21070 Some(Bounds {
21071 origin: element_bounds.origin + point(x, y),
21072 size: size(em_width, line_height),
21073 })
21074 }
21075
21076 fn character_index_for_point(
21077 &mut self,
21078 point: gpui::Point<Pixels>,
21079 _window: &mut Window,
21080 _cx: &mut Context<Self>,
21081 ) -> Option<usize> {
21082 let position_map = self.last_position_map.as_ref()?;
21083 if !position_map.text_hitbox.contains(&point) {
21084 return None;
21085 }
21086 let display_point = position_map.point_for_position(point).previous_valid;
21087 let anchor = position_map
21088 .snapshot
21089 .display_point_to_anchor(display_point, Bias::Left);
21090 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21091 Some(utf16_offset.0)
21092 }
21093}
21094
21095trait SelectionExt {
21096 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21097 fn spanned_rows(
21098 &self,
21099 include_end_if_at_line_start: bool,
21100 map: &DisplaySnapshot,
21101 ) -> Range<MultiBufferRow>;
21102}
21103
21104impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21105 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21106 let start = self
21107 .start
21108 .to_point(&map.buffer_snapshot)
21109 .to_display_point(map);
21110 let end = self
21111 .end
21112 .to_point(&map.buffer_snapshot)
21113 .to_display_point(map);
21114 if self.reversed {
21115 end..start
21116 } else {
21117 start..end
21118 }
21119 }
21120
21121 fn spanned_rows(
21122 &self,
21123 include_end_if_at_line_start: bool,
21124 map: &DisplaySnapshot,
21125 ) -> Range<MultiBufferRow> {
21126 let start = self.start.to_point(&map.buffer_snapshot);
21127 let mut end = self.end.to_point(&map.buffer_snapshot);
21128 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21129 end.row -= 1;
21130 }
21131
21132 let buffer_start = map.prev_line_boundary(start).0;
21133 let buffer_end = map.next_line_boundary(end).0;
21134 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21135 }
21136}
21137
21138impl<T: InvalidationRegion> InvalidationStack<T> {
21139 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21140 where
21141 S: Clone + ToOffset,
21142 {
21143 while let Some(region) = self.last() {
21144 let all_selections_inside_invalidation_ranges =
21145 if selections.len() == region.ranges().len() {
21146 selections
21147 .iter()
21148 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21149 .all(|(selection, invalidation_range)| {
21150 let head = selection.head().to_offset(buffer);
21151 invalidation_range.start <= head && invalidation_range.end >= head
21152 })
21153 } else {
21154 false
21155 };
21156
21157 if all_selections_inside_invalidation_ranges {
21158 break;
21159 } else {
21160 self.pop();
21161 }
21162 }
21163 }
21164}
21165
21166impl<T> Default for InvalidationStack<T> {
21167 fn default() -> Self {
21168 Self(Default::default())
21169 }
21170}
21171
21172impl<T> Deref for InvalidationStack<T> {
21173 type Target = Vec<T>;
21174
21175 fn deref(&self) -> &Self::Target {
21176 &self.0
21177 }
21178}
21179
21180impl<T> DerefMut for InvalidationStack<T> {
21181 fn deref_mut(&mut self) -> &mut Self::Target {
21182 &mut self.0
21183 }
21184}
21185
21186impl InvalidationRegion for SnippetState {
21187 fn ranges(&self) -> &[Range<Anchor>] {
21188 &self.ranges[self.active_index]
21189 }
21190}
21191
21192fn inline_completion_edit_text(
21193 current_snapshot: &BufferSnapshot,
21194 edits: &[(Range<Anchor>, String)],
21195 edit_preview: &EditPreview,
21196 include_deletions: bool,
21197 cx: &App,
21198) -> HighlightedText {
21199 let edits = edits
21200 .iter()
21201 .map(|(anchor, text)| {
21202 (
21203 anchor.start.text_anchor..anchor.end.text_anchor,
21204 text.clone(),
21205 )
21206 })
21207 .collect::<Vec<_>>();
21208
21209 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21210}
21211
21212pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21213 match severity {
21214 lsp::DiagnosticSeverity::ERROR => colors.error,
21215 lsp::DiagnosticSeverity::WARNING => colors.warning,
21216 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21217 lsp::DiagnosticSeverity::HINT => colors.info,
21218 _ => colors.ignored,
21219 }
21220}
21221
21222pub fn styled_runs_for_code_label<'a>(
21223 label: &'a CodeLabel,
21224 syntax_theme: &'a theme::SyntaxTheme,
21225) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21226 let fade_out = HighlightStyle {
21227 fade_out: Some(0.35),
21228 ..Default::default()
21229 };
21230
21231 let mut prev_end = label.filter_range.end;
21232 label
21233 .runs
21234 .iter()
21235 .enumerate()
21236 .flat_map(move |(ix, (range, highlight_id))| {
21237 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21238 style
21239 } else {
21240 return Default::default();
21241 };
21242 let mut muted_style = style;
21243 muted_style.highlight(fade_out);
21244
21245 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21246 if range.start >= label.filter_range.end {
21247 if range.start > prev_end {
21248 runs.push((prev_end..range.start, fade_out));
21249 }
21250 runs.push((range.clone(), muted_style));
21251 } else if range.end <= label.filter_range.end {
21252 runs.push((range.clone(), style));
21253 } else {
21254 runs.push((range.start..label.filter_range.end, style));
21255 runs.push((label.filter_range.end..range.end, muted_style));
21256 }
21257 prev_end = cmp::max(prev_end, range.end);
21258
21259 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21260 runs.push((prev_end..label.text.len(), fade_out));
21261 }
21262
21263 runs
21264 })
21265}
21266
21267pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21268 let mut prev_index = 0;
21269 let mut prev_codepoint: Option<char> = None;
21270 text.char_indices()
21271 .chain([(text.len(), '\0')])
21272 .filter_map(move |(index, codepoint)| {
21273 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21274 let is_boundary = index == text.len()
21275 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21276 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21277 if is_boundary {
21278 let chunk = &text[prev_index..index];
21279 prev_index = index;
21280 Some(chunk)
21281 } else {
21282 None
21283 }
21284 })
21285}
21286
21287pub trait RangeToAnchorExt: Sized {
21288 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21289
21290 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21291 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21292 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21293 }
21294}
21295
21296impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21297 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21298 let start_offset = self.start.to_offset(snapshot);
21299 let end_offset = self.end.to_offset(snapshot);
21300 if start_offset == end_offset {
21301 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21302 } else {
21303 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21304 }
21305 }
21306}
21307
21308pub trait RowExt {
21309 fn as_f32(&self) -> f32;
21310
21311 fn next_row(&self) -> Self;
21312
21313 fn previous_row(&self) -> Self;
21314
21315 fn minus(&self, other: Self) -> u32;
21316}
21317
21318impl RowExt for DisplayRow {
21319 fn as_f32(&self) -> f32 {
21320 self.0 as f32
21321 }
21322
21323 fn next_row(&self) -> Self {
21324 Self(self.0 + 1)
21325 }
21326
21327 fn previous_row(&self) -> Self {
21328 Self(self.0.saturating_sub(1))
21329 }
21330
21331 fn minus(&self, other: Self) -> u32 {
21332 self.0 - other.0
21333 }
21334}
21335
21336impl RowExt for MultiBufferRow {
21337 fn as_f32(&self) -> f32 {
21338 self.0 as f32
21339 }
21340
21341 fn next_row(&self) -> Self {
21342 Self(self.0 + 1)
21343 }
21344
21345 fn previous_row(&self) -> Self {
21346 Self(self.0.saturating_sub(1))
21347 }
21348
21349 fn minus(&self, other: Self) -> u32 {
21350 self.0 - other.0
21351 }
21352}
21353
21354trait RowRangeExt {
21355 type Row;
21356
21357 fn len(&self) -> usize;
21358
21359 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21360}
21361
21362impl RowRangeExt for Range<MultiBufferRow> {
21363 type Row = MultiBufferRow;
21364
21365 fn len(&self) -> usize {
21366 (self.end.0 - self.start.0) as usize
21367 }
21368
21369 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21370 (self.start.0..self.end.0).map(MultiBufferRow)
21371 }
21372}
21373
21374impl RowRangeExt for Range<DisplayRow> {
21375 type Row = DisplayRow;
21376
21377 fn len(&self) -> usize {
21378 (self.end.0 - self.start.0) as usize
21379 }
21380
21381 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21382 (self.start.0..self.end.0).map(DisplayRow)
21383 }
21384}
21385
21386/// If select range has more than one line, we
21387/// just point the cursor to range.start.
21388fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21389 if range.start.row == range.end.row {
21390 range
21391 } else {
21392 range.start..range.start
21393 }
21394}
21395pub struct KillRing(ClipboardItem);
21396impl Global for KillRing {}
21397
21398const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21399
21400enum BreakpointPromptEditAction {
21401 Log,
21402 Condition,
21403 HitCondition,
21404}
21405
21406struct BreakpointPromptEditor {
21407 pub(crate) prompt: Entity<Editor>,
21408 editor: WeakEntity<Editor>,
21409 breakpoint_anchor: Anchor,
21410 breakpoint: Breakpoint,
21411 edit_action: BreakpointPromptEditAction,
21412 block_ids: HashSet<CustomBlockId>,
21413 editor_margins: Arc<Mutex<EditorMargins>>,
21414 _subscriptions: Vec<Subscription>,
21415}
21416
21417impl BreakpointPromptEditor {
21418 const MAX_LINES: u8 = 4;
21419
21420 fn new(
21421 editor: WeakEntity<Editor>,
21422 breakpoint_anchor: Anchor,
21423 breakpoint: Breakpoint,
21424 edit_action: BreakpointPromptEditAction,
21425 window: &mut Window,
21426 cx: &mut Context<Self>,
21427 ) -> Self {
21428 let base_text = match edit_action {
21429 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21430 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21431 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21432 }
21433 .map(|msg| msg.to_string())
21434 .unwrap_or_default();
21435
21436 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21437 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21438
21439 let prompt = cx.new(|cx| {
21440 let mut prompt = Editor::new(
21441 EditorMode::AutoHeight {
21442 max_lines: Self::MAX_LINES as usize,
21443 },
21444 buffer,
21445 None,
21446 window,
21447 cx,
21448 );
21449 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21450 prompt.set_show_cursor_when_unfocused(false, cx);
21451 prompt.set_placeholder_text(
21452 match edit_action {
21453 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21454 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21455 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21456 },
21457 cx,
21458 );
21459
21460 prompt
21461 });
21462
21463 Self {
21464 prompt,
21465 editor,
21466 breakpoint_anchor,
21467 breakpoint,
21468 edit_action,
21469 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21470 block_ids: Default::default(),
21471 _subscriptions: vec![],
21472 }
21473 }
21474
21475 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21476 self.block_ids.extend(block_ids)
21477 }
21478
21479 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21480 if let Some(editor) = self.editor.upgrade() {
21481 let message = self
21482 .prompt
21483 .read(cx)
21484 .buffer
21485 .read(cx)
21486 .as_singleton()
21487 .expect("A multi buffer in breakpoint prompt isn't possible")
21488 .read(cx)
21489 .as_rope()
21490 .to_string();
21491
21492 editor.update(cx, |editor, cx| {
21493 editor.edit_breakpoint_at_anchor(
21494 self.breakpoint_anchor,
21495 self.breakpoint.clone(),
21496 match self.edit_action {
21497 BreakpointPromptEditAction::Log => {
21498 BreakpointEditAction::EditLogMessage(message.into())
21499 }
21500 BreakpointPromptEditAction::Condition => {
21501 BreakpointEditAction::EditCondition(message.into())
21502 }
21503 BreakpointPromptEditAction::HitCondition => {
21504 BreakpointEditAction::EditHitCondition(message.into())
21505 }
21506 },
21507 cx,
21508 );
21509
21510 editor.remove_blocks(self.block_ids.clone(), None, cx);
21511 cx.focus_self(window);
21512 });
21513 }
21514 }
21515
21516 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21517 self.editor
21518 .update(cx, |editor, cx| {
21519 editor.remove_blocks(self.block_ids.clone(), None, cx);
21520 window.focus(&editor.focus_handle);
21521 })
21522 .log_err();
21523 }
21524
21525 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21526 let settings = ThemeSettings::get_global(cx);
21527 let text_style = TextStyle {
21528 color: if self.prompt.read(cx).read_only(cx) {
21529 cx.theme().colors().text_disabled
21530 } else {
21531 cx.theme().colors().text
21532 },
21533 font_family: settings.buffer_font.family.clone(),
21534 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21535 font_size: settings.buffer_font_size(cx).into(),
21536 font_weight: settings.buffer_font.weight,
21537 line_height: relative(settings.buffer_line_height.value()),
21538 ..Default::default()
21539 };
21540 EditorElement::new(
21541 &self.prompt,
21542 EditorStyle {
21543 background: cx.theme().colors().editor_background,
21544 local_player: cx.theme().players().local(),
21545 text: text_style,
21546 ..Default::default()
21547 },
21548 )
21549 }
21550}
21551
21552impl Render for BreakpointPromptEditor {
21553 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21554 let editor_margins = *self.editor_margins.lock();
21555 let gutter_dimensions = editor_margins.gutter;
21556 h_flex()
21557 .key_context("Editor")
21558 .bg(cx.theme().colors().editor_background)
21559 .border_y_1()
21560 .border_color(cx.theme().status().info_border)
21561 .size_full()
21562 .py(window.line_height() / 2.5)
21563 .on_action(cx.listener(Self::confirm))
21564 .on_action(cx.listener(Self::cancel))
21565 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21566 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21567 }
21568}
21569
21570impl Focusable for BreakpointPromptEditor {
21571 fn focus_handle(&self, cx: &App) -> FocusHandle {
21572 self.prompt.focus_handle(cx)
21573 }
21574}
21575
21576fn all_edits_insertions_or_deletions(
21577 edits: &Vec<(Range<Anchor>, String)>,
21578 snapshot: &MultiBufferSnapshot,
21579) -> bool {
21580 let mut all_insertions = true;
21581 let mut all_deletions = true;
21582
21583 for (range, new_text) in edits.iter() {
21584 let range_is_empty = range.to_offset(&snapshot).is_empty();
21585 let text_is_empty = new_text.is_empty();
21586
21587 if range_is_empty != text_is_empty {
21588 if range_is_empty {
21589 all_deletions = false;
21590 } else {
21591 all_insertions = false;
21592 }
21593 } else {
21594 return false;
21595 }
21596
21597 if !all_insertions && !all_deletions {
21598 return false;
21599 }
21600 }
21601 all_insertions || all_deletions
21602}
21603
21604struct MissingEditPredictionKeybindingTooltip;
21605
21606impl Render for MissingEditPredictionKeybindingTooltip {
21607 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21608 ui::tooltip_container(window, cx, |container, _, cx| {
21609 container
21610 .flex_shrink_0()
21611 .max_w_80()
21612 .min_h(rems_from_px(124.))
21613 .justify_between()
21614 .child(
21615 v_flex()
21616 .flex_1()
21617 .text_ui_sm(cx)
21618 .child(Label::new("Conflict with Accept Keybinding"))
21619 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21620 )
21621 .child(
21622 h_flex()
21623 .pb_1()
21624 .gap_1()
21625 .items_end()
21626 .w_full()
21627 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21628 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21629 }))
21630 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21631 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21632 })),
21633 )
21634 })
21635 }
21636}
21637
21638#[derive(Debug, Clone, Copy, PartialEq)]
21639pub struct LineHighlight {
21640 pub background: Background,
21641 pub border: Option<gpui::Hsla>,
21642 pub include_gutter: bool,
21643 pub type_id: Option<TypeId>,
21644}
21645
21646fn render_diff_hunk_controls(
21647 row: u32,
21648 status: &DiffHunkStatus,
21649 hunk_range: Range<Anchor>,
21650 is_created_file: bool,
21651 line_height: Pixels,
21652 editor: &Entity<Editor>,
21653 _window: &mut Window,
21654 cx: &mut App,
21655) -> AnyElement {
21656 h_flex()
21657 .h(line_height)
21658 .mr_1()
21659 .gap_1()
21660 .px_0p5()
21661 .pb_1()
21662 .border_x_1()
21663 .border_b_1()
21664 .border_color(cx.theme().colors().border_variant)
21665 .rounded_b_lg()
21666 .bg(cx.theme().colors().editor_background)
21667 .gap_1()
21668 .occlude()
21669 .shadow_md()
21670 .child(if status.has_secondary_hunk() {
21671 Button::new(("stage", row as u64), "Stage")
21672 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21673 .tooltip({
21674 let focus_handle = editor.focus_handle(cx);
21675 move |window, cx| {
21676 Tooltip::for_action_in(
21677 "Stage Hunk",
21678 &::git::ToggleStaged,
21679 &focus_handle,
21680 window,
21681 cx,
21682 )
21683 }
21684 })
21685 .on_click({
21686 let editor = editor.clone();
21687 move |_event, _window, cx| {
21688 editor.update(cx, |editor, cx| {
21689 editor.stage_or_unstage_diff_hunks(
21690 true,
21691 vec![hunk_range.start..hunk_range.start],
21692 cx,
21693 );
21694 });
21695 }
21696 })
21697 } else {
21698 Button::new(("unstage", row as u64), "Unstage")
21699 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21700 .tooltip({
21701 let focus_handle = editor.focus_handle(cx);
21702 move |window, cx| {
21703 Tooltip::for_action_in(
21704 "Unstage Hunk",
21705 &::git::ToggleStaged,
21706 &focus_handle,
21707 window,
21708 cx,
21709 )
21710 }
21711 })
21712 .on_click({
21713 let editor = editor.clone();
21714 move |_event, _window, cx| {
21715 editor.update(cx, |editor, cx| {
21716 editor.stage_or_unstage_diff_hunks(
21717 false,
21718 vec![hunk_range.start..hunk_range.start],
21719 cx,
21720 );
21721 });
21722 }
21723 })
21724 })
21725 .child(
21726 Button::new(("restore", row as u64), "Restore")
21727 .tooltip({
21728 let focus_handle = editor.focus_handle(cx);
21729 move |window, cx| {
21730 Tooltip::for_action_in(
21731 "Restore Hunk",
21732 &::git::Restore,
21733 &focus_handle,
21734 window,
21735 cx,
21736 )
21737 }
21738 })
21739 .on_click({
21740 let editor = editor.clone();
21741 move |_event, window, cx| {
21742 editor.update(cx, |editor, cx| {
21743 let snapshot = editor.snapshot(window, cx);
21744 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21745 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21746 });
21747 }
21748 })
21749 .disabled(is_created_file),
21750 )
21751 .when(
21752 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21753 |el| {
21754 el.child(
21755 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21756 .shape(IconButtonShape::Square)
21757 .icon_size(IconSize::Small)
21758 // .disabled(!has_multiple_hunks)
21759 .tooltip({
21760 let focus_handle = editor.focus_handle(cx);
21761 move |window, cx| {
21762 Tooltip::for_action_in(
21763 "Next Hunk",
21764 &GoToHunk,
21765 &focus_handle,
21766 window,
21767 cx,
21768 )
21769 }
21770 })
21771 .on_click({
21772 let editor = editor.clone();
21773 move |_event, window, cx| {
21774 editor.update(cx, |editor, cx| {
21775 let snapshot = editor.snapshot(window, cx);
21776 let position =
21777 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21778 editor.go_to_hunk_before_or_after_position(
21779 &snapshot,
21780 position,
21781 Direction::Next,
21782 window,
21783 cx,
21784 );
21785 editor.expand_selected_diff_hunks(cx);
21786 });
21787 }
21788 }),
21789 )
21790 .child(
21791 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21792 .shape(IconButtonShape::Square)
21793 .icon_size(IconSize::Small)
21794 // .disabled(!has_multiple_hunks)
21795 .tooltip({
21796 let focus_handle = editor.focus_handle(cx);
21797 move |window, cx| {
21798 Tooltip::for_action_in(
21799 "Previous Hunk",
21800 &GoToPreviousHunk,
21801 &focus_handle,
21802 window,
21803 cx,
21804 )
21805 }
21806 })
21807 .on_click({
21808 let editor = editor.clone();
21809 move |_event, window, cx| {
21810 editor.update(cx, |editor, cx| {
21811 let snapshot = editor.snapshot(window, cx);
21812 let point =
21813 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21814 editor.go_to_hunk_before_or_after_position(
21815 &snapshot,
21816 point,
21817 Direction::Prev,
21818 window,
21819 cx,
21820 );
21821 editor.expand_selected_diff_hunks(cx);
21822 });
21823 }
21824 }),
21825 )
21826 },
21827 )
21828 .into_any_element()
21829}