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 ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
129 },
130 session::{Session, SessionEvent},
131 },
132 project_settings::DiagnosticSeverity,
133};
134
135pub use git::blame::BlameRenderer;
136pub use proposed_changes_editor::{
137 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
138};
139use smallvec::smallvec;
140use std::{cell::OnceCell, iter::Peekable, ops::Not};
141use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
142
143pub use lsp::CompletionContext;
144use lsp::{
145 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
146 LanguageServerId, LanguageServerName,
147};
148
149use language::BufferSnapshot;
150pub use lsp_ext::lsp_tasks;
151use movement::TextLayoutDetails;
152pub use multi_buffer::{
153 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
154 RowInfo, ToOffset, ToPoint,
155};
156use multi_buffer::{
157 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
158 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
159};
160use parking_lot::Mutex;
161use project::{
162 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
163 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
164 TaskSourceKind,
165 debugger::breakpoint_store::Breakpoint,
166 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
167 project_settings::{GitGutterSetting, ProjectSettings},
168};
169use rand::prelude::*;
170use rpc::{ErrorExt, proto::*};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
172use selections_collection::{
173 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
174};
175use serde::{Deserialize, Serialize};
176use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
177use smallvec::SmallVec;
178use snippet::Snippet;
179use std::sync::Arc;
180use std::{
181 any::TypeId,
182 borrow::Cow,
183 cell::RefCell,
184 cmp::{self, Ordering, Reverse},
185 mem,
186 num::NonZeroU32,
187 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 time::{Duration, Instant},
191};
192pub use sum_tree::Bias;
193use sum_tree::TreeMap;
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
195use theme::{
196 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Key, Tooltip, h_flex, prelude::*,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{ItemHandle, PreviewTabsSettings},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::SearchEvent,
211};
212
213use crate::hover_links::{find_url, find_url_from_range};
214use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
219const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
220const MAX_LINE_LEN: usize = 1024;
221const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
222const MAX_SELECTION_HISTORY_LEN: usize = 1024;
223pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
224#[doc(hidden)]
225pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
226const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
227
228pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
235pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
236
237pub type RenderDiffHunkControlsFn = Arc<
238 dyn Fn(
239 u32,
240 &DiffHunkStatus,
241 Range<Anchor>,
242 bool,
243 Pixels,
244 &Entity<Editor>,
245 &mut Window,
246 &mut App,
247 ) -> AnyElement,
248>;
249
250const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
251 alt: true,
252 shift: true,
253 control: false,
254 platform: false,
255 function: false,
256};
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296enum SelectedTextHighlight {}
297
298pub enum ConflictsOuter {}
299pub enum ConflictsOurs {}
300pub enum ConflictsTheirs {}
301pub enum ConflictsOursMarker {}
302pub enum ConflictsTheirsMarker {}
303
304#[derive(Debug, Copy, Clone, PartialEq, Eq)]
305pub enum Navigated {
306 Yes,
307 No,
308}
309
310impl Navigated {
311 pub fn from_bool(yes: bool) -> Navigated {
312 if yes { Navigated::Yes } else { Navigated::No }
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq)]
317enum DisplayDiffHunk {
318 Folded {
319 display_row: DisplayRow,
320 },
321 Unfolded {
322 is_created_file: bool,
323 diff_base_byte_range: Range<usize>,
324 display_row_range: Range<DisplayRow>,
325 multi_buffer_range: Range<Anchor>,
326 status: DiffHunkStatus,
327 },
328}
329
330pub enum HideMouseCursorOrigin {
331 TypingAction,
332 MovementAction,
333}
334
335pub fn init_settings(cx: &mut App) {
336 EditorSettings::register(cx);
337}
338
339pub fn init(cx: &mut App) {
340 init_settings(cx);
341
342 cx.set_global(GlobalBlameRenderer(Arc::new(())));
343
344 workspace::register_project_item::<Editor>(cx);
345 workspace::FollowableViewRegistry::register::<Editor>(cx);
346 workspace::register_serializable_item::<Editor>(cx);
347
348 cx.observe_new(
349 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
350 workspace.register_action(Editor::new_file);
351 workspace.register_action(Editor::new_file_vertical);
352 workspace.register_action(Editor::new_file_horizontal);
353 workspace.register_action(Editor::cancel_language_server_work);
354 },
355 )
356 .detach();
357
358 cx.on_action(move |_: &workspace::NewFile, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 Editor::new_file(workspace, &Default::default(), window, cx)
367 },
368 )
369 .detach();
370 }
371 });
372 cx.on_action(move |_: &workspace::NewWindow, cx| {
373 let app_state = workspace::AppState::global(cx);
374 if let Some(app_state) = app_state.upgrade() {
375 workspace::open_new(
376 Default::default(),
377 app_state,
378 cx,
379 |workspace, window, cx| {
380 cx.activate(true);
381 Editor::new_file(workspace, &Default::default(), window, cx)
382 },
383 )
384 .detach();
385 }
386 });
387}
388
389pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
390 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
391}
392
393pub trait DiagnosticRenderer {
394 fn render_group(
395 &self,
396 diagnostic_group: Vec<DiagnosticEntry<Point>>,
397 buffer_id: BufferId,
398 snapshot: EditorSnapshot,
399 editor: WeakEntity<Editor>,
400 cx: &mut App,
401 ) -> Vec<BlockProperties<Anchor>>;
402
403 fn render_hover(
404 &self,
405 diagnostic_group: Vec<DiagnosticEntry<Point>>,
406 range: Range<Point>,
407 buffer_id: BufferId,
408 cx: &mut App,
409 ) -> Option<Entity<markdown::Markdown>>;
410
411 fn open_link(
412 &self,
413 editor: &mut Editor,
414 link: SharedString,
415 window: &mut Window,
416 cx: &mut Context<Editor>,
417 );
418}
419
420pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
421
422impl GlobalDiagnosticRenderer {
423 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
424 cx.try_global::<Self>().map(|g| g.0.clone())
425 }
426}
427
428impl gpui::Global for GlobalDiagnosticRenderer {}
429pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
430 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
431}
432
433pub struct SearchWithinRange;
434
435trait InvalidationRegion {
436 fn ranges(&self) -> &[Range<Anchor>];
437}
438
439#[derive(Clone, Debug, PartialEq)]
440pub enum SelectPhase {
441 Begin {
442 position: DisplayPoint,
443 add: bool,
444 click_count: usize,
445 },
446 BeginColumnar {
447 position: DisplayPoint,
448 reset: bool,
449 goal_column: u32,
450 },
451 Extend {
452 position: DisplayPoint,
453 click_count: usize,
454 },
455 Update {
456 position: DisplayPoint,
457 goal_column: u32,
458 scroll_delta: gpui::Point<f32>,
459 },
460 End,
461}
462
463#[derive(Clone, Debug)]
464pub enum SelectMode {
465 Character,
466 Word(Range<Anchor>),
467 Line(Range<Anchor>),
468 All,
469}
470
471#[derive(Clone, PartialEq, Eq, Debug)]
472pub enum EditorMode {
473 SingleLine {
474 auto_width: bool,
475 },
476 AutoHeight {
477 max_lines: usize,
478 },
479 Full {
480 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
481 scale_ui_elements_with_buffer_font_size: bool,
482 /// When set to `true`, the editor will render a background for the active line.
483 show_active_line_background: bool,
484 /// When set to `true`, the editor's height will be determined by its content.
485 sized_by_content: bool,
486 },
487 Minimap {
488 parent: WeakEntity<Editor>,
489 },
490}
491
492impl EditorMode {
493 pub fn full() -> Self {
494 Self::Full {
495 scale_ui_elements_with_buffer_font_size: true,
496 show_active_line_background: true,
497 sized_by_content: false,
498 }
499 }
500
501 pub fn is_full(&self) -> bool {
502 matches!(self, Self::Full { .. })
503 }
504
505 fn is_minimap(&self) -> bool {
506 matches!(self, Self::Minimap { .. })
507 }
508}
509
510#[derive(Copy, Clone, Debug)]
511pub enum SoftWrap {
512 /// Prefer not to wrap at all.
513 ///
514 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
515 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
516 GitDiff,
517 /// Prefer a single line generally, unless an overly long line is encountered.
518 None,
519 /// Soft wrap lines that exceed the editor width.
520 EditorWidth,
521 /// Soft wrap lines at the preferred line length.
522 Column(u32),
523 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
524 Bounded(u32),
525}
526
527#[derive(Clone)]
528pub struct EditorStyle {
529 pub background: Hsla,
530 pub local_player: PlayerColor,
531 pub text: TextStyle,
532 pub scrollbar_width: Pixels,
533 pub syntax: Arc<SyntaxTheme>,
534 pub status: StatusColors,
535 pub inlay_hints_style: HighlightStyle,
536 pub inline_completion_styles: InlineCompletionStyles,
537 pub unnecessary_code_fade: f32,
538 pub show_underlines: bool,
539}
540
541impl Default for EditorStyle {
542 fn default() -> Self {
543 Self {
544 background: Hsla::default(),
545 local_player: PlayerColor::default(),
546 text: TextStyle::default(),
547 scrollbar_width: Pixels::default(),
548 syntax: Default::default(),
549 // HACK: Status colors don't have a real default.
550 // We should look into removing the status colors from the editor
551 // style and retrieve them directly from the theme.
552 status: StatusColors::dark(),
553 inlay_hints_style: HighlightStyle::default(),
554 inline_completion_styles: InlineCompletionStyles {
555 insertion: HighlightStyle::default(),
556 whitespace: HighlightStyle::default(),
557 },
558 unnecessary_code_fade: Default::default(),
559 show_underlines: true,
560 }
561 }
562}
563
564pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
565 let show_background = language_settings::language_settings(None, None, cx)
566 .inlay_hints
567 .show_background;
568
569 HighlightStyle {
570 color: Some(cx.theme().status().hint),
571 background_color: show_background.then(|| cx.theme().status().hint_background),
572 ..HighlightStyle::default()
573 }
574}
575
576pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
577 InlineCompletionStyles {
578 insertion: HighlightStyle {
579 color: Some(cx.theme().status().predictive),
580 ..HighlightStyle::default()
581 },
582 whitespace: HighlightStyle {
583 background_color: Some(cx.theme().status().created_background),
584 ..HighlightStyle::default()
585 },
586 }
587}
588
589type CompletionId = usize;
590
591pub(crate) enum EditDisplayMode {
592 TabAccept,
593 DiffPopover,
594 Inline,
595}
596
597enum InlineCompletion {
598 Edit {
599 edits: Vec<(Range<Anchor>, String)>,
600 edit_preview: Option<EditPreview>,
601 display_mode: EditDisplayMode,
602 snapshot: BufferSnapshot,
603 },
604 Move {
605 target: Anchor,
606 snapshot: BufferSnapshot,
607 },
608}
609
610struct InlineCompletionState {
611 inlay_ids: Vec<InlayId>,
612 completion: InlineCompletion,
613 completion_id: Option<SharedString>,
614 invalidation_range: Range<Anchor>,
615}
616
617enum EditPredictionSettings {
618 Disabled,
619 Enabled {
620 show_in_menu: bool,
621 preview_requires_modifier: bool,
622 },
623}
624
625enum InlineCompletionHighlight {}
626
627#[derive(Debug, Clone)]
628struct InlineDiagnostic {
629 message: SharedString,
630 group_id: usize,
631 is_primary: bool,
632 start: Point,
633 severity: lsp::DiagnosticSeverity,
634}
635
636pub enum MenuInlineCompletionsPolicy {
637 Never,
638 ByProvider,
639}
640
641pub enum EditPredictionPreview {
642 /// Modifier is not pressed
643 Inactive { released_too_fast: bool },
644 /// Modifier pressed
645 Active {
646 since: Instant,
647 previous_scroll_position: Option<ScrollAnchor>,
648 },
649}
650
651impl EditPredictionPreview {
652 pub fn released_too_fast(&self) -> bool {
653 match self {
654 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
655 EditPredictionPreview::Active { .. } => false,
656 }
657 }
658
659 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
660 if let EditPredictionPreview::Active {
661 previous_scroll_position,
662 ..
663 } = self
664 {
665 *previous_scroll_position = scroll_position;
666 }
667 }
668}
669
670pub struct ContextMenuOptions {
671 pub min_entries_visible: usize,
672 pub max_entries_visible: usize,
673 pub placement: Option<ContextMenuPlacement>,
674}
675
676#[derive(Debug, Clone, PartialEq, Eq)]
677pub enum ContextMenuPlacement {
678 Above,
679 Below,
680}
681
682#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
683struct EditorActionId(usize);
684
685impl EditorActionId {
686 pub fn post_inc(&mut self) -> Self {
687 let answer = self.0;
688
689 *self = Self(answer + 1);
690
691 Self(answer)
692 }
693}
694
695// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
696// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
697
698type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
699type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
700
701#[derive(Default)]
702struct ScrollbarMarkerState {
703 scrollbar_size: Size<Pixels>,
704 dirty: bool,
705 markers: Arc<[PaintQuad]>,
706 pending_refresh: Option<Task<Result<()>>>,
707}
708
709impl ScrollbarMarkerState {
710 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
711 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
712 }
713}
714
715#[derive(Clone, Copy, PartialEq, Eq)]
716pub enum MinimapVisibility {
717 Disabled,
718 Enabled(bool),
719}
720
721impl MinimapVisibility {
722 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
723 if mode.is_full() {
724 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
725 } else {
726 Self::Disabled
727 }
728 }
729
730 fn disabled(&self) -> bool {
731 match *self {
732 Self::Disabled => true,
733 _ => false,
734 }
735 }
736
737 fn visible(&self) -> bool {
738 match *self {
739 Self::Enabled(visible) => visible,
740 _ => false,
741 }
742 }
743
744 fn toggle_visibility(&self) -> Self {
745 match *self {
746 Self::Enabled(visible) => Self::Enabled(!visible),
747 Self::Disabled => Self::Disabled,
748 }
749 }
750}
751
752#[derive(Clone, Debug)]
753struct RunnableTasks {
754 templates: Vec<(TaskSourceKind, TaskTemplate)>,
755 offset: multi_buffer::Anchor,
756 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
757 column: u32,
758 // Values of all named captures, including those starting with '_'
759 extra_variables: HashMap<String, String>,
760 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
761 context_range: Range<BufferOffset>,
762}
763
764impl RunnableTasks {
765 fn resolve<'a>(
766 &'a self,
767 cx: &'a task::TaskContext,
768 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
769 self.templates.iter().filter_map(|(kind, template)| {
770 template
771 .resolve_task(&kind.to_id_base(), cx)
772 .map(|task| (kind.clone(), task))
773 })
774 }
775}
776
777#[derive(Clone)]
778struct ResolvedTasks {
779 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
780 position: Anchor,
781}
782
783#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
784struct BufferOffset(usize);
785
786// Addons allow storing per-editor state in other crates (e.g. Vim)
787pub trait Addon: 'static {
788 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
789
790 fn render_buffer_header_controls(
791 &self,
792 _: &ExcerptInfo,
793 _: &Window,
794 _: &App,
795 ) -> Option<AnyElement> {
796 None
797 }
798
799 fn to_any(&self) -> &dyn std::any::Any;
800
801 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
802 None
803 }
804}
805
806/// A set of caret positions, registered when the editor was edited.
807pub struct ChangeList {
808 changes: Vec<Vec<Anchor>>,
809 /// Currently "selected" change.
810 position: Option<usize>,
811}
812
813impl ChangeList {
814 pub fn new() -> Self {
815 Self {
816 changes: Vec::new(),
817 position: None,
818 }
819 }
820
821 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
822 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
823 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
824 if self.changes.is_empty() {
825 return None;
826 }
827
828 let prev = self.position.unwrap_or(self.changes.len());
829 let next = if direction == Direction::Prev {
830 prev.saturating_sub(count)
831 } else {
832 (prev + count).min(self.changes.len() - 1)
833 };
834 self.position = Some(next);
835 self.changes.get(next).map(|anchors| anchors.as_slice())
836 }
837
838 /// Adds a new change to the list, resetting the change list position.
839 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
840 self.position.take();
841 if pop_state {
842 self.changes.pop();
843 }
844 self.changes.push(new_positions.clone());
845 }
846
847 pub fn last(&self) -> Option<&[Anchor]> {
848 self.changes.last().map(|anchors| anchors.as_slice())
849 }
850}
851
852#[derive(Clone)]
853struct InlineBlamePopoverState {
854 scroll_handle: ScrollHandle,
855 commit_message: Option<ParsedCommitMessage>,
856 markdown: Entity<Markdown>,
857}
858
859struct InlineBlamePopover {
860 position: gpui::Point<Pixels>,
861 show_task: Option<Task<()>>,
862 hide_task: Option<Task<()>>,
863 popover_bounds: Option<Bounds<Pixels>>,
864 popover_state: InlineBlamePopoverState,
865}
866
867/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
868/// a breakpoint on them.
869#[derive(Clone, Copy, Debug)]
870struct PhantomBreakpointIndicator {
871 display_row: DisplayRow,
872 /// There's a small debounce between hovering over the line and showing the indicator.
873 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
874 is_active: bool,
875 collides_with_existing_breakpoint: bool,
876}
877/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
878///
879/// See the [module level documentation](self) for more information.
880pub struct Editor {
881 focus_handle: FocusHandle,
882 last_focused_descendant: Option<WeakFocusHandle>,
883 /// The text buffer being edited
884 buffer: Entity<MultiBuffer>,
885 /// Map of how text in the buffer should be displayed.
886 /// Handles soft wraps, folds, fake inlay text insertions, etc.
887 pub display_map: Entity<DisplayMap>,
888 pub selections: SelectionsCollection,
889 pub scroll_manager: ScrollManager,
890 /// When inline assist editors are linked, they all render cursors because
891 /// typing enters text into each of them, even the ones that aren't focused.
892 pub(crate) show_cursor_when_unfocused: bool,
893 columnar_selection_tail: Option<Anchor>,
894 add_selections_state: Option<AddSelectionsState>,
895 select_next_state: Option<SelectNextState>,
896 select_prev_state: Option<SelectNextState>,
897 selection_history: SelectionHistory,
898 autoclose_regions: Vec<AutocloseRegion>,
899 snippet_stack: InvalidationStack<SnippetState>,
900 select_syntax_node_history: SelectSyntaxNodeHistory,
901 ime_transaction: Option<TransactionId>,
902 pub diagnostics_max_severity: DiagnosticSeverity,
903 active_diagnostics: ActiveDiagnostic,
904 show_inline_diagnostics: bool,
905 inline_diagnostics_update: Task<()>,
906 inline_diagnostics_enabled: bool,
907 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
908 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
909 hard_wrap: Option<usize>,
910
911 // TODO: make this a access method
912 pub project: Option<Entity<Project>>,
913 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
914 completion_provider: Option<Box<dyn CompletionProvider>>,
915 collaboration_hub: Option<Box<dyn CollaborationHub>>,
916 blink_manager: Entity<BlinkManager>,
917 show_cursor_names: bool,
918 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
919 pub show_local_selections: bool,
920 mode: EditorMode,
921 show_breadcrumbs: bool,
922 show_gutter: bool,
923 show_scrollbars: bool,
924 minimap_visibility: MinimapVisibility,
925 disable_expand_excerpt_buttons: bool,
926 show_line_numbers: Option<bool>,
927 use_relative_line_numbers: Option<bool>,
928 show_git_diff_gutter: Option<bool>,
929 show_code_actions: Option<bool>,
930 show_runnables: Option<bool>,
931 show_breakpoints: Option<bool>,
932 show_wrap_guides: Option<bool>,
933 show_indent_guides: Option<bool>,
934 placeholder_text: Option<Arc<str>>,
935 highlight_order: usize,
936 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
937 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
938 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
939 scrollbar_marker_state: ScrollbarMarkerState,
940 active_indent_guides_state: ActiveIndentGuidesState,
941 nav_history: Option<ItemNavHistory>,
942 context_menu: RefCell<Option<CodeContextMenu>>,
943 context_menu_options: Option<ContextMenuOptions>,
944 mouse_context_menu: Option<MouseContextMenu>,
945 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
946 inline_blame_popover: Option<InlineBlamePopover>,
947 signature_help_state: SignatureHelpState,
948 auto_signature_help: Option<bool>,
949 find_all_references_task_sources: Vec<Anchor>,
950 next_completion_id: CompletionId,
951 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
952 code_actions_task: Option<Task<Result<()>>>,
953 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
954 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
955 document_highlights_task: Option<Task<()>>,
956 linked_editing_range_task: Option<Task<Option<()>>>,
957 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
958 pending_rename: Option<RenameState>,
959 searchable: bool,
960 cursor_shape: CursorShape,
961 current_line_highlight: Option<CurrentLineHighlight>,
962 collapse_matches: bool,
963 autoindent_mode: Option<AutoindentMode>,
964 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
965 input_enabled: bool,
966 use_modal_editing: bool,
967 read_only: bool,
968 leader_id: Option<CollaboratorId>,
969 remote_id: Option<ViewId>,
970 pub hover_state: HoverState,
971 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
972 gutter_hovered: bool,
973 hovered_link_state: Option<HoveredLinkState>,
974 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
975 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
976 active_inline_completion: Option<InlineCompletionState>,
977 /// Used to prevent flickering as the user types while the menu is open
978 stale_inline_completion_in_menu: Option<InlineCompletionState>,
979 edit_prediction_settings: EditPredictionSettings,
980 inline_completions_hidden_for_vim_mode: bool,
981 show_inline_completions_override: Option<bool>,
982 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
983 edit_prediction_preview: EditPredictionPreview,
984 edit_prediction_indent_conflict: bool,
985 edit_prediction_requires_modifier_in_indent_conflict: bool,
986 inlay_hint_cache: InlayHintCache,
987 next_inlay_id: usize,
988 _subscriptions: Vec<Subscription>,
989 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
990 gutter_dimensions: GutterDimensions,
991 style: Option<EditorStyle>,
992 text_style_refinement: Option<TextStyleRefinement>,
993 next_editor_action_id: EditorActionId,
994 editor_actions:
995 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
996 use_autoclose: bool,
997 use_auto_surround: bool,
998 auto_replace_emoji_shortcode: bool,
999 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1000 show_git_blame_gutter: bool,
1001 show_git_blame_inline: bool,
1002 show_git_blame_inline_delay_task: Option<Task<()>>,
1003 git_blame_inline_enabled: bool,
1004 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1005 serialize_dirty_buffers: bool,
1006 show_selection_menu: Option<bool>,
1007 blame: Option<Entity<GitBlame>>,
1008 blame_subscription: Option<Subscription>,
1009 custom_context_menu: Option<
1010 Box<
1011 dyn 'static
1012 + Fn(
1013 &mut Self,
1014 DisplayPoint,
1015 &mut Window,
1016 &mut Context<Self>,
1017 ) -> Option<Entity<ui::ContextMenu>>,
1018 >,
1019 >,
1020 last_bounds: Option<Bounds<Pixels>>,
1021 last_position_map: Option<Rc<PositionMap>>,
1022 expect_bounds_change: Option<Bounds<Pixels>>,
1023 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1024 tasks_update_task: Option<Task<()>>,
1025 breakpoint_store: Option<Entity<BreakpointStore>>,
1026 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1027 in_project_search: bool,
1028 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1029 breadcrumb_header: Option<String>,
1030 focused_block: Option<FocusedBlock>,
1031 next_scroll_position: NextScrollCursorCenterTopBottom,
1032 addons: HashMap<TypeId, Box<dyn Addon>>,
1033 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1034 load_diff_task: Option<Shared<Task<()>>>,
1035 /// Whether we are temporarily displaying a diff other than git's
1036 temporary_diff_override: bool,
1037 selection_mark_mode: bool,
1038 toggle_fold_multiple_buffers: Task<()>,
1039 _scroll_cursor_center_top_bottom_task: Task<()>,
1040 serialize_selections: Task<()>,
1041 serialize_folds: Task<()>,
1042 mouse_cursor_hidden: bool,
1043 minimap: Option<Entity<Self>>,
1044 hide_mouse_mode: HideMouseMode,
1045 pub change_list: ChangeList,
1046 inline_value_cache: InlineValueCache,
1047}
1048
1049#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1050enum NextScrollCursorCenterTopBottom {
1051 #[default]
1052 Center,
1053 Top,
1054 Bottom,
1055}
1056
1057impl NextScrollCursorCenterTopBottom {
1058 fn next(&self) -> Self {
1059 match self {
1060 Self::Center => Self::Top,
1061 Self::Top => Self::Bottom,
1062 Self::Bottom => Self::Center,
1063 }
1064 }
1065}
1066
1067#[derive(Clone)]
1068pub struct EditorSnapshot {
1069 pub mode: EditorMode,
1070 show_gutter: bool,
1071 show_line_numbers: Option<bool>,
1072 show_git_diff_gutter: Option<bool>,
1073 show_runnables: Option<bool>,
1074 show_breakpoints: Option<bool>,
1075 git_blame_gutter_max_author_length: Option<usize>,
1076 pub display_snapshot: DisplaySnapshot,
1077 pub placeholder_text: Option<Arc<str>>,
1078 is_focused: bool,
1079 scroll_anchor: ScrollAnchor,
1080 ongoing_scroll: OngoingScroll,
1081 current_line_highlight: CurrentLineHighlight,
1082 gutter_hovered: bool,
1083}
1084
1085#[derive(Default, Debug, Clone, Copy)]
1086pub struct GutterDimensions {
1087 pub left_padding: Pixels,
1088 pub right_padding: Pixels,
1089 pub width: Pixels,
1090 pub margin: Pixels,
1091 pub git_blame_entries_width: Option<Pixels>,
1092}
1093
1094impl GutterDimensions {
1095 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1096 Self {
1097 margin: Self::default_gutter_margin(font_id, font_size, cx),
1098 ..Default::default()
1099 }
1100 }
1101
1102 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1103 -cx.text_system().descent(font_id, font_size)
1104 }
1105 /// The full width of the space taken up by the gutter.
1106 pub fn full_width(&self) -> Pixels {
1107 self.margin + self.width
1108 }
1109
1110 /// The width of the space reserved for the fold indicators,
1111 /// use alongside 'justify_end' and `gutter_width` to
1112 /// right align content with the line numbers
1113 pub fn fold_area_width(&self) -> Pixels {
1114 self.margin + self.right_padding
1115 }
1116}
1117
1118#[derive(Debug)]
1119pub struct RemoteSelection {
1120 pub replica_id: ReplicaId,
1121 pub selection: Selection<Anchor>,
1122 pub cursor_shape: CursorShape,
1123 pub collaborator_id: CollaboratorId,
1124 pub line_mode: bool,
1125 pub user_name: Option<SharedString>,
1126 pub color: PlayerColor,
1127}
1128
1129#[derive(Clone, Debug)]
1130struct SelectionHistoryEntry {
1131 selections: Arc<[Selection<Anchor>]>,
1132 select_next_state: Option<SelectNextState>,
1133 select_prev_state: Option<SelectNextState>,
1134 add_selections_state: Option<AddSelectionsState>,
1135}
1136
1137enum SelectionHistoryMode {
1138 Normal,
1139 Undoing,
1140 Redoing,
1141}
1142
1143#[derive(Clone, PartialEq, Eq, Hash)]
1144struct HoveredCursor {
1145 replica_id: u16,
1146 selection_id: usize,
1147}
1148
1149impl Default for SelectionHistoryMode {
1150 fn default() -> Self {
1151 Self::Normal
1152 }
1153}
1154
1155#[derive(Default)]
1156struct SelectionHistory {
1157 #[allow(clippy::type_complexity)]
1158 selections_by_transaction:
1159 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1160 mode: SelectionHistoryMode,
1161 undo_stack: VecDeque<SelectionHistoryEntry>,
1162 redo_stack: VecDeque<SelectionHistoryEntry>,
1163}
1164
1165impl SelectionHistory {
1166 fn insert_transaction(
1167 &mut self,
1168 transaction_id: TransactionId,
1169 selections: Arc<[Selection<Anchor>]>,
1170 ) {
1171 self.selections_by_transaction
1172 .insert(transaction_id, (selections, None));
1173 }
1174
1175 #[allow(clippy::type_complexity)]
1176 fn transaction(
1177 &self,
1178 transaction_id: TransactionId,
1179 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1180 self.selections_by_transaction.get(&transaction_id)
1181 }
1182
1183 #[allow(clippy::type_complexity)]
1184 fn transaction_mut(
1185 &mut self,
1186 transaction_id: TransactionId,
1187 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1188 self.selections_by_transaction.get_mut(&transaction_id)
1189 }
1190
1191 fn push(&mut self, entry: SelectionHistoryEntry) {
1192 if !entry.selections.is_empty() {
1193 match self.mode {
1194 SelectionHistoryMode::Normal => {
1195 self.push_undo(entry);
1196 self.redo_stack.clear();
1197 }
1198 SelectionHistoryMode::Undoing => self.push_redo(entry),
1199 SelectionHistoryMode::Redoing => self.push_undo(entry),
1200 }
1201 }
1202 }
1203
1204 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1205 if self
1206 .undo_stack
1207 .back()
1208 .map_or(true, |e| e.selections != entry.selections)
1209 {
1210 self.undo_stack.push_back(entry);
1211 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1212 self.undo_stack.pop_front();
1213 }
1214 }
1215 }
1216
1217 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1218 if self
1219 .redo_stack
1220 .back()
1221 .map_or(true, |e| e.selections != entry.selections)
1222 {
1223 self.redo_stack.push_back(entry);
1224 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1225 self.redo_stack.pop_front();
1226 }
1227 }
1228 }
1229}
1230
1231#[derive(Clone, Copy)]
1232pub struct RowHighlightOptions {
1233 pub autoscroll: bool,
1234 pub include_gutter: bool,
1235}
1236
1237impl Default for RowHighlightOptions {
1238 fn default() -> Self {
1239 Self {
1240 autoscroll: Default::default(),
1241 include_gutter: true,
1242 }
1243 }
1244}
1245
1246struct RowHighlight {
1247 index: usize,
1248 range: Range<Anchor>,
1249 color: Hsla,
1250 options: RowHighlightOptions,
1251 type_id: TypeId,
1252}
1253
1254#[derive(Clone, Debug)]
1255struct AddSelectionsState {
1256 above: bool,
1257 stack: Vec<usize>,
1258}
1259
1260#[derive(Clone)]
1261struct SelectNextState {
1262 query: AhoCorasick,
1263 wordwise: bool,
1264 done: bool,
1265}
1266
1267impl std::fmt::Debug for SelectNextState {
1268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1269 f.debug_struct(std::any::type_name::<Self>())
1270 .field("wordwise", &self.wordwise)
1271 .field("done", &self.done)
1272 .finish()
1273 }
1274}
1275
1276#[derive(Debug)]
1277struct AutocloseRegion {
1278 selection_id: usize,
1279 range: Range<Anchor>,
1280 pair: BracketPair,
1281}
1282
1283#[derive(Debug)]
1284struct SnippetState {
1285 ranges: Vec<Vec<Range<Anchor>>>,
1286 active_index: usize,
1287 choices: Vec<Option<Vec<String>>>,
1288}
1289
1290#[doc(hidden)]
1291pub struct RenameState {
1292 pub range: Range<Anchor>,
1293 pub old_name: Arc<str>,
1294 pub editor: Entity<Editor>,
1295 block_id: CustomBlockId,
1296}
1297
1298struct InvalidationStack<T>(Vec<T>);
1299
1300struct RegisteredInlineCompletionProvider {
1301 provider: Arc<dyn InlineCompletionProviderHandle>,
1302 _subscription: Subscription,
1303}
1304
1305#[derive(Debug, PartialEq, Eq)]
1306pub struct ActiveDiagnosticGroup {
1307 pub active_range: Range<Anchor>,
1308 pub active_message: String,
1309 pub group_id: usize,
1310 pub blocks: HashSet<CustomBlockId>,
1311}
1312
1313#[derive(Debug, PartialEq, Eq)]
1314
1315pub(crate) enum ActiveDiagnostic {
1316 None,
1317 All,
1318 Group(ActiveDiagnosticGroup),
1319}
1320
1321#[derive(Serialize, Deserialize, Clone, Debug)]
1322pub struct ClipboardSelection {
1323 /// The number of bytes in this selection.
1324 pub len: usize,
1325 /// Whether this was a full-line selection.
1326 pub is_entire_line: bool,
1327 /// The indentation of the first line when this content was originally copied.
1328 pub first_line_indent: u32,
1329}
1330
1331// selections, scroll behavior, was newest selection reversed
1332type SelectSyntaxNodeHistoryState = (
1333 Box<[Selection<usize>]>,
1334 SelectSyntaxNodeScrollBehavior,
1335 bool,
1336);
1337
1338#[derive(Default)]
1339struct SelectSyntaxNodeHistory {
1340 stack: Vec<SelectSyntaxNodeHistoryState>,
1341 // disable temporarily to allow changing selections without losing the stack
1342 pub disable_clearing: bool,
1343}
1344
1345impl SelectSyntaxNodeHistory {
1346 pub fn try_clear(&mut self) {
1347 if !self.disable_clearing {
1348 self.stack.clear();
1349 }
1350 }
1351
1352 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1353 self.stack.push(selection);
1354 }
1355
1356 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1357 self.stack.pop()
1358 }
1359}
1360
1361enum SelectSyntaxNodeScrollBehavior {
1362 CursorTop,
1363 FitSelection,
1364 CursorBottom,
1365}
1366
1367#[derive(Debug)]
1368pub(crate) struct NavigationData {
1369 cursor_anchor: Anchor,
1370 cursor_position: Point,
1371 scroll_anchor: ScrollAnchor,
1372 scroll_top_row: u32,
1373}
1374
1375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1376pub enum GotoDefinitionKind {
1377 Symbol,
1378 Declaration,
1379 Type,
1380 Implementation,
1381}
1382
1383#[derive(Debug, Clone)]
1384enum InlayHintRefreshReason {
1385 ModifiersChanged(bool),
1386 Toggle(bool),
1387 SettingsChange(InlayHintSettings),
1388 NewLinesShown,
1389 BufferEdited(HashSet<Arc<Language>>),
1390 RefreshRequested,
1391 ExcerptsRemoved(Vec<ExcerptId>),
1392}
1393
1394impl InlayHintRefreshReason {
1395 fn description(&self) -> &'static str {
1396 match self {
1397 Self::ModifiersChanged(_) => "modifiers changed",
1398 Self::Toggle(_) => "toggle",
1399 Self::SettingsChange(_) => "settings change",
1400 Self::NewLinesShown => "new lines shown",
1401 Self::BufferEdited(_) => "buffer edited",
1402 Self::RefreshRequested => "refresh requested",
1403 Self::ExcerptsRemoved(_) => "excerpts removed",
1404 }
1405 }
1406}
1407
1408pub enum FormatTarget {
1409 Buffers,
1410 Ranges(Vec<Range<MultiBufferPoint>>),
1411}
1412
1413pub(crate) struct FocusedBlock {
1414 id: BlockId,
1415 focus_handle: WeakFocusHandle,
1416}
1417
1418#[derive(Clone)]
1419enum JumpData {
1420 MultiBufferRow {
1421 row: MultiBufferRow,
1422 line_offset_from_top: u32,
1423 },
1424 MultiBufferPoint {
1425 excerpt_id: ExcerptId,
1426 position: Point,
1427 anchor: text::Anchor,
1428 line_offset_from_top: u32,
1429 },
1430}
1431
1432pub enum MultibufferSelectionMode {
1433 First,
1434 All,
1435}
1436
1437#[derive(Clone, Copy, Debug, Default)]
1438pub struct RewrapOptions {
1439 pub override_language_settings: bool,
1440 pub preserve_existing_whitespace: bool,
1441}
1442
1443impl Editor {
1444 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1445 let buffer = cx.new(|cx| Buffer::local("", cx));
1446 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1447 Self::new(
1448 EditorMode::SingleLine { auto_width: false },
1449 buffer,
1450 None,
1451 window,
1452 cx,
1453 )
1454 }
1455
1456 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1457 let buffer = cx.new(|cx| Buffer::local("", cx));
1458 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1459 Self::new(EditorMode::full(), buffer, None, window, cx)
1460 }
1461
1462 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1463 let buffer = cx.new(|cx| Buffer::local("", cx));
1464 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1465 Self::new(
1466 EditorMode::SingleLine { auto_width: true },
1467 buffer,
1468 None,
1469 window,
1470 cx,
1471 )
1472 }
1473
1474 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1475 let buffer = cx.new(|cx| Buffer::local("", cx));
1476 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1477 Self::new(
1478 EditorMode::AutoHeight { max_lines },
1479 buffer,
1480 None,
1481 window,
1482 cx,
1483 )
1484 }
1485
1486 pub fn for_buffer(
1487 buffer: Entity<Buffer>,
1488 project: Option<Entity<Project>>,
1489 window: &mut Window,
1490 cx: &mut Context<Self>,
1491 ) -> Self {
1492 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1493 Self::new(EditorMode::full(), buffer, project, window, cx)
1494 }
1495
1496 pub fn for_multibuffer(
1497 buffer: Entity<MultiBuffer>,
1498 project: Option<Entity<Project>>,
1499 window: &mut Window,
1500 cx: &mut Context<Self>,
1501 ) -> Self {
1502 Self::new(EditorMode::full(), buffer, project, window, cx)
1503 }
1504
1505 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1506 let mut clone = Self::new(
1507 self.mode.clone(),
1508 self.buffer.clone(),
1509 self.project.clone(),
1510 window,
1511 cx,
1512 );
1513 self.display_map.update(cx, |display_map, cx| {
1514 let snapshot = display_map.snapshot(cx);
1515 clone.display_map.update(cx, |display_map, cx| {
1516 display_map.set_state(&snapshot, cx);
1517 });
1518 });
1519 clone.folds_did_change(cx);
1520 clone.selections.clone_state(&self.selections);
1521 clone.scroll_manager.clone_state(&self.scroll_manager);
1522 clone.searchable = self.searchable;
1523 clone.read_only = self.read_only;
1524 clone
1525 }
1526
1527 pub fn new(
1528 mode: EditorMode,
1529 buffer: Entity<MultiBuffer>,
1530 project: Option<Entity<Project>>,
1531 window: &mut Window,
1532 cx: &mut Context<Self>,
1533 ) -> Self {
1534 Editor::new_internal(mode, buffer, project, None, window, cx)
1535 }
1536
1537 fn new_internal(
1538 mode: EditorMode,
1539 buffer: Entity<MultiBuffer>,
1540 project: Option<Entity<Project>>,
1541 display_map: Option<Entity<DisplayMap>>,
1542 window: &mut Window,
1543 cx: &mut Context<Self>,
1544 ) -> Self {
1545 debug_assert!(
1546 display_map.is_none() || mode.is_minimap(),
1547 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1548 );
1549
1550 let full_mode = mode.is_full();
1551 let diagnostics_max_severity = if full_mode {
1552 EditorSettings::get_global(cx)
1553 .diagnostics_max_severity
1554 .unwrap_or(DiagnosticSeverity::Hint)
1555 } else {
1556 DiagnosticSeverity::Off
1557 };
1558 let style = window.text_style();
1559 let font_size = style.font_size.to_pixels(window.rem_size());
1560 let editor = cx.entity().downgrade();
1561 let fold_placeholder = FoldPlaceholder {
1562 constrain_width: true,
1563 render: Arc::new(move |fold_id, fold_range, cx| {
1564 let editor = editor.clone();
1565 div()
1566 .id(fold_id)
1567 .bg(cx.theme().colors().ghost_element_background)
1568 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1569 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1570 .rounded_xs()
1571 .size_full()
1572 .cursor_pointer()
1573 .child("⋯")
1574 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1575 .on_click(move |_, _window, cx| {
1576 editor
1577 .update(cx, |editor, cx| {
1578 editor.unfold_ranges(
1579 &[fold_range.start..fold_range.end],
1580 true,
1581 false,
1582 cx,
1583 );
1584 cx.stop_propagation();
1585 })
1586 .ok();
1587 })
1588 .into_any()
1589 }),
1590 merge_adjacent: true,
1591 ..FoldPlaceholder::default()
1592 };
1593 let display_map = display_map.unwrap_or_else(|| {
1594 cx.new(|cx| {
1595 DisplayMap::new(
1596 buffer.clone(),
1597 style.font(),
1598 font_size,
1599 None,
1600 FILE_HEADER_HEIGHT,
1601 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1602 fold_placeholder,
1603 diagnostics_max_severity,
1604 cx,
1605 )
1606 })
1607 });
1608
1609 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1610
1611 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1612
1613 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1614 .then(|| language_settings::SoftWrap::None);
1615
1616 let mut project_subscriptions = Vec::new();
1617 if mode.is_full() {
1618 if let Some(project) = project.as_ref() {
1619 project_subscriptions.push(cx.subscribe_in(
1620 project,
1621 window,
1622 |editor, _, event, window, cx| match event {
1623 project::Event::RefreshCodeLens => {
1624 // we always query lens with actions, without storing them, always refreshing them
1625 }
1626 project::Event::RefreshInlayHints => {
1627 editor
1628 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1629 }
1630 project::Event::SnippetEdit(id, snippet_edits) => {
1631 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1632 let focus_handle = editor.focus_handle(cx);
1633 if focus_handle.is_focused(window) {
1634 let snapshot = buffer.read(cx).snapshot();
1635 for (range, snippet) in snippet_edits {
1636 let editor_range =
1637 language::range_from_lsp(*range).to_offset(&snapshot);
1638 editor
1639 .insert_snippet(
1640 &[editor_range],
1641 snippet.clone(),
1642 window,
1643 cx,
1644 )
1645 .ok();
1646 }
1647 }
1648 }
1649 }
1650 _ => {}
1651 },
1652 ));
1653 if let Some(task_inventory) = project
1654 .read(cx)
1655 .task_store()
1656 .read(cx)
1657 .task_inventory()
1658 .cloned()
1659 {
1660 project_subscriptions.push(cx.observe_in(
1661 &task_inventory,
1662 window,
1663 |editor, _, window, cx| {
1664 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1665 },
1666 ));
1667 };
1668
1669 project_subscriptions.push(cx.subscribe_in(
1670 &project.read(cx).breakpoint_store(),
1671 window,
1672 |editor, _, event, window, cx| match event {
1673 BreakpointStoreEvent::ClearDebugLines => {
1674 editor.clear_row_highlights::<ActiveDebugLine>();
1675 editor.refresh_inline_values(cx);
1676 }
1677 BreakpointStoreEvent::SetDebugLine => {
1678 if editor.go_to_active_debug_line(window, cx) {
1679 cx.stop_propagation();
1680 }
1681
1682 editor.refresh_inline_values(cx);
1683 }
1684 _ => {}
1685 },
1686 ));
1687 }
1688 }
1689
1690 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1691
1692 let inlay_hint_settings =
1693 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1694 let focus_handle = cx.focus_handle();
1695 cx.on_focus(&focus_handle, window, Self::handle_focus)
1696 .detach();
1697 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1698 .detach();
1699 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1700 .detach();
1701 cx.on_blur(&focus_handle, window, Self::handle_blur)
1702 .detach();
1703
1704 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1705 Some(false)
1706 } else {
1707 None
1708 };
1709
1710 let breakpoint_store = match (&mode, project.as_ref()) {
1711 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1712 _ => None,
1713 };
1714
1715 let mut code_action_providers = Vec::new();
1716 let mut load_uncommitted_diff = None;
1717 if let Some(project) = project.clone() {
1718 load_uncommitted_diff = Some(
1719 update_uncommitted_diff_for_buffer(
1720 cx.entity(),
1721 &project,
1722 buffer.read(cx).all_buffers(),
1723 buffer.clone(),
1724 cx,
1725 )
1726 .shared(),
1727 );
1728 code_action_providers.push(Rc::new(project) as Rc<_>);
1729 }
1730
1731 let mut this = Self {
1732 focus_handle,
1733 show_cursor_when_unfocused: false,
1734 last_focused_descendant: None,
1735 buffer: buffer.clone(),
1736 display_map: display_map.clone(),
1737 selections,
1738 scroll_manager: ScrollManager::new(cx),
1739 columnar_selection_tail: None,
1740 add_selections_state: None,
1741 select_next_state: None,
1742 select_prev_state: None,
1743 selection_history: SelectionHistory::default(),
1744 autoclose_regions: Vec::new(),
1745 snippet_stack: InvalidationStack::default(),
1746 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1747 ime_transaction: None,
1748 active_diagnostics: ActiveDiagnostic::None,
1749 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1750 inline_diagnostics_update: Task::ready(()),
1751 inline_diagnostics: Vec::new(),
1752 soft_wrap_mode_override,
1753 diagnostics_max_severity,
1754 hard_wrap: None,
1755 completion_provider: project.clone().map(|project| Box::new(project) as _),
1756 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1757 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1758 project,
1759 blink_manager: blink_manager.clone(),
1760 show_local_selections: true,
1761 show_scrollbars: full_mode,
1762 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1763 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1764 show_gutter: mode.is_full(),
1765 show_line_numbers: None,
1766 use_relative_line_numbers: None,
1767 disable_expand_excerpt_buttons: false,
1768 show_git_diff_gutter: None,
1769 show_code_actions: None,
1770 show_runnables: None,
1771 show_breakpoints: None,
1772 show_wrap_guides: None,
1773 show_indent_guides,
1774 placeholder_text: None,
1775 highlight_order: 0,
1776 highlighted_rows: HashMap::default(),
1777 background_highlights: TreeMap::default(),
1778 gutter_highlights: TreeMap::default(),
1779 scrollbar_marker_state: ScrollbarMarkerState::default(),
1780 active_indent_guides_state: ActiveIndentGuidesState::default(),
1781 nav_history: None,
1782 context_menu: RefCell::new(None),
1783 context_menu_options: None,
1784 mouse_context_menu: None,
1785 completion_tasks: Vec::new(),
1786 inline_blame_popover: None,
1787 signature_help_state: SignatureHelpState::default(),
1788 auto_signature_help: None,
1789 find_all_references_task_sources: Vec::new(),
1790 next_completion_id: 0,
1791 next_inlay_id: 0,
1792 code_action_providers,
1793 available_code_actions: None,
1794 code_actions_task: None,
1795 quick_selection_highlight_task: None,
1796 debounced_selection_highlight_task: None,
1797 document_highlights_task: None,
1798 linked_editing_range_task: None,
1799 pending_rename: None,
1800 searchable: true,
1801 cursor_shape: EditorSettings::get_global(cx)
1802 .cursor_shape
1803 .unwrap_or_default(),
1804 current_line_highlight: None,
1805 autoindent_mode: Some(AutoindentMode::EachLine),
1806 collapse_matches: false,
1807 workspace: None,
1808 input_enabled: true,
1809 use_modal_editing: mode.is_full(),
1810 read_only: mode.is_minimap(),
1811 use_autoclose: true,
1812 use_auto_surround: true,
1813 auto_replace_emoji_shortcode: false,
1814 jsx_tag_auto_close_enabled_in_any_buffer: false,
1815 leader_id: None,
1816 remote_id: None,
1817 hover_state: HoverState::default(),
1818 pending_mouse_down: None,
1819 hovered_link_state: None,
1820 edit_prediction_provider: None,
1821 active_inline_completion: None,
1822 stale_inline_completion_in_menu: None,
1823 edit_prediction_preview: EditPredictionPreview::Inactive {
1824 released_too_fast: false,
1825 },
1826 inline_diagnostics_enabled: mode.is_full(),
1827 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1828 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1829
1830 gutter_hovered: false,
1831 pixel_position_of_newest_cursor: None,
1832 last_bounds: None,
1833 last_position_map: None,
1834 expect_bounds_change: None,
1835 gutter_dimensions: GutterDimensions::default(),
1836 style: None,
1837 show_cursor_names: false,
1838 hovered_cursors: HashMap::default(),
1839 next_editor_action_id: EditorActionId::default(),
1840 editor_actions: Rc::default(),
1841 inline_completions_hidden_for_vim_mode: false,
1842 show_inline_completions_override: None,
1843 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1844 edit_prediction_settings: EditPredictionSettings::Disabled,
1845 edit_prediction_indent_conflict: false,
1846 edit_prediction_requires_modifier_in_indent_conflict: true,
1847 custom_context_menu: None,
1848 show_git_blame_gutter: false,
1849 show_git_blame_inline: false,
1850 show_selection_menu: None,
1851 show_git_blame_inline_delay_task: None,
1852 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1853 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1854 serialize_dirty_buffers: !mode.is_minimap()
1855 && ProjectSettings::get_global(cx)
1856 .session
1857 .restore_unsaved_buffers,
1858 blame: None,
1859 blame_subscription: None,
1860 tasks: BTreeMap::default(),
1861
1862 breakpoint_store,
1863 gutter_breakpoint_indicator: (None, None),
1864 _subscriptions: vec![
1865 cx.observe(&buffer, Self::on_buffer_changed),
1866 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1867 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1868 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1869 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1870 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1871 cx.observe_window_activation(window, |editor, window, cx| {
1872 let active = window.is_window_active();
1873 editor.blink_manager.update(cx, |blink_manager, cx| {
1874 if active {
1875 blink_manager.enable(cx);
1876 } else {
1877 blink_manager.disable(cx);
1878 }
1879 });
1880 }),
1881 ],
1882 tasks_update_task: None,
1883 linked_edit_ranges: Default::default(),
1884 in_project_search: false,
1885 previous_search_ranges: None,
1886 breadcrumb_header: None,
1887 focused_block: None,
1888 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1889 addons: HashMap::default(),
1890 registered_buffers: HashMap::default(),
1891 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1892 selection_mark_mode: false,
1893 toggle_fold_multiple_buffers: Task::ready(()),
1894 serialize_selections: Task::ready(()),
1895 serialize_folds: Task::ready(()),
1896 text_style_refinement: None,
1897 load_diff_task: load_uncommitted_diff,
1898 temporary_diff_override: false,
1899 mouse_cursor_hidden: false,
1900 minimap: None,
1901 hide_mouse_mode: EditorSettings::get_global(cx)
1902 .hide_mouse
1903 .unwrap_or_default(),
1904 change_list: ChangeList::new(),
1905 mode,
1906 };
1907 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1908 this._subscriptions
1909 .push(cx.observe(breakpoints, |_, _, cx| {
1910 cx.notify();
1911 }));
1912 }
1913 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1914 this._subscriptions.extend(project_subscriptions);
1915
1916 this._subscriptions.push(cx.subscribe_in(
1917 &cx.entity(),
1918 window,
1919 |editor, _, e: &EditorEvent, window, cx| match e {
1920 EditorEvent::ScrollPositionChanged { local, .. } => {
1921 if *local {
1922 let new_anchor = editor.scroll_manager.anchor();
1923 let snapshot = editor.snapshot(window, cx);
1924 editor.update_restoration_data(cx, move |data| {
1925 data.scroll_position = (
1926 new_anchor.top_row(&snapshot.buffer_snapshot),
1927 new_anchor.offset,
1928 );
1929 });
1930 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1931 editor.inline_blame_popover.take();
1932 }
1933 }
1934 EditorEvent::Edited { .. } => {
1935 if !vim_enabled(cx) {
1936 let (map, selections) = editor.selections.all_adjusted_display(cx);
1937 let pop_state = editor
1938 .change_list
1939 .last()
1940 .map(|previous| {
1941 previous.len() == selections.len()
1942 && previous.iter().enumerate().all(|(ix, p)| {
1943 p.to_display_point(&map).row()
1944 == selections[ix].head().row()
1945 })
1946 })
1947 .unwrap_or(false);
1948 let new_positions = selections
1949 .into_iter()
1950 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1951 .collect();
1952 editor
1953 .change_list
1954 .push_to_change_list(pop_state, new_positions);
1955 }
1956 }
1957 _ => (),
1958 },
1959 ));
1960
1961 if let Some(dap_store) = this
1962 .project
1963 .as_ref()
1964 .map(|project| project.read(cx).dap_store())
1965 {
1966 let weak_editor = cx.weak_entity();
1967
1968 this._subscriptions
1969 .push(
1970 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1971 let session_entity = cx.entity();
1972 weak_editor
1973 .update(cx, |editor, cx| {
1974 editor._subscriptions.push(
1975 cx.subscribe(&session_entity, Self::on_debug_session_event),
1976 );
1977 })
1978 .ok();
1979 }),
1980 );
1981
1982 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1983 this._subscriptions
1984 .push(cx.subscribe(&session, Self::on_debug_session_event));
1985 }
1986 }
1987
1988 this.end_selection(window, cx);
1989 this.scroll_manager.show_scrollbars(window, cx);
1990 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1991
1992 if full_mode {
1993 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1994 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1995
1996 if this.git_blame_inline_enabled {
1997 this.start_git_blame_inline(false, window, cx);
1998 }
1999
2000 this.go_to_active_debug_line(window, cx);
2001
2002 if let Some(buffer) = buffer.read(cx).as_singleton() {
2003 if let Some(project) = this.project.as_ref() {
2004 let handle = project.update(cx, |project, cx| {
2005 project.register_buffer_with_language_servers(&buffer, cx)
2006 });
2007 this.registered_buffers
2008 .insert(buffer.read(cx).remote_id(), handle);
2009 }
2010 }
2011
2012 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2013 }
2014
2015 this.report_editor_event("Editor Opened", None, cx);
2016 this
2017 }
2018
2019 pub fn deploy_mouse_context_menu(
2020 &mut self,
2021 position: gpui::Point<Pixels>,
2022 context_menu: Entity<ContextMenu>,
2023 window: &mut Window,
2024 cx: &mut Context<Self>,
2025 ) {
2026 self.mouse_context_menu = Some(MouseContextMenu::new(
2027 self,
2028 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2029 context_menu,
2030 window,
2031 cx,
2032 ));
2033 }
2034
2035 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2036 self.mouse_context_menu
2037 .as_ref()
2038 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2039 }
2040
2041 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2042 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2043 }
2044
2045 fn key_context_internal(
2046 &self,
2047 has_active_edit_prediction: bool,
2048 window: &Window,
2049 cx: &App,
2050 ) -> KeyContext {
2051 let mut key_context = KeyContext::new_with_defaults();
2052 key_context.add("Editor");
2053 let mode = match self.mode {
2054 EditorMode::SingleLine { .. } => "single_line",
2055 EditorMode::AutoHeight { .. } => "auto_height",
2056 EditorMode::Minimap { .. } => "minimap",
2057 EditorMode::Full { .. } => "full",
2058 };
2059
2060 if EditorSettings::jupyter_enabled(cx) {
2061 key_context.add("jupyter");
2062 }
2063
2064 key_context.set("mode", mode);
2065 if self.pending_rename.is_some() {
2066 key_context.add("renaming");
2067 }
2068
2069 match self.context_menu.borrow().as_ref() {
2070 Some(CodeContextMenu::Completions(_)) => {
2071 key_context.add("menu");
2072 key_context.add("showing_completions");
2073 }
2074 Some(CodeContextMenu::CodeActions(_)) => {
2075 key_context.add("menu");
2076 key_context.add("showing_code_actions")
2077 }
2078 None => {}
2079 }
2080
2081 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2082 if !self.focus_handle(cx).contains_focused(window, cx)
2083 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2084 {
2085 for addon in self.addons.values() {
2086 addon.extend_key_context(&mut key_context, cx)
2087 }
2088 }
2089
2090 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2091 if let Some(extension) = singleton_buffer
2092 .read(cx)
2093 .file()
2094 .and_then(|file| file.path().extension()?.to_str())
2095 {
2096 key_context.set("extension", extension.to_string());
2097 }
2098 } else {
2099 key_context.add("multibuffer");
2100 }
2101
2102 if has_active_edit_prediction {
2103 if self.edit_prediction_in_conflict() {
2104 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2105 } else {
2106 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2107 key_context.add("copilot_suggestion");
2108 }
2109 }
2110
2111 if self.selection_mark_mode {
2112 key_context.add("selection_mode");
2113 }
2114
2115 key_context
2116 }
2117
2118 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2119 self.mouse_cursor_hidden = match origin {
2120 HideMouseCursorOrigin::TypingAction => {
2121 matches!(
2122 self.hide_mouse_mode,
2123 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2124 )
2125 }
2126 HideMouseCursorOrigin::MovementAction => {
2127 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2128 }
2129 };
2130 }
2131
2132 pub fn edit_prediction_in_conflict(&self) -> bool {
2133 if !self.show_edit_predictions_in_menu() {
2134 return false;
2135 }
2136
2137 let showing_completions = self
2138 .context_menu
2139 .borrow()
2140 .as_ref()
2141 .map_or(false, |context| {
2142 matches!(context, CodeContextMenu::Completions(_))
2143 });
2144
2145 showing_completions
2146 || self.edit_prediction_requires_modifier()
2147 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2148 // bindings to insert tab characters.
2149 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2150 }
2151
2152 pub fn accept_edit_prediction_keybind(
2153 &self,
2154 window: &Window,
2155 cx: &App,
2156 ) -> AcceptEditPredictionBinding {
2157 let key_context = self.key_context_internal(true, window, cx);
2158 let in_conflict = self.edit_prediction_in_conflict();
2159
2160 AcceptEditPredictionBinding(
2161 window
2162 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2163 .into_iter()
2164 .filter(|binding| {
2165 !in_conflict
2166 || binding
2167 .keystrokes()
2168 .first()
2169 .map_or(false, |keystroke| keystroke.modifiers.modified())
2170 })
2171 .rev()
2172 .min_by_key(|binding| {
2173 binding
2174 .keystrokes()
2175 .first()
2176 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2177 }),
2178 )
2179 }
2180
2181 pub fn new_file(
2182 workspace: &mut Workspace,
2183 _: &workspace::NewFile,
2184 window: &mut Window,
2185 cx: &mut Context<Workspace>,
2186 ) {
2187 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2188 "Failed to create buffer",
2189 window,
2190 cx,
2191 |e, _, _| match e.error_code() {
2192 ErrorCode::RemoteUpgradeRequired => Some(format!(
2193 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2194 e.error_tag("required").unwrap_or("the latest version")
2195 )),
2196 _ => None,
2197 },
2198 );
2199 }
2200
2201 pub fn new_in_workspace(
2202 workspace: &mut Workspace,
2203 window: &mut Window,
2204 cx: &mut Context<Workspace>,
2205 ) -> Task<Result<Entity<Editor>>> {
2206 let project = workspace.project().clone();
2207 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2208
2209 cx.spawn_in(window, async move |workspace, cx| {
2210 let buffer = create.await?;
2211 workspace.update_in(cx, |workspace, window, cx| {
2212 let editor =
2213 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2214 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2215 editor
2216 })
2217 })
2218 }
2219
2220 fn new_file_vertical(
2221 workspace: &mut Workspace,
2222 _: &workspace::NewFileSplitVertical,
2223 window: &mut Window,
2224 cx: &mut Context<Workspace>,
2225 ) {
2226 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2227 }
2228
2229 fn new_file_horizontal(
2230 workspace: &mut Workspace,
2231 _: &workspace::NewFileSplitHorizontal,
2232 window: &mut Window,
2233 cx: &mut Context<Workspace>,
2234 ) {
2235 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2236 }
2237
2238 fn new_file_in_direction(
2239 workspace: &mut Workspace,
2240 direction: SplitDirection,
2241 window: &mut Window,
2242 cx: &mut Context<Workspace>,
2243 ) {
2244 let project = workspace.project().clone();
2245 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2246
2247 cx.spawn_in(window, async move |workspace, cx| {
2248 let buffer = create.await?;
2249 workspace.update_in(cx, move |workspace, window, cx| {
2250 workspace.split_item(
2251 direction,
2252 Box::new(
2253 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2254 ),
2255 window,
2256 cx,
2257 )
2258 })?;
2259 anyhow::Ok(())
2260 })
2261 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2262 match e.error_code() {
2263 ErrorCode::RemoteUpgradeRequired => Some(format!(
2264 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2265 e.error_tag("required").unwrap_or("the latest version")
2266 )),
2267 _ => None,
2268 }
2269 });
2270 }
2271
2272 pub fn leader_id(&self) -> Option<CollaboratorId> {
2273 self.leader_id
2274 }
2275
2276 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2277 &self.buffer
2278 }
2279
2280 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2281 self.workspace.as_ref()?.0.upgrade()
2282 }
2283
2284 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2285 self.buffer().read(cx).title(cx)
2286 }
2287
2288 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2289 let git_blame_gutter_max_author_length = self
2290 .render_git_blame_gutter(cx)
2291 .then(|| {
2292 if let Some(blame) = self.blame.as_ref() {
2293 let max_author_length =
2294 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2295 Some(max_author_length)
2296 } else {
2297 None
2298 }
2299 })
2300 .flatten();
2301
2302 EditorSnapshot {
2303 mode: self.mode.clone(),
2304 show_gutter: self.show_gutter,
2305 show_line_numbers: self.show_line_numbers,
2306 show_git_diff_gutter: self.show_git_diff_gutter,
2307 show_runnables: self.show_runnables,
2308 show_breakpoints: self.show_breakpoints,
2309 git_blame_gutter_max_author_length,
2310 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2311 scroll_anchor: self.scroll_manager.anchor(),
2312 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2313 placeholder_text: self.placeholder_text.clone(),
2314 is_focused: self.focus_handle.is_focused(window),
2315 current_line_highlight: self
2316 .current_line_highlight
2317 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2318 gutter_hovered: self.gutter_hovered,
2319 }
2320 }
2321
2322 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2323 self.buffer.read(cx).language_at(point, cx)
2324 }
2325
2326 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2327 self.buffer.read(cx).read(cx).file_at(point).cloned()
2328 }
2329
2330 pub fn active_excerpt(
2331 &self,
2332 cx: &App,
2333 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2334 self.buffer
2335 .read(cx)
2336 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2337 }
2338
2339 pub fn mode(&self) -> &EditorMode {
2340 &self.mode
2341 }
2342
2343 pub fn set_mode(&mut self, mode: EditorMode) {
2344 self.mode = mode;
2345 }
2346
2347 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2348 self.collaboration_hub.as_deref()
2349 }
2350
2351 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2352 self.collaboration_hub = Some(hub);
2353 }
2354
2355 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2356 self.in_project_search = in_project_search;
2357 }
2358
2359 pub fn set_custom_context_menu(
2360 &mut self,
2361 f: impl 'static
2362 + Fn(
2363 &mut Self,
2364 DisplayPoint,
2365 &mut Window,
2366 &mut Context<Self>,
2367 ) -> Option<Entity<ui::ContextMenu>>,
2368 ) {
2369 self.custom_context_menu = Some(Box::new(f))
2370 }
2371
2372 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2373 self.completion_provider = provider;
2374 }
2375
2376 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2377 self.semantics_provider.clone()
2378 }
2379
2380 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2381 self.semantics_provider = provider;
2382 }
2383
2384 pub fn set_edit_prediction_provider<T>(
2385 &mut self,
2386 provider: Option<Entity<T>>,
2387 window: &mut Window,
2388 cx: &mut Context<Self>,
2389 ) where
2390 T: EditPredictionProvider,
2391 {
2392 self.edit_prediction_provider =
2393 provider.map(|provider| RegisteredInlineCompletionProvider {
2394 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2395 if this.focus_handle.is_focused(window) {
2396 this.update_visible_inline_completion(window, cx);
2397 }
2398 }),
2399 provider: Arc::new(provider),
2400 });
2401 self.update_edit_prediction_settings(cx);
2402 self.refresh_inline_completion(false, false, window, cx);
2403 }
2404
2405 pub fn placeholder_text(&self) -> Option<&str> {
2406 self.placeholder_text.as_deref()
2407 }
2408
2409 pub fn set_placeholder_text(
2410 &mut self,
2411 placeholder_text: impl Into<Arc<str>>,
2412 cx: &mut Context<Self>,
2413 ) {
2414 let placeholder_text = Some(placeholder_text.into());
2415 if self.placeholder_text != placeholder_text {
2416 self.placeholder_text = placeholder_text;
2417 cx.notify();
2418 }
2419 }
2420
2421 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2422 self.cursor_shape = cursor_shape;
2423
2424 // Disrupt blink for immediate user feedback that the cursor shape has changed
2425 self.blink_manager.update(cx, BlinkManager::show_cursor);
2426
2427 cx.notify();
2428 }
2429
2430 pub fn set_current_line_highlight(
2431 &mut self,
2432 current_line_highlight: Option<CurrentLineHighlight>,
2433 ) {
2434 self.current_line_highlight = current_line_highlight;
2435 }
2436
2437 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2438 self.collapse_matches = collapse_matches;
2439 }
2440
2441 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2442 let buffers = self.buffer.read(cx).all_buffers();
2443 let Some(project) = self.project.as_ref() else {
2444 return;
2445 };
2446 project.update(cx, |project, cx| {
2447 for buffer in buffers {
2448 self.registered_buffers
2449 .entry(buffer.read(cx).remote_id())
2450 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2451 }
2452 })
2453 }
2454
2455 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2456 if self.collapse_matches {
2457 return range.start..range.start;
2458 }
2459 range.clone()
2460 }
2461
2462 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2463 if self.display_map.read(cx).clip_at_line_ends != clip {
2464 self.display_map
2465 .update(cx, |map, _| map.clip_at_line_ends = clip);
2466 }
2467 }
2468
2469 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2470 self.input_enabled = input_enabled;
2471 }
2472
2473 pub fn set_inline_completions_hidden_for_vim_mode(
2474 &mut self,
2475 hidden: bool,
2476 window: &mut Window,
2477 cx: &mut Context<Self>,
2478 ) {
2479 if hidden != self.inline_completions_hidden_for_vim_mode {
2480 self.inline_completions_hidden_for_vim_mode = hidden;
2481 if hidden {
2482 self.update_visible_inline_completion(window, cx);
2483 } else {
2484 self.refresh_inline_completion(true, false, window, cx);
2485 }
2486 }
2487 }
2488
2489 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2490 self.menu_inline_completions_policy = value;
2491 }
2492
2493 pub fn set_autoindent(&mut self, autoindent: bool) {
2494 if autoindent {
2495 self.autoindent_mode = Some(AutoindentMode::EachLine);
2496 } else {
2497 self.autoindent_mode = None;
2498 }
2499 }
2500
2501 pub fn read_only(&self, cx: &App) -> bool {
2502 self.read_only || self.buffer.read(cx).read_only()
2503 }
2504
2505 pub fn set_read_only(&mut self, read_only: bool) {
2506 self.read_only = read_only;
2507 }
2508
2509 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2510 self.use_autoclose = autoclose;
2511 }
2512
2513 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2514 self.use_auto_surround = auto_surround;
2515 }
2516
2517 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2518 self.auto_replace_emoji_shortcode = auto_replace;
2519 }
2520
2521 pub fn toggle_edit_predictions(
2522 &mut self,
2523 _: &ToggleEditPrediction,
2524 window: &mut Window,
2525 cx: &mut Context<Self>,
2526 ) {
2527 if self.show_inline_completions_override.is_some() {
2528 self.set_show_edit_predictions(None, window, cx);
2529 } else {
2530 let show_edit_predictions = !self.edit_predictions_enabled();
2531 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2532 }
2533 }
2534
2535 pub fn set_show_edit_predictions(
2536 &mut self,
2537 show_edit_predictions: Option<bool>,
2538 window: &mut Window,
2539 cx: &mut Context<Self>,
2540 ) {
2541 self.show_inline_completions_override = show_edit_predictions;
2542 self.update_edit_prediction_settings(cx);
2543
2544 if let Some(false) = show_edit_predictions {
2545 self.discard_inline_completion(false, cx);
2546 } else {
2547 self.refresh_inline_completion(false, true, window, cx);
2548 }
2549 }
2550
2551 fn inline_completions_disabled_in_scope(
2552 &self,
2553 buffer: &Entity<Buffer>,
2554 buffer_position: language::Anchor,
2555 cx: &App,
2556 ) -> bool {
2557 let snapshot = buffer.read(cx).snapshot();
2558 let settings = snapshot.settings_at(buffer_position, cx);
2559
2560 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2561 return false;
2562 };
2563
2564 scope.override_name().map_or(false, |scope_name| {
2565 settings
2566 .edit_predictions_disabled_in
2567 .iter()
2568 .any(|s| s == scope_name)
2569 })
2570 }
2571
2572 pub fn set_use_modal_editing(&mut self, to: bool) {
2573 self.use_modal_editing = to;
2574 }
2575
2576 pub fn use_modal_editing(&self) -> bool {
2577 self.use_modal_editing
2578 }
2579
2580 fn selections_did_change(
2581 &mut self,
2582 local: bool,
2583 old_cursor_position: &Anchor,
2584 show_completions: bool,
2585 window: &mut Window,
2586 cx: &mut Context<Self>,
2587 ) {
2588 window.invalidate_character_coordinates();
2589
2590 // Copy selections to primary selection buffer
2591 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2592 if local {
2593 let selections = self.selections.all::<usize>(cx);
2594 let buffer_handle = self.buffer.read(cx).read(cx);
2595
2596 let mut text = String::new();
2597 for (index, selection) in selections.iter().enumerate() {
2598 let text_for_selection = buffer_handle
2599 .text_for_range(selection.start..selection.end)
2600 .collect::<String>();
2601
2602 text.push_str(&text_for_selection);
2603 if index != selections.len() - 1 {
2604 text.push('\n');
2605 }
2606 }
2607
2608 if !text.is_empty() {
2609 cx.write_to_primary(ClipboardItem::new_string(text));
2610 }
2611 }
2612
2613 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2614 self.buffer.update(cx, |buffer, cx| {
2615 buffer.set_active_selections(
2616 &self.selections.disjoint_anchors(),
2617 self.selections.line_mode,
2618 self.cursor_shape,
2619 cx,
2620 )
2621 });
2622 }
2623 let display_map = self
2624 .display_map
2625 .update(cx, |display_map, cx| display_map.snapshot(cx));
2626 let buffer = &display_map.buffer_snapshot;
2627 self.add_selections_state = None;
2628 self.select_next_state = None;
2629 self.select_prev_state = None;
2630 self.select_syntax_node_history.try_clear();
2631 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2632 self.snippet_stack
2633 .invalidate(&self.selections.disjoint_anchors(), buffer);
2634 self.take_rename(false, window, cx);
2635
2636 let new_cursor_position = self.selections.newest_anchor().head();
2637
2638 self.push_to_nav_history(
2639 *old_cursor_position,
2640 Some(new_cursor_position.to_point(buffer)),
2641 false,
2642 cx,
2643 );
2644
2645 if local {
2646 let new_cursor_position = self.selections.newest_anchor().head();
2647 let mut context_menu = self.context_menu.borrow_mut();
2648 let completion_menu = match context_menu.as_ref() {
2649 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2650 _ => {
2651 *context_menu = None;
2652 None
2653 }
2654 };
2655 if let Some(buffer_id) = new_cursor_position.buffer_id {
2656 if !self.registered_buffers.contains_key(&buffer_id) {
2657 if let Some(project) = self.project.as_ref() {
2658 project.update(cx, |project, cx| {
2659 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2660 return;
2661 };
2662 self.registered_buffers.insert(
2663 buffer_id,
2664 project.register_buffer_with_language_servers(&buffer, cx),
2665 );
2666 })
2667 }
2668 }
2669 }
2670
2671 if let Some(completion_menu) = completion_menu {
2672 let cursor_position = new_cursor_position.to_offset(buffer);
2673 let (word_range, kind) =
2674 buffer.surrounding_word(completion_menu.initial_position, true);
2675 if kind == Some(CharKind::Word)
2676 && word_range.to_inclusive().contains(&cursor_position)
2677 {
2678 let mut completion_menu = completion_menu.clone();
2679 drop(context_menu);
2680
2681 let query = Self::completion_query(buffer, cursor_position);
2682 cx.spawn(async move |this, cx| {
2683 completion_menu
2684 .filter(query.as_deref(), cx.background_executor().clone())
2685 .await;
2686
2687 this.update(cx, |this, cx| {
2688 let mut context_menu = this.context_menu.borrow_mut();
2689 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2690 else {
2691 return;
2692 };
2693
2694 if menu.id > completion_menu.id {
2695 return;
2696 }
2697
2698 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2699 drop(context_menu);
2700 cx.notify();
2701 })
2702 })
2703 .detach();
2704
2705 if show_completions {
2706 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2707 }
2708 } else {
2709 drop(context_menu);
2710 self.hide_context_menu(window, cx);
2711 }
2712 } else {
2713 drop(context_menu);
2714 }
2715
2716 hide_hover(self, cx);
2717
2718 if old_cursor_position.to_display_point(&display_map).row()
2719 != new_cursor_position.to_display_point(&display_map).row()
2720 {
2721 self.available_code_actions.take();
2722 }
2723 self.refresh_code_actions(window, cx);
2724 self.refresh_document_highlights(cx);
2725 self.refresh_selected_text_highlights(false, window, cx);
2726 refresh_matching_bracket_highlights(self, window, cx);
2727 self.update_visible_inline_completion(window, cx);
2728 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2729 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2730 self.inline_blame_popover.take();
2731 if self.git_blame_inline_enabled {
2732 self.start_inline_blame_timer(window, cx);
2733 }
2734 }
2735
2736 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2737 cx.emit(EditorEvent::SelectionsChanged { local });
2738
2739 let selections = &self.selections.disjoint;
2740 if selections.len() == 1 {
2741 cx.emit(SearchEvent::ActiveMatchChanged)
2742 }
2743 if local {
2744 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2745 let inmemory_selections = selections
2746 .iter()
2747 .map(|s| {
2748 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2749 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2750 })
2751 .collect();
2752 self.update_restoration_data(cx, |data| {
2753 data.selections = inmemory_selections;
2754 });
2755
2756 if WorkspaceSettings::get(None, cx).restore_on_startup
2757 != RestoreOnStartupBehavior::None
2758 {
2759 if let Some(workspace_id) =
2760 self.workspace.as_ref().and_then(|workspace| workspace.1)
2761 {
2762 let snapshot = self.buffer().read(cx).snapshot(cx);
2763 let selections = selections.clone();
2764 let background_executor = cx.background_executor().clone();
2765 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2766 self.serialize_selections = cx.background_spawn(async move {
2767 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2768 let db_selections = selections
2769 .iter()
2770 .map(|selection| {
2771 (
2772 selection.start.to_offset(&snapshot),
2773 selection.end.to_offset(&snapshot),
2774 )
2775 })
2776 .collect();
2777
2778 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2779 .await
2780 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2781 .log_err();
2782 });
2783 }
2784 }
2785 }
2786 }
2787
2788 cx.notify();
2789 }
2790
2791 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2792 use text::ToOffset as _;
2793 use text::ToPoint as _;
2794
2795 if self.mode.is_minimap()
2796 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2797 {
2798 return;
2799 }
2800
2801 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2802 return;
2803 };
2804
2805 let snapshot = singleton.read(cx).snapshot();
2806 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2807 let display_snapshot = display_map.snapshot(cx);
2808
2809 display_snapshot
2810 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2811 .map(|fold| {
2812 fold.range.start.text_anchor.to_point(&snapshot)
2813 ..fold.range.end.text_anchor.to_point(&snapshot)
2814 })
2815 .collect()
2816 });
2817 self.update_restoration_data(cx, |data| {
2818 data.folds = inmemory_folds;
2819 });
2820
2821 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2822 return;
2823 };
2824 let background_executor = cx.background_executor().clone();
2825 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2826 let db_folds = self.display_map.update(cx, |display_map, cx| {
2827 display_map
2828 .snapshot(cx)
2829 .folds_in_range(0..snapshot.len())
2830 .map(|fold| {
2831 (
2832 fold.range.start.text_anchor.to_offset(&snapshot),
2833 fold.range.end.text_anchor.to_offset(&snapshot),
2834 )
2835 })
2836 .collect()
2837 });
2838 self.serialize_folds = cx.background_spawn(async move {
2839 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2840 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2841 .await
2842 .with_context(|| {
2843 format!(
2844 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2845 )
2846 })
2847 .log_err();
2848 });
2849 }
2850
2851 pub fn sync_selections(
2852 &mut self,
2853 other: Entity<Editor>,
2854 cx: &mut Context<Self>,
2855 ) -> gpui::Subscription {
2856 let other_selections = other.read(cx).selections.disjoint.to_vec();
2857 self.selections.change_with(cx, |selections| {
2858 selections.select_anchors(other_selections);
2859 });
2860
2861 let other_subscription =
2862 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2863 EditorEvent::SelectionsChanged { local: true } => {
2864 let other_selections = other.read(cx).selections.disjoint.to_vec();
2865 if other_selections.is_empty() {
2866 return;
2867 }
2868 this.selections.change_with(cx, |selections| {
2869 selections.select_anchors(other_selections);
2870 });
2871 }
2872 _ => {}
2873 });
2874
2875 let this_subscription =
2876 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2877 EditorEvent::SelectionsChanged { local: true } => {
2878 let these_selections = this.selections.disjoint.to_vec();
2879 if these_selections.is_empty() {
2880 return;
2881 }
2882 other.update(cx, |other_editor, cx| {
2883 other_editor.selections.change_with(cx, |selections| {
2884 selections.select_anchors(these_selections);
2885 })
2886 });
2887 }
2888 _ => {}
2889 });
2890
2891 Subscription::join(other_subscription, this_subscription)
2892 }
2893
2894 pub fn change_selections<R>(
2895 &mut self,
2896 autoscroll: Option<Autoscroll>,
2897 window: &mut Window,
2898 cx: &mut Context<Self>,
2899 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2900 ) -> R {
2901 self.change_selections_inner(autoscroll, true, window, cx, change)
2902 }
2903
2904 fn change_selections_inner<R>(
2905 &mut self,
2906 autoscroll: Option<Autoscroll>,
2907 request_completions: bool,
2908 window: &mut Window,
2909 cx: &mut Context<Self>,
2910 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2911 ) -> R {
2912 let old_cursor_position = self.selections.newest_anchor().head();
2913 self.push_to_selection_history();
2914
2915 let (changed, result) = self.selections.change_with(cx, change);
2916
2917 if changed {
2918 if let Some(autoscroll) = autoscroll {
2919 self.request_autoscroll(autoscroll, cx);
2920 }
2921 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2922
2923 if self.should_open_signature_help_automatically(
2924 &old_cursor_position,
2925 self.signature_help_state.backspace_pressed(),
2926 cx,
2927 ) {
2928 self.show_signature_help(&ShowSignatureHelp, window, cx);
2929 }
2930 self.signature_help_state.set_backspace_pressed(false);
2931 }
2932
2933 result
2934 }
2935
2936 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2937 where
2938 I: IntoIterator<Item = (Range<S>, T)>,
2939 S: ToOffset,
2940 T: Into<Arc<str>>,
2941 {
2942 if self.read_only(cx) {
2943 return;
2944 }
2945
2946 self.buffer
2947 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2948 }
2949
2950 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2951 where
2952 I: IntoIterator<Item = (Range<S>, T)>,
2953 S: ToOffset,
2954 T: Into<Arc<str>>,
2955 {
2956 if self.read_only(cx) {
2957 return;
2958 }
2959
2960 self.buffer.update(cx, |buffer, cx| {
2961 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2962 });
2963 }
2964
2965 pub fn edit_with_block_indent<I, S, T>(
2966 &mut self,
2967 edits: I,
2968 original_indent_columns: Vec<Option<u32>>,
2969 cx: &mut Context<Self>,
2970 ) where
2971 I: IntoIterator<Item = (Range<S>, T)>,
2972 S: ToOffset,
2973 T: Into<Arc<str>>,
2974 {
2975 if self.read_only(cx) {
2976 return;
2977 }
2978
2979 self.buffer.update(cx, |buffer, cx| {
2980 buffer.edit(
2981 edits,
2982 Some(AutoindentMode::Block {
2983 original_indent_columns,
2984 }),
2985 cx,
2986 )
2987 });
2988 }
2989
2990 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2991 self.hide_context_menu(window, cx);
2992
2993 match phase {
2994 SelectPhase::Begin {
2995 position,
2996 add,
2997 click_count,
2998 } => self.begin_selection(position, add, click_count, window, cx),
2999 SelectPhase::BeginColumnar {
3000 position,
3001 goal_column,
3002 reset,
3003 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3004 SelectPhase::Extend {
3005 position,
3006 click_count,
3007 } => self.extend_selection(position, click_count, window, cx),
3008 SelectPhase::Update {
3009 position,
3010 goal_column,
3011 scroll_delta,
3012 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3013 SelectPhase::End => self.end_selection(window, cx),
3014 }
3015 }
3016
3017 fn extend_selection(
3018 &mut self,
3019 position: DisplayPoint,
3020 click_count: usize,
3021 window: &mut Window,
3022 cx: &mut Context<Self>,
3023 ) {
3024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3025 let tail = self.selections.newest::<usize>(cx).tail();
3026 self.begin_selection(position, false, click_count, window, cx);
3027
3028 let position = position.to_offset(&display_map, Bias::Left);
3029 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3030
3031 let mut pending_selection = self
3032 .selections
3033 .pending_anchor()
3034 .expect("extend_selection not called with pending selection");
3035 if position >= tail {
3036 pending_selection.start = tail_anchor;
3037 } else {
3038 pending_selection.end = tail_anchor;
3039 pending_selection.reversed = true;
3040 }
3041
3042 let mut pending_mode = self.selections.pending_mode().unwrap();
3043 match &mut pending_mode {
3044 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3045 _ => {}
3046 }
3047
3048 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3049
3050 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3051 s.set_pending(pending_selection, pending_mode)
3052 });
3053 }
3054
3055 fn begin_selection(
3056 &mut self,
3057 position: DisplayPoint,
3058 add: bool,
3059 click_count: usize,
3060 window: &mut Window,
3061 cx: &mut Context<Self>,
3062 ) {
3063 if !self.focus_handle.is_focused(window) {
3064 self.last_focused_descendant = None;
3065 window.focus(&self.focus_handle);
3066 }
3067
3068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3069 let buffer = &display_map.buffer_snapshot;
3070 let position = display_map.clip_point(position, Bias::Left);
3071
3072 let start;
3073 let end;
3074 let mode;
3075 let mut auto_scroll;
3076 match click_count {
3077 1 => {
3078 start = buffer.anchor_before(position.to_point(&display_map));
3079 end = start;
3080 mode = SelectMode::Character;
3081 auto_scroll = true;
3082 }
3083 2 => {
3084 let range = movement::surrounding_word(&display_map, position);
3085 start = buffer.anchor_before(range.start.to_point(&display_map));
3086 end = buffer.anchor_before(range.end.to_point(&display_map));
3087 mode = SelectMode::Word(start..end);
3088 auto_scroll = true;
3089 }
3090 3 => {
3091 let position = display_map
3092 .clip_point(position, Bias::Left)
3093 .to_point(&display_map);
3094 let line_start = display_map.prev_line_boundary(position).0;
3095 let next_line_start = buffer.clip_point(
3096 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3097 Bias::Left,
3098 );
3099 start = buffer.anchor_before(line_start);
3100 end = buffer.anchor_before(next_line_start);
3101 mode = SelectMode::Line(start..end);
3102 auto_scroll = true;
3103 }
3104 _ => {
3105 start = buffer.anchor_before(0);
3106 end = buffer.anchor_before(buffer.len());
3107 mode = SelectMode::All;
3108 auto_scroll = false;
3109 }
3110 }
3111 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3112
3113 let point_to_delete: Option<usize> = {
3114 let selected_points: Vec<Selection<Point>> =
3115 self.selections.disjoint_in_range(start..end, cx);
3116
3117 if !add || click_count > 1 {
3118 None
3119 } else if !selected_points.is_empty() {
3120 Some(selected_points[0].id)
3121 } else {
3122 let clicked_point_already_selected =
3123 self.selections.disjoint.iter().find(|selection| {
3124 selection.start.to_point(buffer) == start.to_point(buffer)
3125 || selection.end.to_point(buffer) == end.to_point(buffer)
3126 });
3127
3128 clicked_point_already_selected.map(|selection| selection.id)
3129 }
3130 };
3131
3132 let selections_count = self.selections.count();
3133
3134 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3135 if let Some(point_to_delete) = point_to_delete {
3136 s.delete(point_to_delete);
3137
3138 if selections_count == 1 {
3139 s.set_pending_anchor_range(start..end, mode);
3140 }
3141 } else {
3142 if !add {
3143 s.clear_disjoint();
3144 }
3145
3146 s.set_pending_anchor_range(start..end, mode);
3147 }
3148 });
3149 }
3150
3151 fn begin_columnar_selection(
3152 &mut self,
3153 position: DisplayPoint,
3154 goal_column: u32,
3155 reset: bool,
3156 window: &mut Window,
3157 cx: &mut Context<Self>,
3158 ) {
3159 if !self.focus_handle.is_focused(window) {
3160 self.last_focused_descendant = None;
3161 window.focus(&self.focus_handle);
3162 }
3163
3164 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3165
3166 if reset {
3167 let pointer_position = display_map
3168 .buffer_snapshot
3169 .anchor_before(position.to_point(&display_map));
3170
3171 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3172 s.clear_disjoint();
3173 s.set_pending_anchor_range(
3174 pointer_position..pointer_position,
3175 SelectMode::Character,
3176 );
3177 });
3178 }
3179
3180 let tail = self.selections.newest::<Point>(cx).tail();
3181 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3182
3183 if !reset {
3184 self.select_columns(
3185 tail.to_display_point(&display_map),
3186 position,
3187 goal_column,
3188 &display_map,
3189 window,
3190 cx,
3191 );
3192 }
3193 }
3194
3195 fn update_selection(
3196 &mut self,
3197 position: DisplayPoint,
3198 goal_column: u32,
3199 scroll_delta: gpui::Point<f32>,
3200 window: &mut Window,
3201 cx: &mut Context<Self>,
3202 ) {
3203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3204
3205 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3206 let tail = tail.to_display_point(&display_map);
3207 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3208 } else if let Some(mut pending) = self.selections.pending_anchor() {
3209 let buffer = self.buffer.read(cx).snapshot(cx);
3210 let head;
3211 let tail;
3212 let mode = self.selections.pending_mode().unwrap();
3213 match &mode {
3214 SelectMode::Character => {
3215 head = position.to_point(&display_map);
3216 tail = pending.tail().to_point(&buffer);
3217 }
3218 SelectMode::Word(original_range) => {
3219 let original_display_range = original_range.start.to_display_point(&display_map)
3220 ..original_range.end.to_display_point(&display_map);
3221 let original_buffer_range = original_display_range.start.to_point(&display_map)
3222 ..original_display_range.end.to_point(&display_map);
3223 if movement::is_inside_word(&display_map, position)
3224 || original_display_range.contains(&position)
3225 {
3226 let word_range = movement::surrounding_word(&display_map, position);
3227 if word_range.start < original_display_range.start {
3228 head = word_range.start.to_point(&display_map);
3229 } else {
3230 head = word_range.end.to_point(&display_map);
3231 }
3232 } else {
3233 head = position.to_point(&display_map);
3234 }
3235
3236 if head <= original_buffer_range.start {
3237 tail = original_buffer_range.end;
3238 } else {
3239 tail = original_buffer_range.start;
3240 }
3241 }
3242 SelectMode::Line(original_range) => {
3243 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3244
3245 let position = display_map
3246 .clip_point(position, Bias::Left)
3247 .to_point(&display_map);
3248 let line_start = display_map.prev_line_boundary(position).0;
3249 let next_line_start = buffer.clip_point(
3250 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3251 Bias::Left,
3252 );
3253
3254 if line_start < original_range.start {
3255 head = line_start
3256 } else {
3257 head = next_line_start
3258 }
3259
3260 if head <= original_range.start {
3261 tail = original_range.end;
3262 } else {
3263 tail = original_range.start;
3264 }
3265 }
3266 SelectMode::All => {
3267 return;
3268 }
3269 };
3270
3271 if head < tail {
3272 pending.start = buffer.anchor_before(head);
3273 pending.end = buffer.anchor_before(tail);
3274 pending.reversed = true;
3275 } else {
3276 pending.start = buffer.anchor_before(tail);
3277 pending.end = buffer.anchor_before(head);
3278 pending.reversed = false;
3279 }
3280
3281 self.change_selections(None, window, cx, |s| {
3282 s.set_pending(pending, mode);
3283 });
3284 } else {
3285 log::error!("update_selection dispatched with no pending selection");
3286 return;
3287 }
3288
3289 self.apply_scroll_delta(scroll_delta, window, cx);
3290 cx.notify();
3291 }
3292
3293 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3294 self.columnar_selection_tail.take();
3295 if self.selections.pending_anchor().is_some() {
3296 let selections = self.selections.all::<usize>(cx);
3297 self.change_selections(None, window, cx, |s| {
3298 s.select(selections);
3299 s.clear_pending();
3300 });
3301 }
3302 }
3303
3304 fn select_columns(
3305 &mut self,
3306 tail: DisplayPoint,
3307 head: DisplayPoint,
3308 goal_column: u32,
3309 display_map: &DisplaySnapshot,
3310 window: &mut Window,
3311 cx: &mut Context<Self>,
3312 ) {
3313 let start_row = cmp::min(tail.row(), head.row());
3314 let end_row = cmp::max(tail.row(), head.row());
3315 let start_column = cmp::min(tail.column(), goal_column);
3316 let end_column = cmp::max(tail.column(), goal_column);
3317 let reversed = start_column < tail.column();
3318
3319 let selection_ranges = (start_row.0..=end_row.0)
3320 .map(DisplayRow)
3321 .filter_map(|row| {
3322 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3323 let start = display_map
3324 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3325 .to_point(display_map);
3326 let end = display_map
3327 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3328 .to_point(display_map);
3329 if reversed {
3330 Some(end..start)
3331 } else {
3332 Some(start..end)
3333 }
3334 } else {
3335 None
3336 }
3337 })
3338 .collect::<Vec<_>>();
3339
3340 self.change_selections(None, window, cx, |s| {
3341 s.select_ranges(selection_ranges);
3342 });
3343 cx.notify();
3344 }
3345
3346 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3347 self.selections
3348 .all_adjusted(cx)
3349 .iter()
3350 .any(|selection| !selection.is_empty())
3351 }
3352
3353 pub fn has_pending_nonempty_selection(&self) -> bool {
3354 let pending_nonempty_selection = match self.selections.pending_anchor() {
3355 Some(Selection { start, end, .. }) => start != end,
3356 None => false,
3357 };
3358
3359 pending_nonempty_selection
3360 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3361 }
3362
3363 pub fn has_pending_selection(&self) -> bool {
3364 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3365 }
3366
3367 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3368 self.selection_mark_mode = false;
3369
3370 if self.clear_expanded_diff_hunks(cx) {
3371 cx.notify();
3372 return;
3373 }
3374 if self.dismiss_menus_and_popups(true, window, cx) {
3375 return;
3376 }
3377
3378 if self.mode.is_full()
3379 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3380 {
3381 return;
3382 }
3383
3384 cx.propagate();
3385 }
3386
3387 pub fn dismiss_menus_and_popups(
3388 &mut self,
3389 is_user_requested: bool,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) -> bool {
3393 if self.take_rename(false, window, cx).is_some() {
3394 return true;
3395 }
3396
3397 if hide_hover(self, cx) {
3398 return true;
3399 }
3400
3401 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3402 return true;
3403 }
3404
3405 if self.hide_context_menu(window, cx).is_some() {
3406 return true;
3407 }
3408
3409 if self.mouse_context_menu.take().is_some() {
3410 return true;
3411 }
3412
3413 if is_user_requested && self.discard_inline_completion(true, cx) {
3414 return true;
3415 }
3416
3417 if self.snippet_stack.pop().is_some() {
3418 return true;
3419 }
3420
3421 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3422 self.dismiss_diagnostics(cx);
3423 return true;
3424 }
3425
3426 false
3427 }
3428
3429 fn linked_editing_ranges_for(
3430 &self,
3431 selection: Range<text::Anchor>,
3432 cx: &App,
3433 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3434 if self.linked_edit_ranges.is_empty() {
3435 return None;
3436 }
3437 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3438 selection.end.buffer_id.and_then(|end_buffer_id| {
3439 if selection.start.buffer_id != Some(end_buffer_id) {
3440 return None;
3441 }
3442 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3443 let snapshot = buffer.read(cx).snapshot();
3444 self.linked_edit_ranges
3445 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3446 .map(|ranges| (ranges, snapshot, buffer))
3447 })?;
3448 use text::ToOffset as TO;
3449 // find offset from the start of current range to current cursor position
3450 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3451
3452 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3453 let start_difference = start_offset - start_byte_offset;
3454 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3455 let end_difference = end_offset - start_byte_offset;
3456 // Current range has associated linked ranges.
3457 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3458 for range in linked_ranges.iter() {
3459 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3460 let end_offset = start_offset + end_difference;
3461 let start_offset = start_offset + start_difference;
3462 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3463 continue;
3464 }
3465 if self.selections.disjoint_anchor_ranges().any(|s| {
3466 if s.start.buffer_id != selection.start.buffer_id
3467 || s.end.buffer_id != selection.end.buffer_id
3468 {
3469 return false;
3470 }
3471 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3472 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3473 }) {
3474 continue;
3475 }
3476 let start = buffer_snapshot.anchor_after(start_offset);
3477 let end = buffer_snapshot.anchor_after(end_offset);
3478 linked_edits
3479 .entry(buffer.clone())
3480 .or_default()
3481 .push(start..end);
3482 }
3483 Some(linked_edits)
3484 }
3485
3486 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3487 let text: Arc<str> = text.into();
3488
3489 if self.read_only(cx) {
3490 return;
3491 }
3492
3493 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3494
3495 let selections = self.selections.all_adjusted(cx);
3496 let mut bracket_inserted = false;
3497 let mut edits = Vec::new();
3498 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3499 let mut new_selections = Vec::with_capacity(selections.len());
3500 let mut new_autoclose_regions = Vec::new();
3501 let snapshot = self.buffer.read(cx).read(cx);
3502 let mut clear_linked_edit_ranges = false;
3503
3504 for (selection, autoclose_region) in
3505 self.selections_with_autoclose_regions(selections, &snapshot)
3506 {
3507 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3508 // Determine if the inserted text matches the opening or closing
3509 // bracket of any of this language's bracket pairs.
3510 let mut bracket_pair = None;
3511 let mut is_bracket_pair_start = false;
3512 let mut is_bracket_pair_end = false;
3513 if !text.is_empty() {
3514 let mut bracket_pair_matching_end = None;
3515 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3516 // and they are removing the character that triggered IME popup.
3517 for (pair, enabled) in scope.brackets() {
3518 if !pair.close && !pair.surround {
3519 continue;
3520 }
3521
3522 if enabled && pair.start.ends_with(text.as_ref()) {
3523 let prefix_len = pair.start.len() - text.len();
3524 let preceding_text_matches_prefix = prefix_len == 0
3525 || (selection.start.column >= (prefix_len as u32)
3526 && snapshot.contains_str_at(
3527 Point::new(
3528 selection.start.row,
3529 selection.start.column - (prefix_len as u32),
3530 ),
3531 &pair.start[..prefix_len],
3532 ));
3533 if preceding_text_matches_prefix {
3534 bracket_pair = Some(pair.clone());
3535 is_bracket_pair_start = true;
3536 break;
3537 }
3538 }
3539 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3540 {
3541 // take first bracket pair matching end, but don't break in case a later bracket
3542 // pair matches start
3543 bracket_pair_matching_end = Some(pair.clone());
3544 }
3545 }
3546 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3547 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3548 is_bracket_pair_end = true;
3549 }
3550 }
3551
3552 if let Some(bracket_pair) = bracket_pair {
3553 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3554 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3555 let auto_surround =
3556 self.use_auto_surround && snapshot_settings.use_auto_surround;
3557 if selection.is_empty() {
3558 if is_bracket_pair_start {
3559 // If the inserted text is a suffix of an opening bracket and the
3560 // selection is preceded by the rest of the opening bracket, then
3561 // insert the closing bracket.
3562 let following_text_allows_autoclose = snapshot
3563 .chars_at(selection.start)
3564 .next()
3565 .map_or(true, |c| scope.should_autoclose_before(c));
3566
3567 let preceding_text_allows_autoclose = selection.start.column == 0
3568 || snapshot.reversed_chars_at(selection.start).next().map_or(
3569 true,
3570 |c| {
3571 bracket_pair.start != bracket_pair.end
3572 || !snapshot
3573 .char_classifier_at(selection.start)
3574 .is_word(c)
3575 },
3576 );
3577
3578 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3579 && bracket_pair.start.len() == 1
3580 {
3581 let target = bracket_pair.start.chars().next().unwrap();
3582 let current_line_count = snapshot
3583 .reversed_chars_at(selection.start)
3584 .take_while(|&c| c != '\n')
3585 .filter(|&c| c == target)
3586 .count();
3587 current_line_count % 2 == 1
3588 } else {
3589 false
3590 };
3591
3592 if autoclose
3593 && bracket_pair.close
3594 && following_text_allows_autoclose
3595 && preceding_text_allows_autoclose
3596 && !is_closing_quote
3597 {
3598 let anchor = snapshot.anchor_before(selection.end);
3599 new_selections.push((selection.map(|_| anchor), text.len()));
3600 new_autoclose_regions.push((
3601 anchor,
3602 text.len(),
3603 selection.id,
3604 bracket_pair.clone(),
3605 ));
3606 edits.push((
3607 selection.range(),
3608 format!("{}{}", text, bracket_pair.end).into(),
3609 ));
3610 bracket_inserted = true;
3611 continue;
3612 }
3613 }
3614
3615 if let Some(region) = autoclose_region {
3616 // If the selection is followed by an auto-inserted closing bracket,
3617 // then don't insert that closing bracket again; just move the selection
3618 // past the closing bracket.
3619 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3620 && text.as_ref() == region.pair.end.as_str();
3621 if should_skip {
3622 let anchor = snapshot.anchor_after(selection.end);
3623 new_selections
3624 .push((selection.map(|_| anchor), region.pair.end.len()));
3625 continue;
3626 }
3627 }
3628
3629 let always_treat_brackets_as_autoclosed = snapshot
3630 .language_settings_at(selection.start, cx)
3631 .always_treat_brackets_as_autoclosed;
3632 if always_treat_brackets_as_autoclosed
3633 && is_bracket_pair_end
3634 && snapshot.contains_str_at(selection.end, text.as_ref())
3635 {
3636 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3637 // and the inserted text is a closing bracket and the selection is followed
3638 // by the closing bracket then move the selection past the closing bracket.
3639 let anchor = snapshot.anchor_after(selection.end);
3640 new_selections.push((selection.map(|_| anchor), text.len()));
3641 continue;
3642 }
3643 }
3644 // If an opening bracket is 1 character long and is typed while
3645 // text is selected, then surround that text with the bracket pair.
3646 else if auto_surround
3647 && bracket_pair.surround
3648 && is_bracket_pair_start
3649 && bracket_pair.start.chars().count() == 1
3650 {
3651 edits.push((selection.start..selection.start, text.clone()));
3652 edits.push((
3653 selection.end..selection.end,
3654 bracket_pair.end.as_str().into(),
3655 ));
3656 bracket_inserted = true;
3657 new_selections.push((
3658 Selection {
3659 id: selection.id,
3660 start: snapshot.anchor_after(selection.start),
3661 end: snapshot.anchor_before(selection.end),
3662 reversed: selection.reversed,
3663 goal: selection.goal,
3664 },
3665 0,
3666 ));
3667 continue;
3668 }
3669 }
3670 }
3671
3672 if self.auto_replace_emoji_shortcode
3673 && selection.is_empty()
3674 && text.as_ref().ends_with(':')
3675 {
3676 if let Some(possible_emoji_short_code) =
3677 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3678 {
3679 if !possible_emoji_short_code.is_empty() {
3680 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3681 let emoji_shortcode_start = Point::new(
3682 selection.start.row,
3683 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3684 );
3685
3686 // Remove shortcode from buffer
3687 edits.push((
3688 emoji_shortcode_start..selection.start,
3689 "".to_string().into(),
3690 ));
3691 new_selections.push((
3692 Selection {
3693 id: selection.id,
3694 start: snapshot.anchor_after(emoji_shortcode_start),
3695 end: snapshot.anchor_before(selection.start),
3696 reversed: selection.reversed,
3697 goal: selection.goal,
3698 },
3699 0,
3700 ));
3701
3702 // Insert emoji
3703 let selection_start_anchor = snapshot.anchor_after(selection.start);
3704 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3705 edits.push((selection.start..selection.end, emoji.to_string().into()));
3706
3707 continue;
3708 }
3709 }
3710 }
3711 }
3712
3713 // If not handling any auto-close operation, then just replace the selected
3714 // text with the given input and move the selection to the end of the
3715 // newly inserted text.
3716 let anchor = snapshot.anchor_after(selection.end);
3717 if !self.linked_edit_ranges.is_empty() {
3718 let start_anchor = snapshot.anchor_before(selection.start);
3719
3720 let is_word_char = text.chars().next().map_or(true, |char| {
3721 let classifier = snapshot
3722 .char_classifier_at(start_anchor.to_offset(&snapshot))
3723 .ignore_punctuation(true);
3724 classifier.is_word(char)
3725 });
3726
3727 if is_word_char {
3728 if let Some(ranges) = self
3729 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3730 {
3731 for (buffer, edits) in ranges {
3732 linked_edits
3733 .entry(buffer.clone())
3734 .or_default()
3735 .extend(edits.into_iter().map(|range| (range, text.clone())));
3736 }
3737 }
3738 } else {
3739 clear_linked_edit_ranges = true;
3740 }
3741 }
3742
3743 new_selections.push((selection.map(|_| anchor), 0));
3744 edits.push((selection.start..selection.end, text.clone()));
3745 }
3746
3747 drop(snapshot);
3748
3749 self.transact(window, cx, |this, window, cx| {
3750 if clear_linked_edit_ranges {
3751 this.linked_edit_ranges.clear();
3752 }
3753 let initial_buffer_versions =
3754 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3755
3756 this.buffer.update(cx, |buffer, cx| {
3757 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3758 });
3759 for (buffer, edits) in linked_edits {
3760 buffer.update(cx, |buffer, cx| {
3761 let snapshot = buffer.snapshot();
3762 let edits = edits
3763 .into_iter()
3764 .map(|(range, text)| {
3765 use text::ToPoint as TP;
3766 let end_point = TP::to_point(&range.end, &snapshot);
3767 let start_point = TP::to_point(&range.start, &snapshot);
3768 (start_point..end_point, text)
3769 })
3770 .sorted_by_key(|(range, _)| range.start);
3771 buffer.edit(edits, None, cx);
3772 })
3773 }
3774 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3775 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3776 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3777 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3778 .zip(new_selection_deltas)
3779 .map(|(selection, delta)| Selection {
3780 id: selection.id,
3781 start: selection.start + delta,
3782 end: selection.end + delta,
3783 reversed: selection.reversed,
3784 goal: SelectionGoal::None,
3785 })
3786 .collect::<Vec<_>>();
3787
3788 let mut i = 0;
3789 for (position, delta, selection_id, pair) in new_autoclose_regions {
3790 let position = position.to_offset(&map.buffer_snapshot) + delta;
3791 let start = map.buffer_snapshot.anchor_before(position);
3792 let end = map.buffer_snapshot.anchor_after(position);
3793 while let Some(existing_state) = this.autoclose_regions.get(i) {
3794 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3795 Ordering::Less => i += 1,
3796 Ordering::Greater => break,
3797 Ordering::Equal => {
3798 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3799 Ordering::Less => i += 1,
3800 Ordering::Equal => break,
3801 Ordering::Greater => break,
3802 }
3803 }
3804 }
3805 }
3806 this.autoclose_regions.insert(
3807 i,
3808 AutocloseRegion {
3809 selection_id,
3810 range: start..end,
3811 pair,
3812 },
3813 );
3814 }
3815
3816 let had_active_inline_completion = this.has_active_inline_completion();
3817 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3818 s.select(new_selections)
3819 });
3820
3821 if !bracket_inserted {
3822 if let Some(on_type_format_task) =
3823 this.trigger_on_type_formatting(text.to_string(), window, cx)
3824 {
3825 on_type_format_task.detach_and_log_err(cx);
3826 }
3827 }
3828
3829 let editor_settings = EditorSettings::get_global(cx);
3830 if bracket_inserted
3831 && (editor_settings.auto_signature_help
3832 || editor_settings.show_signature_help_after_edits)
3833 {
3834 this.show_signature_help(&ShowSignatureHelp, window, cx);
3835 }
3836
3837 let trigger_in_words =
3838 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3839 if this.hard_wrap.is_some() {
3840 let latest: Range<Point> = this.selections.newest(cx).range();
3841 if latest.is_empty()
3842 && this
3843 .buffer()
3844 .read(cx)
3845 .snapshot(cx)
3846 .line_len(MultiBufferRow(latest.start.row))
3847 == latest.start.column
3848 {
3849 this.rewrap_impl(
3850 RewrapOptions {
3851 override_language_settings: true,
3852 preserve_existing_whitespace: true,
3853 },
3854 cx,
3855 )
3856 }
3857 }
3858 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3859 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3860 this.refresh_inline_completion(true, false, window, cx);
3861 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3862 });
3863 }
3864
3865 fn find_possible_emoji_shortcode_at_position(
3866 snapshot: &MultiBufferSnapshot,
3867 position: Point,
3868 ) -> Option<String> {
3869 let mut chars = Vec::new();
3870 let mut found_colon = false;
3871 for char in snapshot.reversed_chars_at(position).take(100) {
3872 // Found a possible emoji shortcode in the middle of the buffer
3873 if found_colon {
3874 if char.is_whitespace() {
3875 chars.reverse();
3876 return Some(chars.iter().collect());
3877 }
3878 // If the previous character is not a whitespace, we are in the middle of a word
3879 // and we only want to complete the shortcode if the word is made up of other emojis
3880 let mut containing_word = String::new();
3881 for ch in snapshot
3882 .reversed_chars_at(position)
3883 .skip(chars.len() + 1)
3884 .take(100)
3885 {
3886 if ch.is_whitespace() {
3887 break;
3888 }
3889 containing_word.push(ch);
3890 }
3891 let containing_word = containing_word.chars().rev().collect::<String>();
3892 if util::word_consists_of_emojis(containing_word.as_str()) {
3893 chars.reverse();
3894 return Some(chars.iter().collect());
3895 }
3896 }
3897
3898 if char.is_whitespace() || !char.is_ascii() {
3899 return None;
3900 }
3901 if char == ':' {
3902 found_colon = true;
3903 } else {
3904 chars.push(char);
3905 }
3906 }
3907 // Found a possible emoji shortcode at the beginning of the buffer
3908 chars.reverse();
3909 Some(chars.iter().collect())
3910 }
3911
3912 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3913 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3914 self.transact(window, cx, |this, window, cx| {
3915 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3916 let selections = this.selections.all::<usize>(cx);
3917 let multi_buffer = this.buffer.read(cx);
3918 let buffer = multi_buffer.snapshot(cx);
3919 selections
3920 .iter()
3921 .map(|selection| {
3922 let start_point = selection.start.to_point(&buffer);
3923 let mut existing_indent =
3924 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3925 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3926 let start = selection.start;
3927 let end = selection.end;
3928 let selection_is_empty = start == end;
3929 let language_scope = buffer.language_scope_at(start);
3930 let (
3931 comment_delimiter,
3932 doc_delimiter,
3933 insert_extra_newline,
3934 indent_on_newline,
3935 indent_on_extra_newline,
3936 ) = if let Some(language) = &language_scope {
3937 let mut insert_extra_newline =
3938 insert_extra_newline_brackets(&buffer, start..end, language)
3939 || insert_extra_newline_tree_sitter(&buffer, start..end);
3940
3941 // Comment extension on newline is allowed only for cursor selections
3942 let comment_delimiter = maybe!({
3943 if !selection_is_empty {
3944 return None;
3945 }
3946
3947 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3948 return None;
3949 }
3950
3951 let delimiters = language.line_comment_prefixes();
3952 let max_len_of_delimiter =
3953 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3954 let (snapshot, range) =
3955 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3956
3957 let mut index_of_first_non_whitespace = 0;
3958 let comment_candidate = snapshot
3959 .chars_for_range(range)
3960 .skip_while(|c| {
3961 let should_skip = c.is_whitespace();
3962 if should_skip {
3963 index_of_first_non_whitespace += 1;
3964 }
3965 should_skip
3966 })
3967 .take(max_len_of_delimiter)
3968 .collect::<String>();
3969 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3970 comment_candidate.starts_with(comment_prefix.as_ref())
3971 })?;
3972 let cursor_is_placed_after_comment_marker =
3973 index_of_first_non_whitespace + comment_prefix.len()
3974 <= start_point.column as usize;
3975 if cursor_is_placed_after_comment_marker {
3976 Some(comment_prefix.clone())
3977 } else {
3978 None
3979 }
3980 });
3981
3982 let mut indent_on_newline = IndentSize::spaces(0);
3983 let mut indent_on_extra_newline = IndentSize::spaces(0);
3984
3985 let doc_delimiter = maybe!({
3986 if !selection_is_empty {
3987 return None;
3988 }
3989
3990 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3991 return None;
3992 }
3993
3994 let DocumentationConfig {
3995 start: start_tag,
3996 end: end_tag,
3997 prefix: delimiter,
3998 tab_size: len,
3999 } = language.documentation()?;
4000
4001 let (snapshot, range) =
4002 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4003
4004 let num_of_whitespaces = snapshot
4005 .chars_for_range(range.clone())
4006 .take_while(|c| c.is_whitespace())
4007 .count();
4008
4009 let cursor_is_after_start_tag = {
4010 let start_tag_len = start_tag.len();
4011 let start_tag_line = snapshot
4012 .chars_for_range(range.clone())
4013 .skip(num_of_whitespaces)
4014 .take(start_tag_len)
4015 .collect::<String>();
4016 if start_tag_line.starts_with(start_tag.as_ref()) {
4017 num_of_whitespaces + start_tag_len
4018 <= start_point.column as usize
4019 } else {
4020 false
4021 }
4022 };
4023
4024 let cursor_is_after_delimiter = {
4025 let delimiter_trim = delimiter.trim_end();
4026 let delimiter_line = snapshot
4027 .chars_for_range(range.clone())
4028 .skip(num_of_whitespaces)
4029 .take(delimiter_trim.len())
4030 .collect::<String>();
4031 if delimiter_line.starts_with(delimiter_trim) {
4032 num_of_whitespaces + delimiter_trim.len()
4033 <= start_point.column as usize
4034 } else {
4035 false
4036 }
4037 };
4038
4039 let cursor_is_before_end_tag_if_exists = {
4040 let num_of_whitespaces_rev = snapshot
4041 .reversed_chars_for_range(range.clone())
4042 .take_while(|c| c.is_whitespace())
4043 .count();
4044 let mut line_iter = snapshot
4045 .reversed_chars_for_range(range)
4046 .skip(num_of_whitespaces_rev);
4047 let end_tag_exists = end_tag
4048 .chars()
4049 .rev()
4050 .all(|char| line_iter.next() == Some(char));
4051 if end_tag_exists {
4052 let max_point = snapshot.line_len(start_point.row) as usize;
4053 let ordering = (num_of_whitespaces_rev
4054 + end_tag.len()
4055 + start_point.column as usize)
4056 .cmp(&max_point);
4057 let cursor_is_before_end_tag =
4058 ordering != Ordering::Greater;
4059 if cursor_is_after_start_tag {
4060 if cursor_is_before_end_tag {
4061 insert_extra_newline = true;
4062 }
4063 let cursor_is_at_start_of_end_tag =
4064 ordering == Ordering::Equal;
4065 if cursor_is_at_start_of_end_tag {
4066 indent_on_extra_newline.len = (*len).into();
4067 }
4068 }
4069 cursor_is_before_end_tag
4070 } else {
4071 true
4072 }
4073 };
4074
4075 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4076 && cursor_is_before_end_tag_if_exists
4077 {
4078 if cursor_is_after_start_tag {
4079 indent_on_newline.len = (*len).into();
4080 }
4081 Some(delimiter.clone())
4082 } else {
4083 None
4084 }
4085 });
4086
4087 (
4088 comment_delimiter,
4089 doc_delimiter,
4090 insert_extra_newline,
4091 indent_on_newline,
4092 indent_on_extra_newline,
4093 )
4094 } else {
4095 (
4096 None,
4097 None,
4098 false,
4099 IndentSize::default(),
4100 IndentSize::default(),
4101 )
4102 };
4103
4104 let prevent_auto_indent = doc_delimiter.is_some();
4105 let delimiter = comment_delimiter.or(doc_delimiter);
4106
4107 let capacity_for_delimiter =
4108 delimiter.as_deref().map(str::len).unwrap_or_default();
4109 let mut new_text = String::with_capacity(
4110 1 + capacity_for_delimiter
4111 + existing_indent.len as usize
4112 + indent_on_newline.len as usize
4113 + indent_on_extra_newline.len as usize,
4114 );
4115 new_text.push('\n');
4116 new_text.extend(existing_indent.chars());
4117 new_text.extend(indent_on_newline.chars());
4118
4119 if let Some(delimiter) = &delimiter {
4120 new_text.push_str(delimiter);
4121 }
4122
4123 if insert_extra_newline {
4124 new_text.push('\n');
4125 new_text.extend(existing_indent.chars());
4126 new_text.extend(indent_on_extra_newline.chars());
4127 }
4128
4129 let anchor = buffer.anchor_after(end);
4130 let new_selection = selection.map(|_| anchor);
4131 (
4132 ((start..end, new_text), prevent_auto_indent),
4133 (insert_extra_newline, new_selection),
4134 )
4135 })
4136 .unzip()
4137 };
4138
4139 let mut auto_indent_edits = Vec::new();
4140 let mut edits = Vec::new();
4141 for (edit, prevent_auto_indent) in edits_with_flags {
4142 if prevent_auto_indent {
4143 edits.push(edit);
4144 } else {
4145 auto_indent_edits.push(edit);
4146 }
4147 }
4148 if !edits.is_empty() {
4149 this.edit(edits, cx);
4150 }
4151 if !auto_indent_edits.is_empty() {
4152 this.edit_with_autoindent(auto_indent_edits, cx);
4153 }
4154
4155 let buffer = this.buffer.read(cx).snapshot(cx);
4156 let new_selections = selection_info
4157 .into_iter()
4158 .map(|(extra_newline_inserted, new_selection)| {
4159 let mut cursor = new_selection.end.to_point(&buffer);
4160 if extra_newline_inserted {
4161 cursor.row -= 1;
4162 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4163 }
4164 new_selection.map(|_| cursor)
4165 })
4166 .collect();
4167
4168 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4169 s.select(new_selections)
4170 });
4171 this.refresh_inline_completion(true, false, window, cx);
4172 });
4173 }
4174
4175 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4176 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4177
4178 let buffer = self.buffer.read(cx);
4179 let snapshot = buffer.snapshot(cx);
4180
4181 let mut edits = Vec::new();
4182 let mut rows = Vec::new();
4183
4184 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4185 let cursor = selection.head();
4186 let row = cursor.row;
4187
4188 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4189
4190 let newline = "\n".to_string();
4191 edits.push((start_of_line..start_of_line, newline));
4192
4193 rows.push(row + rows_inserted as u32);
4194 }
4195
4196 self.transact(window, cx, |editor, window, cx| {
4197 editor.edit(edits, cx);
4198
4199 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4200 let mut index = 0;
4201 s.move_cursors_with(|map, _, _| {
4202 let row = rows[index];
4203 index += 1;
4204
4205 let point = Point::new(row, 0);
4206 let boundary = map.next_line_boundary(point).1;
4207 let clipped = map.clip_point(boundary, Bias::Left);
4208
4209 (clipped, SelectionGoal::None)
4210 });
4211 });
4212
4213 let mut indent_edits = Vec::new();
4214 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4215 for row in rows {
4216 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4217 for (row, indent) in indents {
4218 if indent.len == 0 {
4219 continue;
4220 }
4221
4222 let text = match indent.kind {
4223 IndentKind::Space => " ".repeat(indent.len as usize),
4224 IndentKind::Tab => "\t".repeat(indent.len as usize),
4225 };
4226 let point = Point::new(row.0, 0);
4227 indent_edits.push((point..point, text));
4228 }
4229 }
4230 editor.edit(indent_edits, cx);
4231 });
4232 }
4233
4234 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4235 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4236
4237 let buffer = self.buffer.read(cx);
4238 let snapshot = buffer.snapshot(cx);
4239
4240 let mut edits = Vec::new();
4241 let mut rows = Vec::new();
4242 let mut rows_inserted = 0;
4243
4244 for selection in self.selections.all_adjusted(cx) {
4245 let cursor = selection.head();
4246 let row = cursor.row;
4247
4248 let point = Point::new(row + 1, 0);
4249 let start_of_line = snapshot.clip_point(point, Bias::Left);
4250
4251 let newline = "\n".to_string();
4252 edits.push((start_of_line..start_of_line, newline));
4253
4254 rows_inserted += 1;
4255 rows.push(row + rows_inserted);
4256 }
4257
4258 self.transact(window, cx, |editor, window, cx| {
4259 editor.edit(edits, cx);
4260
4261 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4262 let mut index = 0;
4263 s.move_cursors_with(|map, _, _| {
4264 let row = rows[index];
4265 index += 1;
4266
4267 let point = Point::new(row, 0);
4268 let boundary = map.next_line_boundary(point).1;
4269 let clipped = map.clip_point(boundary, Bias::Left);
4270
4271 (clipped, SelectionGoal::None)
4272 });
4273 });
4274
4275 let mut indent_edits = Vec::new();
4276 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4277 for row in rows {
4278 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4279 for (row, indent) in indents {
4280 if indent.len == 0 {
4281 continue;
4282 }
4283
4284 let text = match indent.kind {
4285 IndentKind::Space => " ".repeat(indent.len as usize),
4286 IndentKind::Tab => "\t".repeat(indent.len as usize),
4287 };
4288 let point = Point::new(row.0, 0);
4289 indent_edits.push((point..point, text));
4290 }
4291 }
4292 editor.edit(indent_edits, cx);
4293 });
4294 }
4295
4296 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4297 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4298 original_indent_columns: Vec::new(),
4299 });
4300 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4301 }
4302
4303 fn insert_with_autoindent_mode(
4304 &mut self,
4305 text: &str,
4306 autoindent_mode: Option<AutoindentMode>,
4307 window: &mut Window,
4308 cx: &mut Context<Self>,
4309 ) {
4310 if self.read_only(cx) {
4311 return;
4312 }
4313
4314 let text: Arc<str> = text.into();
4315 self.transact(window, cx, |this, window, cx| {
4316 let old_selections = this.selections.all_adjusted(cx);
4317 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4318 let anchors = {
4319 let snapshot = buffer.read(cx);
4320 old_selections
4321 .iter()
4322 .map(|s| {
4323 let anchor = snapshot.anchor_after(s.head());
4324 s.map(|_| anchor)
4325 })
4326 .collect::<Vec<_>>()
4327 };
4328 buffer.edit(
4329 old_selections
4330 .iter()
4331 .map(|s| (s.start..s.end, text.clone())),
4332 autoindent_mode,
4333 cx,
4334 );
4335 anchors
4336 });
4337
4338 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4339 s.select_anchors(selection_anchors);
4340 });
4341
4342 cx.notify();
4343 });
4344 }
4345
4346 fn trigger_completion_on_input(
4347 &mut self,
4348 text: &str,
4349 trigger_in_words: bool,
4350 window: &mut Window,
4351 cx: &mut Context<Self>,
4352 ) {
4353 let ignore_completion_provider = self
4354 .context_menu
4355 .borrow()
4356 .as_ref()
4357 .map(|menu| match menu {
4358 CodeContextMenu::Completions(completions_menu) => {
4359 completions_menu.ignore_completion_provider
4360 }
4361 CodeContextMenu::CodeActions(_) => false,
4362 })
4363 .unwrap_or(false);
4364
4365 if ignore_completion_provider {
4366 self.show_word_completions(&ShowWordCompletions, window, cx);
4367 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4368 self.show_completions(
4369 &ShowCompletions {
4370 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4371 },
4372 window,
4373 cx,
4374 );
4375 } else {
4376 self.hide_context_menu(window, cx);
4377 }
4378 }
4379
4380 fn is_completion_trigger(
4381 &self,
4382 text: &str,
4383 trigger_in_words: bool,
4384 cx: &mut Context<Self>,
4385 ) -> bool {
4386 let position = self.selections.newest_anchor().head();
4387 let multibuffer = self.buffer.read(cx);
4388 let Some(buffer) = position
4389 .buffer_id
4390 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4391 else {
4392 return false;
4393 };
4394
4395 if let Some(completion_provider) = &self.completion_provider {
4396 completion_provider.is_completion_trigger(
4397 &buffer,
4398 position.text_anchor,
4399 text,
4400 trigger_in_words,
4401 cx,
4402 )
4403 } else {
4404 false
4405 }
4406 }
4407
4408 /// If any empty selections is touching the start of its innermost containing autoclose
4409 /// region, expand it to select the brackets.
4410 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4411 let selections = self.selections.all::<usize>(cx);
4412 let buffer = self.buffer.read(cx).read(cx);
4413 let new_selections = self
4414 .selections_with_autoclose_regions(selections, &buffer)
4415 .map(|(mut selection, region)| {
4416 if !selection.is_empty() {
4417 return selection;
4418 }
4419
4420 if let Some(region) = region {
4421 let mut range = region.range.to_offset(&buffer);
4422 if selection.start == range.start && range.start >= region.pair.start.len() {
4423 range.start -= region.pair.start.len();
4424 if buffer.contains_str_at(range.start, ®ion.pair.start)
4425 && buffer.contains_str_at(range.end, ®ion.pair.end)
4426 {
4427 range.end += region.pair.end.len();
4428 selection.start = range.start;
4429 selection.end = range.end;
4430
4431 return selection;
4432 }
4433 }
4434 }
4435
4436 let always_treat_brackets_as_autoclosed = buffer
4437 .language_settings_at(selection.start, cx)
4438 .always_treat_brackets_as_autoclosed;
4439
4440 if !always_treat_brackets_as_autoclosed {
4441 return selection;
4442 }
4443
4444 if let Some(scope) = buffer.language_scope_at(selection.start) {
4445 for (pair, enabled) in scope.brackets() {
4446 if !enabled || !pair.close {
4447 continue;
4448 }
4449
4450 if buffer.contains_str_at(selection.start, &pair.end) {
4451 let pair_start_len = pair.start.len();
4452 if buffer.contains_str_at(
4453 selection.start.saturating_sub(pair_start_len),
4454 &pair.start,
4455 ) {
4456 selection.start -= pair_start_len;
4457 selection.end += pair.end.len();
4458
4459 return selection;
4460 }
4461 }
4462 }
4463 }
4464
4465 selection
4466 })
4467 .collect();
4468
4469 drop(buffer);
4470 self.change_selections(None, window, cx, |selections| {
4471 selections.select(new_selections)
4472 });
4473 }
4474
4475 /// Iterate the given selections, and for each one, find the smallest surrounding
4476 /// autoclose region. This uses the ordering of the selections and the autoclose
4477 /// regions to avoid repeated comparisons.
4478 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4479 &'a self,
4480 selections: impl IntoIterator<Item = Selection<D>>,
4481 buffer: &'a MultiBufferSnapshot,
4482 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4483 let mut i = 0;
4484 let mut regions = self.autoclose_regions.as_slice();
4485 selections.into_iter().map(move |selection| {
4486 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4487
4488 let mut enclosing = None;
4489 while let Some(pair_state) = regions.get(i) {
4490 if pair_state.range.end.to_offset(buffer) < range.start {
4491 regions = ®ions[i + 1..];
4492 i = 0;
4493 } else if pair_state.range.start.to_offset(buffer) > range.end {
4494 break;
4495 } else {
4496 if pair_state.selection_id == selection.id {
4497 enclosing = Some(pair_state);
4498 }
4499 i += 1;
4500 }
4501 }
4502
4503 (selection, enclosing)
4504 })
4505 }
4506
4507 /// Remove any autoclose regions that no longer contain their selection.
4508 fn invalidate_autoclose_regions(
4509 &mut self,
4510 mut selections: &[Selection<Anchor>],
4511 buffer: &MultiBufferSnapshot,
4512 ) {
4513 self.autoclose_regions.retain(|state| {
4514 let mut i = 0;
4515 while let Some(selection) = selections.get(i) {
4516 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4517 selections = &selections[1..];
4518 continue;
4519 }
4520 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4521 break;
4522 }
4523 if selection.id == state.selection_id {
4524 return true;
4525 } else {
4526 i += 1;
4527 }
4528 }
4529 false
4530 });
4531 }
4532
4533 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4534 let offset = position.to_offset(buffer);
4535 let (word_range, kind) = buffer.surrounding_word(offset, true);
4536 if offset > word_range.start && kind == Some(CharKind::Word) {
4537 Some(
4538 buffer
4539 .text_for_range(word_range.start..offset)
4540 .collect::<String>(),
4541 )
4542 } else {
4543 None
4544 }
4545 }
4546
4547 pub fn toggle_inline_values(
4548 &mut self,
4549 _: &ToggleInlineValues,
4550 _: &mut Window,
4551 cx: &mut Context<Self>,
4552 ) {
4553 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4554
4555 self.refresh_inline_values(cx);
4556 }
4557
4558 pub fn toggle_inlay_hints(
4559 &mut self,
4560 _: &ToggleInlayHints,
4561 _: &mut Window,
4562 cx: &mut Context<Self>,
4563 ) {
4564 self.refresh_inlay_hints(
4565 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4566 cx,
4567 );
4568 }
4569
4570 pub fn inlay_hints_enabled(&self) -> bool {
4571 self.inlay_hint_cache.enabled
4572 }
4573
4574 pub fn inline_values_enabled(&self) -> bool {
4575 self.inline_value_cache.enabled
4576 }
4577
4578 #[cfg(any(test, feature = "test-support"))]
4579 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4580 self.display_map
4581 .read(cx)
4582 .current_inlays()
4583 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4584 .cloned()
4585 .collect()
4586 }
4587
4588 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4589 if self.semantics_provider.is_none() || !self.mode.is_full() {
4590 return;
4591 }
4592
4593 let reason_description = reason.description();
4594 let ignore_debounce = matches!(
4595 reason,
4596 InlayHintRefreshReason::SettingsChange(_)
4597 | InlayHintRefreshReason::Toggle(_)
4598 | InlayHintRefreshReason::ExcerptsRemoved(_)
4599 | InlayHintRefreshReason::ModifiersChanged(_)
4600 );
4601 let (invalidate_cache, required_languages) = match reason {
4602 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4603 match self.inlay_hint_cache.modifiers_override(enabled) {
4604 Some(enabled) => {
4605 if enabled {
4606 (InvalidationStrategy::RefreshRequested, None)
4607 } else {
4608 self.splice_inlays(
4609 &self
4610 .visible_inlay_hints(cx)
4611 .iter()
4612 .map(|inlay| inlay.id)
4613 .collect::<Vec<InlayId>>(),
4614 Vec::new(),
4615 cx,
4616 );
4617 return;
4618 }
4619 }
4620 None => return,
4621 }
4622 }
4623 InlayHintRefreshReason::Toggle(enabled) => {
4624 if self.inlay_hint_cache.toggle(enabled) {
4625 if enabled {
4626 (InvalidationStrategy::RefreshRequested, None)
4627 } else {
4628 self.splice_inlays(
4629 &self
4630 .visible_inlay_hints(cx)
4631 .iter()
4632 .map(|inlay| inlay.id)
4633 .collect::<Vec<InlayId>>(),
4634 Vec::new(),
4635 cx,
4636 );
4637 return;
4638 }
4639 } else {
4640 return;
4641 }
4642 }
4643 InlayHintRefreshReason::SettingsChange(new_settings) => {
4644 match self.inlay_hint_cache.update_settings(
4645 &self.buffer,
4646 new_settings,
4647 self.visible_inlay_hints(cx),
4648 cx,
4649 ) {
4650 ControlFlow::Break(Some(InlaySplice {
4651 to_remove,
4652 to_insert,
4653 })) => {
4654 self.splice_inlays(&to_remove, to_insert, cx);
4655 return;
4656 }
4657 ControlFlow::Break(None) => return,
4658 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4659 }
4660 }
4661 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4662 if let Some(InlaySplice {
4663 to_remove,
4664 to_insert,
4665 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4666 {
4667 self.splice_inlays(&to_remove, to_insert, cx);
4668 }
4669 self.display_map.update(cx, |display_map, _| {
4670 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4671 });
4672 return;
4673 }
4674 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4675 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4676 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4677 }
4678 InlayHintRefreshReason::RefreshRequested => {
4679 (InvalidationStrategy::RefreshRequested, None)
4680 }
4681 };
4682
4683 if let Some(InlaySplice {
4684 to_remove,
4685 to_insert,
4686 }) = self.inlay_hint_cache.spawn_hint_refresh(
4687 reason_description,
4688 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4689 invalidate_cache,
4690 ignore_debounce,
4691 cx,
4692 ) {
4693 self.splice_inlays(&to_remove, to_insert, cx);
4694 }
4695 }
4696
4697 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4698 self.display_map
4699 .read(cx)
4700 .current_inlays()
4701 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4702 .cloned()
4703 .collect()
4704 }
4705
4706 pub fn excerpts_for_inlay_hints_query(
4707 &self,
4708 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4709 cx: &mut Context<Editor>,
4710 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4711 let Some(project) = self.project.as_ref() else {
4712 return HashMap::default();
4713 };
4714 let project = project.read(cx);
4715 let multi_buffer = self.buffer().read(cx);
4716 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4717 let multi_buffer_visible_start = self
4718 .scroll_manager
4719 .anchor()
4720 .anchor
4721 .to_point(&multi_buffer_snapshot);
4722 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4723 multi_buffer_visible_start
4724 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4725 Bias::Left,
4726 );
4727 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4728 multi_buffer_snapshot
4729 .range_to_buffer_ranges(multi_buffer_visible_range)
4730 .into_iter()
4731 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4732 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4733 let buffer_file = project::File::from_dyn(buffer.file())?;
4734 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4735 let worktree_entry = buffer_worktree
4736 .read(cx)
4737 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4738 if worktree_entry.is_ignored {
4739 return None;
4740 }
4741
4742 let language = buffer.language()?;
4743 if let Some(restrict_to_languages) = restrict_to_languages {
4744 if !restrict_to_languages.contains(language) {
4745 return None;
4746 }
4747 }
4748 Some((
4749 excerpt_id,
4750 (
4751 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4752 buffer.version().clone(),
4753 excerpt_visible_range,
4754 ),
4755 ))
4756 })
4757 .collect()
4758 }
4759
4760 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4761 TextLayoutDetails {
4762 text_system: window.text_system().clone(),
4763 editor_style: self.style.clone().unwrap(),
4764 rem_size: window.rem_size(),
4765 scroll_anchor: self.scroll_manager.anchor(),
4766 visible_rows: self.visible_line_count(),
4767 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4768 }
4769 }
4770
4771 pub fn splice_inlays(
4772 &self,
4773 to_remove: &[InlayId],
4774 to_insert: Vec<Inlay>,
4775 cx: &mut Context<Self>,
4776 ) {
4777 self.display_map.update(cx, |display_map, cx| {
4778 display_map.splice_inlays(to_remove, to_insert, cx)
4779 });
4780 cx.notify();
4781 }
4782
4783 fn trigger_on_type_formatting(
4784 &self,
4785 input: String,
4786 window: &mut Window,
4787 cx: &mut Context<Self>,
4788 ) -> Option<Task<Result<()>>> {
4789 if input.len() != 1 {
4790 return None;
4791 }
4792
4793 let project = self.project.as_ref()?;
4794 let position = self.selections.newest_anchor().head();
4795 let (buffer, buffer_position) = self
4796 .buffer
4797 .read(cx)
4798 .text_anchor_for_position(position, cx)?;
4799
4800 let settings = language_settings::language_settings(
4801 buffer
4802 .read(cx)
4803 .language_at(buffer_position)
4804 .map(|l| l.name()),
4805 buffer.read(cx).file(),
4806 cx,
4807 );
4808 if !settings.use_on_type_format {
4809 return None;
4810 }
4811
4812 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4813 // hence we do LSP request & edit on host side only — add formats to host's history.
4814 let push_to_lsp_host_history = true;
4815 // If this is not the host, append its history with new edits.
4816 let push_to_client_history = project.read(cx).is_via_collab();
4817
4818 let on_type_formatting = project.update(cx, |project, cx| {
4819 project.on_type_format(
4820 buffer.clone(),
4821 buffer_position,
4822 input,
4823 push_to_lsp_host_history,
4824 cx,
4825 )
4826 });
4827 Some(cx.spawn_in(window, async move |editor, cx| {
4828 if let Some(transaction) = on_type_formatting.await? {
4829 if push_to_client_history {
4830 buffer
4831 .update(cx, |buffer, _| {
4832 buffer.push_transaction(transaction, Instant::now());
4833 buffer.finalize_last_transaction();
4834 })
4835 .ok();
4836 }
4837 editor.update(cx, |editor, cx| {
4838 editor.refresh_document_highlights(cx);
4839 })?;
4840 }
4841 Ok(())
4842 }))
4843 }
4844
4845 pub fn show_word_completions(
4846 &mut self,
4847 _: &ShowWordCompletions,
4848 window: &mut Window,
4849 cx: &mut Context<Self>,
4850 ) {
4851 self.open_completions_menu(true, None, window, cx);
4852 }
4853
4854 pub fn show_completions(
4855 &mut self,
4856 options: &ShowCompletions,
4857 window: &mut Window,
4858 cx: &mut Context<Self>,
4859 ) {
4860 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4861 }
4862
4863 fn open_completions_menu(
4864 &mut self,
4865 ignore_completion_provider: bool,
4866 trigger: Option<&str>,
4867 window: &mut Window,
4868 cx: &mut Context<Self>,
4869 ) {
4870 if self.pending_rename.is_some() {
4871 return;
4872 }
4873 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4874 return;
4875 }
4876
4877 let position = self.selections.newest_anchor().head();
4878 if position.diff_base_anchor.is_some() {
4879 return;
4880 }
4881 let (buffer, buffer_position) =
4882 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4883 output
4884 } else {
4885 return;
4886 };
4887 let buffer_snapshot = buffer.read(cx).snapshot();
4888 let show_completion_documentation = buffer_snapshot
4889 .settings_at(buffer_position, cx)
4890 .show_completion_documentation;
4891
4892 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4893
4894 let trigger_kind = match trigger {
4895 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4896 CompletionTriggerKind::TRIGGER_CHARACTER
4897 }
4898 _ => CompletionTriggerKind::INVOKED,
4899 };
4900 let completion_context = CompletionContext {
4901 trigger_character: trigger.and_then(|trigger| {
4902 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4903 Some(String::from(trigger))
4904 } else {
4905 None
4906 }
4907 }),
4908 trigger_kind,
4909 };
4910
4911 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4912 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4913 let word_to_exclude = buffer_snapshot
4914 .text_for_range(old_range.clone())
4915 .collect::<String>();
4916 (
4917 buffer_snapshot.anchor_before(old_range.start)
4918 ..buffer_snapshot.anchor_after(old_range.end),
4919 Some(word_to_exclude),
4920 )
4921 } else {
4922 (buffer_position..buffer_position, None)
4923 };
4924
4925 let completion_settings = language_settings(
4926 buffer_snapshot
4927 .language_at(buffer_position)
4928 .map(|language| language.name()),
4929 buffer_snapshot.file(),
4930 cx,
4931 )
4932 .completions;
4933
4934 // The document can be large, so stay in reasonable bounds when searching for words,
4935 // otherwise completion pop-up might be slow to appear.
4936 const WORD_LOOKUP_ROWS: u32 = 5_000;
4937 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4938 let min_word_search = buffer_snapshot.clip_point(
4939 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4940 Bias::Left,
4941 );
4942 let max_word_search = buffer_snapshot.clip_point(
4943 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4944 Bias::Right,
4945 );
4946 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4947 ..buffer_snapshot.point_to_offset(max_word_search);
4948
4949 let provider = self
4950 .completion_provider
4951 .as_ref()
4952 .filter(|_| !ignore_completion_provider);
4953 let skip_digits = query
4954 .as_ref()
4955 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4956
4957 let (mut words, provided_completions) = match provider {
4958 Some(provider) => {
4959 let completions = provider.completions(
4960 position.excerpt_id,
4961 &buffer,
4962 buffer_position,
4963 completion_context,
4964 window,
4965 cx,
4966 );
4967
4968 let words = match completion_settings.words {
4969 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4970 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4971 .background_spawn(async move {
4972 buffer_snapshot.words_in_range(WordsQuery {
4973 fuzzy_contents: None,
4974 range: word_search_range,
4975 skip_digits,
4976 })
4977 }),
4978 };
4979
4980 (words, completions)
4981 }
4982 None => (
4983 cx.background_spawn(async move {
4984 buffer_snapshot.words_in_range(WordsQuery {
4985 fuzzy_contents: None,
4986 range: word_search_range,
4987 skip_digits,
4988 })
4989 }),
4990 Task::ready(Ok(None)),
4991 ),
4992 };
4993
4994 let sort_completions = provider
4995 .as_ref()
4996 .map_or(false, |provider| provider.sort_completions());
4997
4998 let filter_completions = provider
4999 .as_ref()
5000 .map_or(true, |provider| provider.filter_completions());
5001
5002 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5003
5004 let id = post_inc(&mut self.next_completion_id);
5005 let task = cx.spawn_in(window, async move |editor, cx| {
5006 async move {
5007 editor.update(cx, |this, _| {
5008 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5009 })?;
5010
5011 let mut completions = Vec::new();
5012 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5013 completions.extend(provided_completions);
5014 if completion_settings.words == WordsCompletionMode::Fallback {
5015 words = Task::ready(BTreeMap::default());
5016 }
5017 }
5018
5019 let mut words = words.await;
5020 if let Some(word_to_exclude) = &word_to_exclude {
5021 words.remove(word_to_exclude);
5022 }
5023 for lsp_completion in &completions {
5024 words.remove(&lsp_completion.new_text);
5025 }
5026 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5027 replace_range: old_range.clone(),
5028 new_text: word.clone(),
5029 label: CodeLabel::plain(word, None),
5030 icon_path: None,
5031 documentation: None,
5032 source: CompletionSource::BufferWord {
5033 word_range,
5034 resolved: false,
5035 },
5036 insert_text_mode: Some(InsertTextMode::AS_IS),
5037 confirm: None,
5038 }));
5039
5040 let menu = if completions.is_empty() {
5041 None
5042 } else {
5043 let mut menu = CompletionsMenu::new(
5044 id,
5045 sort_completions,
5046 show_completion_documentation,
5047 ignore_completion_provider,
5048 position,
5049 buffer.clone(),
5050 completions.into(),
5051 snippet_sort_order,
5052 );
5053
5054 menu.filter(
5055 if filter_completions {
5056 query.as_deref()
5057 } else {
5058 None
5059 },
5060 cx.background_executor().clone(),
5061 )
5062 .await;
5063
5064 menu.visible().then_some(menu)
5065 };
5066
5067 editor.update_in(cx, |editor, window, cx| {
5068 match editor.context_menu.borrow().as_ref() {
5069 None => {}
5070 Some(CodeContextMenu::Completions(prev_menu)) => {
5071 if prev_menu.id > id {
5072 return;
5073 }
5074 }
5075 _ => return,
5076 }
5077
5078 if editor.focus_handle.is_focused(window) && menu.is_some() {
5079 let mut menu = menu.unwrap();
5080 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5081
5082 *editor.context_menu.borrow_mut() =
5083 Some(CodeContextMenu::Completions(menu));
5084
5085 if editor.show_edit_predictions_in_menu() {
5086 editor.update_visible_inline_completion(window, cx);
5087 } else {
5088 editor.discard_inline_completion(false, cx);
5089 }
5090
5091 cx.notify();
5092 } else if editor.completion_tasks.len() <= 1 {
5093 // If there are no more completion tasks and the last menu was
5094 // empty, we should hide it.
5095 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5096 // If it was already hidden and we don't show inline
5097 // completions in the menu, we should also show the
5098 // inline-completion when available.
5099 if was_hidden && editor.show_edit_predictions_in_menu() {
5100 editor.update_visible_inline_completion(window, cx);
5101 }
5102 }
5103 })?;
5104
5105 anyhow::Ok(())
5106 }
5107 .log_err()
5108 .await
5109 });
5110
5111 self.completion_tasks.push((id, task));
5112 }
5113
5114 #[cfg(feature = "test-support")]
5115 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5116 let menu = self.context_menu.borrow();
5117 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5118 let completions = menu.completions.borrow();
5119 Some(completions.to_vec())
5120 } else {
5121 None
5122 }
5123 }
5124
5125 pub fn confirm_completion(
5126 &mut self,
5127 action: &ConfirmCompletion,
5128 window: &mut Window,
5129 cx: &mut Context<Self>,
5130 ) -> Option<Task<Result<()>>> {
5131 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5132 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5133 }
5134
5135 pub fn confirm_completion_insert(
5136 &mut self,
5137 _: &ConfirmCompletionInsert,
5138 window: &mut Window,
5139 cx: &mut Context<Self>,
5140 ) -> Option<Task<Result<()>>> {
5141 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5142 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5143 }
5144
5145 pub fn confirm_completion_replace(
5146 &mut self,
5147 _: &ConfirmCompletionReplace,
5148 window: &mut Window,
5149 cx: &mut Context<Self>,
5150 ) -> Option<Task<Result<()>>> {
5151 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5152 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5153 }
5154
5155 pub fn compose_completion(
5156 &mut self,
5157 action: &ComposeCompletion,
5158 window: &mut Window,
5159 cx: &mut Context<Self>,
5160 ) -> Option<Task<Result<()>>> {
5161 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5162 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5163 }
5164
5165 fn do_completion(
5166 &mut self,
5167 item_ix: Option<usize>,
5168 intent: CompletionIntent,
5169 window: &mut Window,
5170 cx: &mut Context<Editor>,
5171 ) -> Option<Task<Result<()>>> {
5172 use language::ToOffset as _;
5173
5174 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5175 else {
5176 return None;
5177 };
5178
5179 let candidate_id = {
5180 let entries = completions_menu.entries.borrow();
5181 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5182 if self.show_edit_predictions_in_menu() {
5183 self.discard_inline_completion(true, cx);
5184 }
5185 mat.candidate_id
5186 };
5187
5188 let buffer_handle = completions_menu.buffer;
5189 let completion = completions_menu
5190 .completions
5191 .borrow()
5192 .get(candidate_id)?
5193 .clone();
5194 cx.stop_propagation();
5195
5196 let snapshot = self.buffer.read(cx).snapshot(cx);
5197 let newest_anchor = self.selections.newest_anchor();
5198
5199 let snippet;
5200 let new_text;
5201 if completion.is_snippet() {
5202 let mut snippet_source = completion.new_text.clone();
5203 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5204 if scope.prefers_label_for_snippet_in_completion() {
5205 if let Some(label) = completion.label() {
5206 if matches!(
5207 completion.kind(),
5208 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5209 ) {
5210 snippet_source = label;
5211 }
5212 }
5213 }
5214 }
5215 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5216 new_text = snippet.as_ref().unwrap().text.clone();
5217 } else {
5218 snippet = None;
5219 new_text = completion.new_text.clone();
5220 };
5221
5222 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5223 let buffer = buffer_handle.read(cx);
5224 let replace_range_multibuffer = {
5225 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5226 let multibuffer_anchor = snapshot
5227 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5228 .unwrap()
5229 ..snapshot
5230 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5231 .unwrap();
5232 multibuffer_anchor.start.to_offset(&snapshot)
5233 ..multibuffer_anchor.end.to_offset(&snapshot)
5234 };
5235 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5236 return None;
5237 }
5238
5239 let old_text = buffer
5240 .text_for_range(replace_range.clone())
5241 .collect::<String>();
5242 let lookbehind = newest_anchor
5243 .start
5244 .text_anchor
5245 .to_offset(buffer)
5246 .saturating_sub(replace_range.start);
5247 let lookahead = replace_range
5248 .end
5249 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5250 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5251 let suffix = &old_text[lookbehind.min(old_text.len())..];
5252
5253 let selections = self.selections.all::<usize>(cx);
5254 let mut ranges = Vec::new();
5255 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5256
5257 for selection in &selections {
5258 let range = if selection.id == newest_anchor.id {
5259 replace_range_multibuffer.clone()
5260 } else {
5261 let mut range = selection.range();
5262
5263 // if prefix is present, don't duplicate it
5264 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5265 range.start = range.start.saturating_sub(lookbehind);
5266
5267 // if suffix is also present, mimic the newest cursor and replace it
5268 if selection.id != newest_anchor.id
5269 && snapshot.contains_str_at(range.end, suffix)
5270 {
5271 range.end += lookahead;
5272 }
5273 }
5274 range
5275 };
5276
5277 ranges.push(range.clone());
5278
5279 if !self.linked_edit_ranges.is_empty() {
5280 let start_anchor = snapshot.anchor_before(range.start);
5281 let end_anchor = snapshot.anchor_after(range.end);
5282 if let Some(ranges) = self
5283 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5284 {
5285 for (buffer, edits) in ranges {
5286 linked_edits
5287 .entry(buffer.clone())
5288 .or_default()
5289 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5290 }
5291 }
5292 }
5293 }
5294
5295 cx.emit(EditorEvent::InputHandled {
5296 utf16_range_to_replace: None,
5297 text: new_text.clone().into(),
5298 });
5299
5300 self.transact(window, cx, |this, window, cx| {
5301 if let Some(mut snippet) = snippet {
5302 snippet.text = new_text.to_string();
5303 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5304 } else {
5305 this.buffer.update(cx, |buffer, cx| {
5306 let auto_indent = match completion.insert_text_mode {
5307 Some(InsertTextMode::AS_IS) => None,
5308 _ => this.autoindent_mode.clone(),
5309 };
5310 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5311 buffer.edit(edits, auto_indent, cx);
5312 });
5313 }
5314 for (buffer, edits) in linked_edits {
5315 buffer.update(cx, |buffer, cx| {
5316 let snapshot = buffer.snapshot();
5317 let edits = edits
5318 .into_iter()
5319 .map(|(range, text)| {
5320 use text::ToPoint as TP;
5321 let end_point = TP::to_point(&range.end, &snapshot);
5322 let start_point = TP::to_point(&range.start, &snapshot);
5323 (start_point..end_point, text)
5324 })
5325 .sorted_by_key(|(range, _)| range.start);
5326 buffer.edit(edits, None, cx);
5327 })
5328 }
5329
5330 this.refresh_inline_completion(true, false, window, cx);
5331 });
5332
5333 let show_new_completions_on_confirm = completion
5334 .confirm
5335 .as_ref()
5336 .map_or(false, |confirm| confirm(intent, window, cx));
5337 if show_new_completions_on_confirm {
5338 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5339 }
5340
5341 let provider = self.completion_provider.as_ref()?;
5342 drop(completion);
5343 let apply_edits = provider.apply_additional_edits_for_completion(
5344 buffer_handle,
5345 completions_menu.completions.clone(),
5346 candidate_id,
5347 true,
5348 cx,
5349 );
5350
5351 let editor_settings = EditorSettings::get_global(cx);
5352 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5353 // After the code completion is finished, users often want to know what signatures are needed.
5354 // so we should automatically call signature_help
5355 self.show_signature_help(&ShowSignatureHelp, window, cx);
5356 }
5357
5358 Some(cx.foreground_executor().spawn(async move {
5359 apply_edits.await?;
5360 Ok(())
5361 }))
5362 }
5363
5364 pub fn toggle_code_actions(
5365 &mut self,
5366 action: &ToggleCodeActions,
5367 window: &mut Window,
5368 cx: &mut Context<Self>,
5369 ) {
5370 let quick_launch = action.quick_launch;
5371 let mut context_menu = self.context_menu.borrow_mut();
5372 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5373 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5374 // Toggle if we're selecting the same one
5375 *context_menu = None;
5376 cx.notify();
5377 return;
5378 } else {
5379 // Otherwise, clear it and start a new one
5380 *context_menu = None;
5381 cx.notify();
5382 }
5383 }
5384 drop(context_menu);
5385 let snapshot = self.snapshot(window, cx);
5386 let deployed_from_indicator = action.deployed_from_indicator;
5387 let mut task = self.code_actions_task.take();
5388 let action = action.clone();
5389 cx.spawn_in(window, async move |editor, cx| {
5390 while let Some(prev_task) = task {
5391 prev_task.await.log_err();
5392 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5393 }
5394
5395 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5396 if editor.focus_handle.is_focused(window) {
5397 let multibuffer_point = action
5398 .deployed_from_indicator
5399 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5400 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5401 let (buffer, buffer_row) = snapshot
5402 .buffer_snapshot
5403 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5404 .and_then(|(buffer_snapshot, range)| {
5405 editor
5406 .buffer
5407 .read(cx)
5408 .buffer(buffer_snapshot.remote_id())
5409 .map(|buffer| (buffer, range.start.row))
5410 })?;
5411 let (_, code_actions) = editor
5412 .available_code_actions
5413 .clone()
5414 .and_then(|(location, code_actions)| {
5415 let snapshot = location.buffer.read(cx).snapshot();
5416 let point_range = location.range.to_point(&snapshot);
5417 let point_range = point_range.start.row..=point_range.end.row;
5418 if point_range.contains(&buffer_row) {
5419 Some((location, code_actions))
5420 } else {
5421 None
5422 }
5423 })
5424 .unzip();
5425 let buffer_id = buffer.read(cx).remote_id();
5426 let tasks = editor
5427 .tasks
5428 .get(&(buffer_id, buffer_row))
5429 .map(|t| Arc::new(t.to_owned()));
5430 if tasks.is_none() && code_actions.is_none() {
5431 return None;
5432 }
5433
5434 editor.completion_tasks.clear();
5435 editor.discard_inline_completion(false, cx);
5436 let task_context =
5437 tasks
5438 .as_ref()
5439 .zip(editor.project.clone())
5440 .map(|(tasks, project)| {
5441 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5442 });
5443
5444 Some(cx.spawn_in(window, async move |editor, cx| {
5445 let task_context = match task_context {
5446 Some(task_context) => task_context.await,
5447 None => None,
5448 };
5449 let resolved_tasks =
5450 tasks
5451 .zip(task_context.clone())
5452 .map(|(tasks, task_context)| ResolvedTasks {
5453 templates: tasks.resolve(&task_context).collect(),
5454 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5455 multibuffer_point.row,
5456 tasks.column,
5457 )),
5458 });
5459 let debug_scenarios = editor.update(cx, |editor, cx| {
5460 if cx.has_flag::<DebuggerFeatureFlag>() {
5461 maybe!({
5462 let project = editor.project.as_ref()?;
5463 let dap_store = project.read(cx).dap_store();
5464 let mut scenarios = vec![];
5465 let resolved_tasks = resolved_tasks.as_ref()?;
5466 let buffer = buffer.read(cx);
5467 let language = buffer.language()?;
5468 let file = buffer.file();
5469 let debug_adapter =
5470 language_settings(language.name().into(), file, cx)
5471 .debuggers
5472 .first()
5473 .map(SharedString::from)
5474 .or_else(|| {
5475 language
5476 .config()
5477 .debuggers
5478 .first()
5479 .map(SharedString::from)
5480 })?;
5481
5482 dap_store.update(cx, |dap_store, cx| {
5483 for (_, task) in &resolved_tasks.templates {
5484 if let Some(scenario) = dap_store
5485 .debug_scenario_for_build_task(
5486 task.original_task().clone(),
5487 debug_adapter.clone().into(),
5488 task.display_label().to_owned().into(),
5489 cx,
5490 )
5491 {
5492 scenarios.push(scenario);
5493 }
5494 }
5495 });
5496 Some(scenarios)
5497 })
5498 .unwrap_or_default()
5499 } else {
5500 vec![]
5501 }
5502 })?;
5503 let spawn_straight_away = quick_launch
5504 && resolved_tasks
5505 .as_ref()
5506 .map_or(false, |tasks| tasks.templates.len() == 1)
5507 && code_actions
5508 .as_ref()
5509 .map_or(true, |actions| actions.is_empty())
5510 && debug_scenarios.is_empty();
5511 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5512 *editor.context_menu.borrow_mut() =
5513 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5514 buffer,
5515 actions: CodeActionContents::new(
5516 resolved_tasks,
5517 code_actions,
5518 debug_scenarios,
5519 task_context.unwrap_or_default(),
5520 ),
5521 selected_item: Default::default(),
5522 scroll_handle: UniformListScrollHandle::default(),
5523 deployed_from_indicator,
5524 }));
5525 if spawn_straight_away {
5526 if let Some(task) = editor.confirm_code_action(
5527 &ConfirmCodeAction { item_ix: Some(0) },
5528 window,
5529 cx,
5530 ) {
5531 cx.notify();
5532 return task;
5533 }
5534 }
5535 cx.notify();
5536 Task::ready(Ok(()))
5537 }) {
5538 task.await
5539 } else {
5540 Ok(())
5541 }
5542 }))
5543 } else {
5544 Some(Task::ready(Ok(())))
5545 }
5546 })?;
5547 if let Some(task) = spawned_test_task {
5548 task.await?;
5549 }
5550
5551 Ok::<_, anyhow::Error>(())
5552 })
5553 .detach_and_log_err(cx);
5554 }
5555
5556 pub fn confirm_code_action(
5557 &mut self,
5558 action: &ConfirmCodeAction,
5559 window: &mut Window,
5560 cx: &mut Context<Self>,
5561 ) -> Option<Task<Result<()>>> {
5562 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5563
5564 let actions_menu =
5565 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5566 menu
5567 } else {
5568 return None;
5569 };
5570
5571 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5572 let action = actions_menu.actions.get(action_ix)?;
5573 let title = action.label();
5574 let buffer = actions_menu.buffer;
5575 let workspace = self.workspace()?;
5576
5577 match action {
5578 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5579 workspace.update(cx, |workspace, cx| {
5580 workspace.schedule_resolved_task(
5581 task_source_kind,
5582 resolved_task,
5583 false,
5584 window,
5585 cx,
5586 );
5587
5588 Some(Task::ready(Ok(())))
5589 })
5590 }
5591 CodeActionsItem::CodeAction {
5592 excerpt_id,
5593 action,
5594 provider,
5595 } => {
5596 let apply_code_action =
5597 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5598 let workspace = workspace.downgrade();
5599 Some(cx.spawn_in(window, async move |editor, cx| {
5600 let project_transaction = apply_code_action.await?;
5601 Self::open_project_transaction(
5602 &editor,
5603 workspace,
5604 project_transaction,
5605 title,
5606 cx,
5607 )
5608 .await
5609 }))
5610 }
5611 CodeActionsItem::DebugScenario(scenario) => {
5612 let context = actions_menu.actions.context.clone();
5613
5614 workspace.update(cx, |workspace, cx| {
5615 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5616 });
5617 Some(Task::ready(Ok(())))
5618 }
5619 }
5620 }
5621
5622 pub async fn open_project_transaction(
5623 this: &WeakEntity<Editor>,
5624 workspace: WeakEntity<Workspace>,
5625 transaction: ProjectTransaction,
5626 title: String,
5627 cx: &mut AsyncWindowContext,
5628 ) -> Result<()> {
5629 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5630 cx.update(|_, cx| {
5631 entries.sort_unstable_by_key(|(buffer, _)| {
5632 buffer.read(cx).file().map(|f| f.path().clone())
5633 });
5634 })?;
5635
5636 // If the project transaction's edits are all contained within this editor, then
5637 // avoid opening a new editor to display them.
5638
5639 if let Some((buffer, transaction)) = entries.first() {
5640 if entries.len() == 1 {
5641 let excerpt = this.update(cx, |editor, cx| {
5642 editor
5643 .buffer()
5644 .read(cx)
5645 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5646 })?;
5647 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5648 if excerpted_buffer == *buffer {
5649 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5650 let excerpt_range = excerpt_range.to_offset(buffer);
5651 buffer
5652 .edited_ranges_for_transaction::<usize>(transaction)
5653 .all(|range| {
5654 excerpt_range.start <= range.start
5655 && excerpt_range.end >= range.end
5656 })
5657 })?;
5658
5659 if all_edits_within_excerpt {
5660 return Ok(());
5661 }
5662 }
5663 }
5664 }
5665 } else {
5666 return Ok(());
5667 }
5668
5669 let mut ranges_to_highlight = Vec::new();
5670 let excerpt_buffer = cx.new(|cx| {
5671 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5672 for (buffer_handle, transaction) in &entries {
5673 let edited_ranges = buffer_handle
5674 .read(cx)
5675 .edited_ranges_for_transaction::<Point>(transaction)
5676 .collect::<Vec<_>>();
5677 let (ranges, _) = multibuffer.set_excerpts_for_path(
5678 PathKey::for_buffer(buffer_handle, cx),
5679 buffer_handle.clone(),
5680 edited_ranges,
5681 DEFAULT_MULTIBUFFER_CONTEXT,
5682 cx,
5683 );
5684
5685 ranges_to_highlight.extend(ranges);
5686 }
5687 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5688 multibuffer
5689 })?;
5690
5691 workspace.update_in(cx, |workspace, window, cx| {
5692 let project = workspace.project().clone();
5693 let editor =
5694 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5695 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5696 editor.update(cx, |editor, cx| {
5697 editor.highlight_background::<Self>(
5698 &ranges_to_highlight,
5699 |theme| theme.editor_highlighted_line_background,
5700 cx,
5701 );
5702 });
5703 })?;
5704
5705 Ok(())
5706 }
5707
5708 pub fn clear_code_action_providers(&mut self) {
5709 self.code_action_providers.clear();
5710 self.available_code_actions.take();
5711 }
5712
5713 pub fn add_code_action_provider(
5714 &mut self,
5715 provider: Rc<dyn CodeActionProvider>,
5716 window: &mut Window,
5717 cx: &mut Context<Self>,
5718 ) {
5719 if self
5720 .code_action_providers
5721 .iter()
5722 .any(|existing_provider| existing_provider.id() == provider.id())
5723 {
5724 return;
5725 }
5726
5727 self.code_action_providers.push(provider);
5728 self.refresh_code_actions(window, cx);
5729 }
5730
5731 pub fn remove_code_action_provider(
5732 &mut self,
5733 id: Arc<str>,
5734 window: &mut Window,
5735 cx: &mut Context<Self>,
5736 ) {
5737 self.code_action_providers
5738 .retain(|provider| provider.id() != id);
5739 self.refresh_code_actions(window, cx);
5740 }
5741
5742 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5743 let newest_selection = self.selections.newest_anchor().clone();
5744 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5745 let buffer = self.buffer.read(cx);
5746 if newest_selection.head().diff_base_anchor.is_some() {
5747 return None;
5748 }
5749 let (start_buffer, start) =
5750 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5751 let (end_buffer, end) =
5752 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5753 if start_buffer != end_buffer {
5754 return None;
5755 }
5756
5757 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5758 cx.background_executor()
5759 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5760 .await;
5761
5762 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5763 let providers = this.code_action_providers.clone();
5764 let tasks = this
5765 .code_action_providers
5766 .iter()
5767 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5768 .collect::<Vec<_>>();
5769 (providers, tasks)
5770 })?;
5771
5772 let mut actions = Vec::new();
5773 for (provider, provider_actions) in
5774 providers.into_iter().zip(future::join_all(tasks).await)
5775 {
5776 if let Some(provider_actions) = provider_actions.log_err() {
5777 actions.extend(provider_actions.into_iter().map(|action| {
5778 AvailableCodeAction {
5779 excerpt_id: newest_selection.start.excerpt_id,
5780 action,
5781 provider: provider.clone(),
5782 }
5783 }));
5784 }
5785 }
5786
5787 this.update(cx, |this, cx| {
5788 this.available_code_actions = if actions.is_empty() {
5789 None
5790 } else {
5791 Some((
5792 Location {
5793 buffer: start_buffer,
5794 range: start..end,
5795 },
5796 actions.into(),
5797 ))
5798 };
5799 cx.notify();
5800 })
5801 }));
5802 None
5803 }
5804
5805 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5806 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5807 self.show_git_blame_inline = false;
5808
5809 self.show_git_blame_inline_delay_task =
5810 Some(cx.spawn_in(window, async move |this, cx| {
5811 cx.background_executor().timer(delay).await;
5812
5813 this.update(cx, |this, cx| {
5814 this.show_git_blame_inline = true;
5815 cx.notify();
5816 })
5817 .log_err();
5818 }));
5819 }
5820 }
5821
5822 fn show_blame_popover(
5823 &mut self,
5824 blame_entry: &BlameEntry,
5825 position: gpui::Point<Pixels>,
5826 cx: &mut Context<Self>,
5827 ) {
5828 if let Some(state) = &mut self.inline_blame_popover {
5829 state.hide_task.take();
5830 cx.notify();
5831 } else {
5832 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5833 let show_task = cx.spawn(async move |editor, cx| {
5834 cx.background_executor()
5835 .timer(std::time::Duration::from_millis(delay))
5836 .await;
5837 editor
5838 .update(cx, |editor, cx| {
5839 if let Some(state) = &mut editor.inline_blame_popover {
5840 state.show_task = None;
5841 cx.notify();
5842 }
5843 })
5844 .ok();
5845 });
5846 let Some(blame) = self.blame.as_ref() else {
5847 return;
5848 };
5849 let blame = blame.read(cx);
5850 let details = blame.details_for_entry(&blame_entry);
5851 let markdown = cx.new(|cx| {
5852 Markdown::new(
5853 details
5854 .as_ref()
5855 .map(|message| message.message.clone())
5856 .unwrap_or_default(),
5857 None,
5858 None,
5859 cx,
5860 )
5861 });
5862 self.inline_blame_popover = Some(InlineBlamePopover {
5863 position,
5864 show_task: Some(show_task),
5865 hide_task: None,
5866 popover_bounds: None,
5867 popover_state: InlineBlamePopoverState {
5868 scroll_handle: ScrollHandle::new(),
5869 commit_message: details,
5870 markdown,
5871 },
5872 });
5873 }
5874 }
5875
5876 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5877 if let Some(state) = &mut self.inline_blame_popover {
5878 if state.show_task.is_some() {
5879 self.inline_blame_popover.take();
5880 cx.notify();
5881 } else {
5882 let hide_task = cx.spawn(async move |editor, cx| {
5883 cx.background_executor()
5884 .timer(std::time::Duration::from_millis(100))
5885 .await;
5886 editor
5887 .update(cx, |editor, cx| {
5888 editor.inline_blame_popover.take();
5889 cx.notify();
5890 })
5891 .ok();
5892 });
5893 state.hide_task = Some(hide_task);
5894 }
5895 }
5896 }
5897
5898 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5899 if self.pending_rename.is_some() {
5900 return None;
5901 }
5902
5903 let provider = self.semantics_provider.clone()?;
5904 let buffer = self.buffer.read(cx);
5905 let newest_selection = self.selections.newest_anchor().clone();
5906 let cursor_position = newest_selection.head();
5907 let (cursor_buffer, cursor_buffer_position) =
5908 buffer.text_anchor_for_position(cursor_position, cx)?;
5909 let (tail_buffer, tail_buffer_position) =
5910 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5911 if cursor_buffer != tail_buffer {
5912 return None;
5913 }
5914
5915 let snapshot = cursor_buffer.read(cx).snapshot();
5916 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5917 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5918 if start_word_range != end_word_range {
5919 self.document_highlights_task.take();
5920 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5921 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5922 return None;
5923 }
5924
5925 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5926 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5927 cx.background_executor()
5928 .timer(Duration::from_millis(debounce))
5929 .await;
5930
5931 let highlights = if let Some(highlights) = cx
5932 .update(|cx| {
5933 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5934 })
5935 .ok()
5936 .flatten()
5937 {
5938 highlights.await.log_err()
5939 } else {
5940 None
5941 };
5942
5943 if let Some(highlights) = highlights {
5944 this.update(cx, |this, cx| {
5945 if this.pending_rename.is_some() {
5946 return;
5947 }
5948
5949 let buffer_id = cursor_position.buffer_id;
5950 let buffer = this.buffer.read(cx);
5951 if !buffer
5952 .text_anchor_for_position(cursor_position, cx)
5953 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5954 {
5955 return;
5956 }
5957
5958 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5959 let mut write_ranges = Vec::new();
5960 let mut read_ranges = Vec::new();
5961 for highlight in highlights {
5962 for (excerpt_id, excerpt_range) in
5963 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5964 {
5965 let start = highlight
5966 .range
5967 .start
5968 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5969 let end = highlight
5970 .range
5971 .end
5972 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5973 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5974 continue;
5975 }
5976
5977 let range = Anchor {
5978 buffer_id,
5979 excerpt_id,
5980 text_anchor: start,
5981 diff_base_anchor: None,
5982 }..Anchor {
5983 buffer_id,
5984 excerpt_id,
5985 text_anchor: end,
5986 diff_base_anchor: None,
5987 };
5988 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5989 write_ranges.push(range);
5990 } else {
5991 read_ranges.push(range);
5992 }
5993 }
5994 }
5995
5996 this.highlight_background::<DocumentHighlightRead>(
5997 &read_ranges,
5998 |theme| theme.editor_document_highlight_read_background,
5999 cx,
6000 );
6001 this.highlight_background::<DocumentHighlightWrite>(
6002 &write_ranges,
6003 |theme| theme.editor_document_highlight_write_background,
6004 cx,
6005 );
6006 cx.notify();
6007 })
6008 .log_err();
6009 }
6010 }));
6011 None
6012 }
6013
6014 fn prepare_highlight_query_from_selection(
6015 &mut self,
6016 cx: &mut Context<Editor>,
6017 ) -> Option<(String, Range<Anchor>)> {
6018 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6019 return None;
6020 }
6021 if !EditorSettings::get_global(cx).selection_highlight {
6022 return None;
6023 }
6024 if self.selections.count() != 1 || self.selections.line_mode {
6025 return None;
6026 }
6027 let selection = self.selections.newest::<Point>(cx);
6028 if selection.is_empty() || selection.start.row != selection.end.row {
6029 return None;
6030 }
6031 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6032 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6033 let query = multi_buffer_snapshot
6034 .text_for_range(selection_anchor_range.clone())
6035 .collect::<String>();
6036 if query.trim().is_empty() {
6037 return None;
6038 }
6039 Some((query, selection_anchor_range))
6040 }
6041
6042 fn update_selection_occurrence_highlights(
6043 &mut self,
6044 query_text: String,
6045 query_range: Range<Anchor>,
6046 multi_buffer_range_to_query: Range<Point>,
6047 use_debounce: bool,
6048 window: &mut Window,
6049 cx: &mut Context<Editor>,
6050 ) -> Task<()> {
6051 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6052 cx.spawn_in(window, async move |editor, cx| {
6053 if use_debounce {
6054 cx.background_executor()
6055 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6056 .await;
6057 }
6058 let match_task = cx.background_spawn(async move {
6059 let buffer_ranges = multi_buffer_snapshot
6060 .range_to_buffer_ranges(multi_buffer_range_to_query)
6061 .into_iter()
6062 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6063 let mut match_ranges = Vec::new();
6064 let Ok(regex) = project::search::SearchQuery::text(
6065 query_text.clone(),
6066 false,
6067 false,
6068 false,
6069 Default::default(),
6070 Default::default(),
6071 false,
6072 None,
6073 ) else {
6074 return Vec::default();
6075 };
6076 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6077 match_ranges.extend(
6078 regex
6079 .search(&buffer_snapshot, Some(search_range.clone()))
6080 .await
6081 .into_iter()
6082 .filter_map(|match_range| {
6083 let match_start = buffer_snapshot
6084 .anchor_after(search_range.start + match_range.start);
6085 let match_end = buffer_snapshot
6086 .anchor_before(search_range.start + match_range.end);
6087 let match_anchor_range = Anchor::range_in_buffer(
6088 excerpt_id,
6089 buffer_snapshot.remote_id(),
6090 match_start..match_end,
6091 );
6092 (match_anchor_range != query_range).then_some(match_anchor_range)
6093 }),
6094 );
6095 }
6096 match_ranges
6097 });
6098 let match_ranges = match_task.await;
6099 editor
6100 .update_in(cx, |editor, _, cx| {
6101 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6102 if !match_ranges.is_empty() {
6103 editor.highlight_background::<SelectedTextHighlight>(
6104 &match_ranges,
6105 |theme| theme.editor_document_highlight_bracket_background,
6106 cx,
6107 )
6108 }
6109 })
6110 .log_err();
6111 })
6112 }
6113
6114 fn refresh_selected_text_highlights(
6115 &mut self,
6116 on_buffer_edit: bool,
6117 window: &mut Window,
6118 cx: &mut Context<Editor>,
6119 ) {
6120 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6121 else {
6122 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6123 self.quick_selection_highlight_task.take();
6124 self.debounced_selection_highlight_task.take();
6125 return;
6126 };
6127 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6128 if on_buffer_edit
6129 || self
6130 .quick_selection_highlight_task
6131 .as_ref()
6132 .map_or(true, |(prev_anchor_range, _)| {
6133 prev_anchor_range != &query_range
6134 })
6135 {
6136 let multi_buffer_visible_start = self
6137 .scroll_manager
6138 .anchor()
6139 .anchor
6140 .to_point(&multi_buffer_snapshot);
6141 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6142 multi_buffer_visible_start
6143 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6144 Bias::Left,
6145 );
6146 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6147 self.quick_selection_highlight_task = Some((
6148 query_range.clone(),
6149 self.update_selection_occurrence_highlights(
6150 query_text.clone(),
6151 query_range.clone(),
6152 multi_buffer_visible_range,
6153 false,
6154 window,
6155 cx,
6156 ),
6157 ));
6158 }
6159 if on_buffer_edit
6160 || self
6161 .debounced_selection_highlight_task
6162 .as_ref()
6163 .map_or(true, |(prev_anchor_range, _)| {
6164 prev_anchor_range != &query_range
6165 })
6166 {
6167 let multi_buffer_start = multi_buffer_snapshot
6168 .anchor_before(0)
6169 .to_point(&multi_buffer_snapshot);
6170 let multi_buffer_end = multi_buffer_snapshot
6171 .anchor_after(multi_buffer_snapshot.len())
6172 .to_point(&multi_buffer_snapshot);
6173 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6174 self.debounced_selection_highlight_task = Some((
6175 query_range.clone(),
6176 self.update_selection_occurrence_highlights(
6177 query_text,
6178 query_range,
6179 multi_buffer_full_range,
6180 true,
6181 window,
6182 cx,
6183 ),
6184 ));
6185 }
6186 }
6187
6188 pub fn refresh_inline_completion(
6189 &mut self,
6190 debounce: bool,
6191 user_requested: bool,
6192 window: &mut Window,
6193 cx: &mut Context<Self>,
6194 ) -> Option<()> {
6195 let provider = self.edit_prediction_provider()?;
6196 let cursor = self.selections.newest_anchor().head();
6197 let (buffer, cursor_buffer_position) =
6198 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6199
6200 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6201 self.discard_inline_completion(false, cx);
6202 return None;
6203 }
6204
6205 if !user_requested
6206 && (!self.should_show_edit_predictions()
6207 || !self.is_focused(window)
6208 || buffer.read(cx).is_empty())
6209 {
6210 self.discard_inline_completion(false, cx);
6211 return None;
6212 }
6213
6214 self.update_visible_inline_completion(window, cx);
6215 provider.refresh(
6216 self.project.clone(),
6217 buffer,
6218 cursor_buffer_position,
6219 debounce,
6220 cx,
6221 );
6222 Some(())
6223 }
6224
6225 fn show_edit_predictions_in_menu(&self) -> bool {
6226 match self.edit_prediction_settings {
6227 EditPredictionSettings::Disabled => false,
6228 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6229 }
6230 }
6231
6232 pub fn edit_predictions_enabled(&self) -> bool {
6233 match self.edit_prediction_settings {
6234 EditPredictionSettings::Disabled => false,
6235 EditPredictionSettings::Enabled { .. } => true,
6236 }
6237 }
6238
6239 fn edit_prediction_requires_modifier(&self) -> bool {
6240 match self.edit_prediction_settings {
6241 EditPredictionSettings::Disabled => false,
6242 EditPredictionSettings::Enabled {
6243 preview_requires_modifier,
6244 ..
6245 } => preview_requires_modifier,
6246 }
6247 }
6248
6249 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6250 if self.edit_prediction_provider.is_none() {
6251 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6252 } else {
6253 let selection = self.selections.newest_anchor();
6254 let cursor = selection.head();
6255
6256 if let Some((buffer, cursor_buffer_position)) =
6257 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6258 {
6259 self.edit_prediction_settings =
6260 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6261 }
6262 }
6263 }
6264
6265 fn edit_prediction_settings_at_position(
6266 &self,
6267 buffer: &Entity<Buffer>,
6268 buffer_position: language::Anchor,
6269 cx: &App,
6270 ) -> EditPredictionSettings {
6271 if !self.mode.is_full()
6272 || !self.show_inline_completions_override.unwrap_or(true)
6273 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6274 {
6275 return EditPredictionSettings::Disabled;
6276 }
6277
6278 let buffer = buffer.read(cx);
6279
6280 let file = buffer.file();
6281
6282 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6283 return EditPredictionSettings::Disabled;
6284 };
6285
6286 let by_provider = matches!(
6287 self.menu_inline_completions_policy,
6288 MenuInlineCompletionsPolicy::ByProvider
6289 );
6290
6291 let show_in_menu = by_provider
6292 && self
6293 .edit_prediction_provider
6294 .as_ref()
6295 .map_or(false, |provider| {
6296 provider.provider.show_completions_in_menu()
6297 });
6298
6299 let preview_requires_modifier =
6300 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6301
6302 EditPredictionSettings::Enabled {
6303 show_in_menu,
6304 preview_requires_modifier,
6305 }
6306 }
6307
6308 fn should_show_edit_predictions(&self) -> bool {
6309 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6310 }
6311
6312 pub fn edit_prediction_preview_is_active(&self) -> bool {
6313 matches!(
6314 self.edit_prediction_preview,
6315 EditPredictionPreview::Active { .. }
6316 )
6317 }
6318
6319 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6320 let cursor = self.selections.newest_anchor().head();
6321 if let Some((buffer, cursor_position)) =
6322 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6323 {
6324 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6325 } else {
6326 false
6327 }
6328 }
6329
6330 pub fn supports_minimap(&self, cx: &App) -> bool {
6331 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6332 }
6333
6334 fn edit_predictions_enabled_in_buffer(
6335 &self,
6336 buffer: &Entity<Buffer>,
6337 buffer_position: language::Anchor,
6338 cx: &App,
6339 ) -> bool {
6340 maybe!({
6341 if self.read_only(cx) {
6342 return Some(false);
6343 }
6344 let provider = self.edit_prediction_provider()?;
6345 if !provider.is_enabled(&buffer, buffer_position, cx) {
6346 return Some(false);
6347 }
6348 let buffer = buffer.read(cx);
6349 let Some(file) = buffer.file() else {
6350 return Some(true);
6351 };
6352 let settings = all_language_settings(Some(file), cx);
6353 Some(settings.edit_predictions_enabled_for_file(file, cx))
6354 })
6355 .unwrap_or(false)
6356 }
6357
6358 fn cycle_inline_completion(
6359 &mut self,
6360 direction: Direction,
6361 window: &mut Window,
6362 cx: &mut Context<Self>,
6363 ) -> Option<()> {
6364 let provider = self.edit_prediction_provider()?;
6365 let cursor = self.selections.newest_anchor().head();
6366 let (buffer, cursor_buffer_position) =
6367 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6368 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6369 return None;
6370 }
6371
6372 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6373 self.update_visible_inline_completion(window, cx);
6374
6375 Some(())
6376 }
6377
6378 pub fn show_inline_completion(
6379 &mut self,
6380 _: &ShowEditPrediction,
6381 window: &mut Window,
6382 cx: &mut Context<Self>,
6383 ) {
6384 if !self.has_active_inline_completion() {
6385 self.refresh_inline_completion(false, true, window, cx);
6386 return;
6387 }
6388
6389 self.update_visible_inline_completion(window, cx);
6390 }
6391
6392 pub fn display_cursor_names(
6393 &mut self,
6394 _: &DisplayCursorNames,
6395 window: &mut Window,
6396 cx: &mut Context<Self>,
6397 ) {
6398 self.show_cursor_names(window, cx);
6399 }
6400
6401 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6402 self.show_cursor_names = true;
6403 cx.notify();
6404 cx.spawn_in(window, async move |this, cx| {
6405 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6406 this.update(cx, |this, cx| {
6407 this.show_cursor_names = false;
6408 cx.notify()
6409 })
6410 .ok()
6411 })
6412 .detach();
6413 }
6414
6415 pub fn next_edit_prediction(
6416 &mut self,
6417 _: &NextEditPrediction,
6418 window: &mut Window,
6419 cx: &mut Context<Self>,
6420 ) {
6421 if self.has_active_inline_completion() {
6422 self.cycle_inline_completion(Direction::Next, window, cx);
6423 } else {
6424 let is_copilot_disabled = self
6425 .refresh_inline_completion(false, true, window, cx)
6426 .is_none();
6427 if is_copilot_disabled {
6428 cx.propagate();
6429 }
6430 }
6431 }
6432
6433 pub fn previous_edit_prediction(
6434 &mut self,
6435 _: &PreviousEditPrediction,
6436 window: &mut Window,
6437 cx: &mut Context<Self>,
6438 ) {
6439 if self.has_active_inline_completion() {
6440 self.cycle_inline_completion(Direction::Prev, window, cx);
6441 } else {
6442 let is_copilot_disabled = self
6443 .refresh_inline_completion(false, true, window, cx)
6444 .is_none();
6445 if is_copilot_disabled {
6446 cx.propagate();
6447 }
6448 }
6449 }
6450
6451 pub fn accept_edit_prediction(
6452 &mut self,
6453 _: &AcceptEditPrediction,
6454 window: &mut Window,
6455 cx: &mut Context<Self>,
6456 ) {
6457 if self.show_edit_predictions_in_menu() {
6458 self.hide_context_menu(window, cx);
6459 }
6460
6461 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6462 return;
6463 };
6464
6465 self.report_inline_completion_event(
6466 active_inline_completion.completion_id.clone(),
6467 true,
6468 cx,
6469 );
6470
6471 match &active_inline_completion.completion {
6472 InlineCompletion::Move { target, .. } => {
6473 let target = *target;
6474
6475 if let Some(position_map) = &self.last_position_map {
6476 if position_map
6477 .visible_row_range
6478 .contains(&target.to_display_point(&position_map.snapshot).row())
6479 || !self.edit_prediction_requires_modifier()
6480 {
6481 self.unfold_ranges(&[target..target], true, false, cx);
6482 // Note that this is also done in vim's handler of the Tab action.
6483 self.change_selections(
6484 Some(Autoscroll::newest()),
6485 window,
6486 cx,
6487 |selections| {
6488 selections.select_anchor_ranges([target..target]);
6489 },
6490 );
6491 self.clear_row_highlights::<EditPredictionPreview>();
6492
6493 self.edit_prediction_preview
6494 .set_previous_scroll_position(None);
6495 } else {
6496 self.edit_prediction_preview
6497 .set_previous_scroll_position(Some(
6498 position_map.snapshot.scroll_anchor,
6499 ));
6500
6501 self.highlight_rows::<EditPredictionPreview>(
6502 target..target,
6503 cx.theme().colors().editor_highlighted_line_background,
6504 RowHighlightOptions {
6505 autoscroll: true,
6506 ..Default::default()
6507 },
6508 cx,
6509 );
6510 self.request_autoscroll(Autoscroll::fit(), cx);
6511 }
6512 }
6513 }
6514 InlineCompletion::Edit { edits, .. } => {
6515 if let Some(provider) = self.edit_prediction_provider() {
6516 provider.accept(cx);
6517 }
6518
6519 let snapshot = self.buffer.read(cx).snapshot(cx);
6520 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6521
6522 self.buffer.update(cx, |buffer, cx| {
6523 buffer.edit(edits.iter().cloned(), None, cx)
6524 });
6525
6526 self.change_selections(None, window, cx, |s| {
6527 s.select_anchor_ranges([last_edit_end..last_edit_end])
6528 });
6529
6530 self.update_visible_inline_completion(window, cx);
6531 if self.active_inline_completion.is_none() {
6532 self.refresh_inline_completion(true, true, window, cx);
6533 }
6534
6535 cx.notify();
6536 }
6537 }
6538
6539 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6540 }
6541
6542 pub fn accept_partial_inline_completion(
6543 &mut self,
6544 _: &AcceptPartialEditPrediction,
6545 window: &mut Window,
6546 cx: &mut Context<Self>,
6547 ) {
6548 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6549 return;
6550 };
6551 if self.selections.count() != 1 {
6552 return;
6553 }
6554
6555 self.report_inline_completion_event(
6556 active_inline_completion.completion_id.clone(),
6557 true,
6558 cx,
6559 );
6560
6561 match &active_inline_completion.completion {
6562 InlineCompletion::Move { target, .. } => {
6563 let target = *target;
6564 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6565 selections.select_anchor_ranges([target..target]);
6566 });
6567 }
6568 InlineCompletion::Edit { edits, .. } => {
6569 // Find an insertion that starts at the cursor position.
6570 let snapshot = self.buffer.read(cx).snapshot(cx);
6571 let cursor_offset = self.selections.newest::<usize>(cx).head();
6572 let insertion = edits.iter().find_map(|(range, text)| {
6573 let range = range.to_offset(&snapshot);
6574 if range.is_empty() && range.start == cursor_offset {
6575 Some(text)
6576 } else {
6577 None
6578 }
6579 });
6580
6581 if let Some(text) = insertion {
6582 let mut partial_completion = text
6583 .chars()
6584 .by_ref()
6585 .take_while(|c| c.is_alphabetic())
6586 .collect::<String>();
6587 if partial_completion.is_empty() {
6588 partial_completion = text
6589 .chars()
6590 .by_ref()
6591 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6592 .collect::<String>();
6593 }
6594
6595 cx.emit(EditorEvent::InputHandled {
6596 utf16_range_to_replace: None,
6597 text: partial_completion.clone().into(),
6598 });
6599
6600 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6601
6602 self.refresh_inline_completion(true, true, window, cx);
6603 cx.notify();
6604 } else {
6605 self.accept_edit_prediction(&Default::default(), window, cx);
6606 }
6607 }
6608 }
6609 }
6610
6611 fn discard_inline_completion(
6612 &mut self,
6613 should_report_inline_completion_event: bool,
6614 cx: &mut Context<Self>,
6615 ) -> bool {
6616 if should_report_inline_completion_event {
6617 let completion_id = self
6618 .active_inline_completion
6619 .as_ref()
6620 .and_then(|active_completion| active_completion.completion_id.clone());
6621
6622 self.report_inline_completion_event(completion_id, false, cx);
6623 }
6624
6625 if let Some(provider) = self.edit_prediction_provider() {
6626 provider.discard(cx);
6627 }
6628
6629 self.take_active_inline_completion(cx)
6630 }
6631
6632 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6633 let Some(provider) = self.edit_prediction_provider() else {
6634 return;
6635 };
6636
6637 let Some((_, buffer, _)) = self
6638 .buffer
6639 .read(cx)
6640 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6641 else {
6642 return;
6643 };
6644
6645 let extension = buffer
6646 .read(cx)
6647 .file()
6648 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6649
6650 let event_type = match accepted {
6651 true => "Edit Prediction Accepted",
6652 false => "Edit Prediction Discarded",
6653 };
6654 telemetry::event!(
6655 event_type,
6656 provider = provider.name(),
6657 prediction_id = id,
6658 suggestion_accepted = accepted,
6659 file_extension = extension,
6660 );
6661 }
6662
6663 pub fn has_active_inline_completion(&self) -> bool {
6664 self.active_inline_completion.is_some()
6665 }
6666
6667 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6668 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6669 return false;
6670 };
6671
6672 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6673 self.clear_highlights::<InlineCompletionHighlight>(cx);
6674 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6675 true
6676 }
6677
6678 /// Returns true when we're displaying the edit prediction popover below the cursor
6679 /// like we are not previewing and the LSP autocomplete menu is visible
6680 /// or we are in `when_holding_modifier` mode.
6681 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6682 if self.edit_prediction_preview_is_active()
6683 || !self.show_edit_predictions_in_menu()
6684 || !self.edit_predictions_enabled()
6685 {
6686 return false;
6687 }
6688
6689 if self.has_visible_completions_menu() {
6690 return true;
6691 }
6692
6693 has_completion && self.edit_prediction_requires_modifier()
6694 }
6695
6696 fn handle_modifiers_changed(
6697 &mut self,
6698 modifiers: Modifiers,
6699 position_map: &PositionMap,
6700 window: &mut Window,
6701 cx: &mut Context<Self>,
6702 ) {
6703 if self.show_edit_predictions_in_menu() {
6704 self.update_edit_prediction_preview(&modifiers, window, cx);
6705 }
6706
6707 self.update_selection_mode(&modifiers, position_map, window, cx);
6708
6709 let mouse_position = window.mouse_position();
6710 if !position_map.text_hitbox.is_hovered(window) {
6711 return;
6712 }
6713
6714 self.update_hovered_link(
6715 position_map.point_for_position(mouse_position),
6716 &position_map.snapshot,
6717 modifiers,
6718 window,
6719 cx,
6720 )
6721 }
6722
6723 fn update_selection_mode(
6724 &mut self,
6725 modifiers: &Modifiers,
6726 position_map: &PositionMap,
6727 window: &mut Window,
6728 cx: &mut Context<Self>,
6729 ) {
6730 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6731 return;
6732 }
6733
6734 let mouse_position = window.mouse_position();
6735 let point_for_position = position_map.point_for_position(mouse_position);
6736 let position = point_for_position.previous_valid;
6737
6738 self.select(
6739 SelectPhase::BeginColumnar {
6740 position,
6741 reset: false,
6742 goal_column: point_for_position.exact_unclipped.column(),
6743 },
6744 window,
6745 cx,
6746 );
6747 }
6748
6749 fn update_edit_prediction_preview(
6750 &mut self,
6751 modifiers: &Modifiers,
6752 window: &mut Window,
6753 cx: &mut Context<Self>,
6754 ) {
6755 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6756 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6757 return;
6758 };
6759
6760 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6761 if matches!(
6762 self.edit_prediction_preview,
6763 EditPredictionPreview::Inactive { .. }
6764 ) {
6765 self.edit_prediction_preview = EditPredictionPreview::Active {
6766 previous_scroll_position: None,
6767 since: Instant::now(),
6768 };
6769
6770 self.update_visible_inline_completion(window, cx);
6771 cx.notify();
6772 }
6773 } else if let EditPredictionPreview::Active {
6774 previous_scroll_position,
6775 since,
6776 } = self.edit_prediction_preview
6777 {
6778 if let (Some(previous_scroll_position), Some(position_map)) =
6779 (previous_scroll_position, self.last_position_map.as_ref())
6780 {
6781 self.set_scroll_position(
6782 previous_scroll_position
6783 .scroll_position(&position_map.snapshot.display_snapshot),
6784 window,
6785 cx,
6786 );
6787 }
6788
6789 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6790 released_too_fast: since.elapsed() < Duration::from_millis(200),
6791 };
6792 self.clear_row_highlights::<EditPredictionPreview>();
6793 self.update_visible_inline_completion(window, cx);
6794 cx.notify();
6795 }
6796 }
6797
6798 fn update_visible_inline_completion(
6799 &mut self,
6800 _window: &mut Window,
6801 cx: &mut Context<Self>,
6802 ) -> Option<()> {
6803 let selection = self.selections.newest_anchor();
6804 let cursor = selection.head();
6805 let multibuffer = self.buffer.read(cx).snapshot(cx);
6806 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6807 let excerpt_id = cursor.excerpt_id;
6808
6809 let show_in_menu = self.show_edit_predictions_in_menu();
6810 let completions_menu_has_precedence = !show_in_menu
6811 && (self.context_menu.borrow().is_some()
6812 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6813
6814 if completions_menu_has_precedence
6815 || !offset_selection.is_empty()
6816 || self
6817 .active_inline_completion
6818 .as_ref()
6819 .map_or(false, |completion| {
6820 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6821 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6822 !invalidation_range.contains(&offset_selection.head())
6823 })
6824 {
6825 self.discard_inline_completion(false, cx);
6826 return None;
6827 }
6828
6829 self.take_active_inline_completion(cx);
6830 let Some(provider) = self.edit_prediction_provider() else {
6831 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6832 return None;
6833 };
6834
6835 let (buffer, cursor_buffer_position) =
6836 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6837
6838 self.edit_prediction_settings =
6839 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6840
6841 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6842
6843 if self.edit_prediction_indent_conflict {
6844 let cursor_point = cursor.to_point(&multibuffer);
6845
6846 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6847
6848 if let Some((_, indent)) = indents.iter().next() {
6849 if indent.len == cursor_point.column {
6850 self.edit_prediction_indent_conflict = false;
6851 }
6852 }
6853 }
6854
6855 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6856 let edits = inline_completion
6857 .edits
6858 .into_iter()
6859 .flat_map(|(range, new_text)| {
6860 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6861 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6862 Some((start..end, new_text))
6863 })
6864 .collect::<Vec<_>>();
6865 if edits.is_empty() {
6866 return None;
6867 }
6868
6869 let first_edit_start = edits.first().unwrap().0.start;
6870 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6871 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6872
6873 let last_edit_end = edits.last().unwrap().0.end;
6874 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6875 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6876
6877 let cursor_row = cursor.to_point(&multibuffer).row;
6878
6879 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6880
6881 let mut inlay_ids = Vec::new();
6882 let invalidation_row_range;
6883 let move_invalidation_row_range = if cursor_row < edit_start_row {
6884 Some(cursor_row..edit_end_row)
6885 } else if cursor_row > edit_end_row {
6886 Some(edit_start_row..cursor_row)
6887 } else {
6888 None
6889 };
6890 let is_move =
6891 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6892 let completion = if is_move {
6893 invalidation_row_range =
6894 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6895 let target = first_edit_start;
6896 InlineCompletion::Move { target, snapshot }
6897 } else {
6898 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6899 && !self.inline_completions_hidden_for_vim_mode;
6900
6901 if show_completions_in_buffer {
6902 if edits
6903 .iter()
6904 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6905 {
6906 let mut inlays = Vec::new();
6907 for (range, new_text) in &edits {
6908 let inlay = Inlay::inline_completion(
6909 post_inc(&mut self.next_inlay_id),
6910 range.start,
6911 new_text.as_str(),
6912 );
6913 inlay_ids.push(inlay.id);
6914 inlays.push(inlay);
6915 }
6916
6917 self.splice_inlays(&[], inlays, cx);
6918 } else {
6919 let background_color = cx.theme().status().deleted_background;
6920 self.highlight_text::<InlineCompletionHighlight>(
6921 edits.iter().map(|(range, _)| range.clone()).collect(),
6922 HighlightStyle {
6923 background_color: Some(background_color),
6924 ..Default::default()
6925 },
6926 cx,
6927 );
6928 }
6929 }
6930
6931 invalidation_row_range = edit_start_row..edit_end_row;
6932
6933 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6934 if provider.show_tab_accept_marker() {
6935 EditDisplayMode::TabAccept
6936 } else {
6937 EditDisplayMode::Inline
6938 }
6939 } else {
6940 EditDisplayMode::DiffPopover
6941 };
6942
6943 InlineCompletion::Edit {
6944 edits,
6945 edit_preview: inline_completion.edit_preview,
6946 display_mode,
6947 snapshot,
6948 }
6949 };
6950
6951 let invalidation_range = multibuffer
6952 .anchor_before(Point::new(invalidation_row_range.start, 0))
6953 ..multibuffer.anchor_after(Point::new(
6954 invalidation_row_range.end,
6955 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6956 ));
6957
6958 self.stale_inline_completion_in_menu = None;
6959 self.active_inline_completion = Some(InlineCompletionState {
6960 inlay_ids,
6961 completion,
6962 completion_id: inline_completion.id,
6963 invalidation_range,
6964 });
6965
6966 cx.notify();
6967
6968 Some(())
6969 }
6970
6971 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6972 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6973 }
6974
6975 fn clear_tasks(&mut self) {
6976 self.tasks.clear()
6977 }
6978
6979 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6980 if self.tasks.insert(key, value).is_some() {
6981 // This case should hopefully be rare, but just in case...
6982 log::error!(
6983 "multiple different run targets found on a single line, only the last target will be rendered"
6984 )
6985 }
6986 }
6987
6988 /// Get all display points of breakpoints that will be rendered within editor
6989 ///
6990 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6991 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6992 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6993 fn active_breakpoints(
6994 &self,
6995 range: Range<DisplayRow>,
6996 window: &mut Window,
6997 cx: &mut Context<Self>,
6998 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6999 let mut breakpoint_display_points = HashMap::default();
7000
7001 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7002 return breakpoint_display_points;
7003 };
7004
7005 let snapshot = self.snapshot(window, cx);
7006
7007 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7008 let Some(project) = self.project.as_ref() else {
7009 return breakpoint_display_points;
7010 };
7011
7012 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7013 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7014
7015 for (buffer_snapshot, range, excerpt_id) in
7016 multi_buffer_snapshot.range_to_buffer_ranges(range)
7017 {
7018 let Some(buffer) = project.read_with(cx, |this, cx| {
7019 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7020 }) else {
7021 continue;
7022 };
7023 let breakpoints = breakpoint_store.read(cx).breakpoints(
7024 &buffer,
7025 Some(
7026 buffer_snapshot.anchor_before(range.start)
7027 ..buffer_snapshot.anchor_after(range.end),
7028 ),
7029 buffer_snapshot,
7030 cx,
7031 );
7032 for (anchor, breakpoint) in breakpoints {
7033 let multi_buffer_anchor =
7034 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
7035 let position = multi_buffer_anchor
7036 .to_point(&multi_buffer_snapshot)
7037 .to_display_point(&snapshot);
7038
7039 breakpoint_display_points
7040 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
7041 }
7042 }
7043
7044 breakpoint_display_points
7045 }
7046
7047 fn breakpoint_context_menu(
7048 &self,
7049 anchor: Anchor,
7050 window: &mut Window,
7051 cx: &mut Context<Self>,
7052 ) -> Entity<ui::ContextMenu> {
7053 let weak_editor = cx.weak_entity();
7054 let focus_handle = self.focus_handle(cx);
7055
7056 let row = self
7057 .buffer
7058 .read(cx)
7059 .snapshot(cx)
7060 .summary_for_anchor::<Point>(&anchor)
7061 .row;
7062
7063 let breakpoint = self
7064 .breakpoint_at_row(row, window, cx)
7065 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7066
7067 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7068 "Edit Log Breakpoint"
7069 } else {
7070 "Set Log Breakpoint"
7071 };
7072
7073 let condition_breakpoint_msg = if breakpoint
7074 .as_ref()
7075 .is_some_and(|bp| bp.1.condition.is_some())
7076 {
7077 "Edit Condition Breakpoint"
7078 } else {
7079 "Set Condition Breakpoint"
7080 };
7081
7082 let hit_condition_breakpoint_msg = if breakpoint
7083 .as_ref()
7084 .is_some_and(|bp| bp.1.hit_condition.is_some())
7085 {
7086 "Edit Hit Condition Breakpoint"
7087 } else {
7088 "Set Hit Condition Breakpoint"
7089 };
7090
7091 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7092 "Unset Breakpoint"
7093 } else {
7094 "Set Breakpoint"
7095 };
7096
7097 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7098 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7099
7100 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7101 BreakpointState::Enabled => Some("Disable"),
7102 BreakpointState::Disabled => Some("Enable"),
7103 });
7104
7105 let (anchor, breakpoint) =
7106 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7107
7108 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7109 menu.on_blur_subscription(Subscription::new(|| {}))
7110 .context(focus_handle)
7111 .when(run_to_cursor, |this| {
7112 let weak_editor = weak_editor.clone();
7113 this.entry("Run to cursor", None, move |window, cx| {
7114 weak_editor
7115 .update(cx, |editor, cx| {
7116 editor.change_selections(None, window, cx, |s| {
7117 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7118 });
7119 })
7120 .ok();
7121
7122 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7123 })
7124 .separator()
7125 })
7126 .when_some(toggle_state_msg, |this, msg| {
7127 this.entry(msg, None, {
7128 let weak_editor = weak_editor.clone();
7129 let breakpoint = breakpoint.clone();
7130 move |_window, cx| {
7131 weak_editor
7132 .update(cx, |this, cx| {
7133 this.edit_breakpoint_at_anchor(
7134 anchor,
7135 breakpoint.as_ref().clone(),
7136 BreakpointEditAction::InvertState,
7137 cx,
7138 );
7139 })
7140 .log_err();
7141 }
7142 })
7143 })
7144 .entry(set_breakpoint_msg, None, {
7145 let weak_editor = weak_editor.clone();
7146 let breakpoint = breakpoint.clone();
7147 move |_window, cx| {
7148 weak_editor
7149 .update(cx, |this, cx| {
7150 this.edit_breakpoint_at_anchor(
7151 anchor,
7152 breakpoint.as_ref().clone(),
7153 BreakpointEditAction::Toggle,
7154 cx,
7155 );
7156 })
7157 .log_err();
7158 }
7159 })
7160 .entry(log_breakpoint_msg, None, {
7161 let breakpoint = breakpoint.clone();
7162 let weak_editor = weak_editor.clone();
7163 move |window, cx| {
7164 weak_editor
7165 .update(cx, |this, cx| {
7166 this.add_edit_breakpoint_block(
7167 anchor,
7168 breakpoint.as_ref(),
7169 BreakpointPromptEditAction::Log,
7170 window,
7171 cx,
7172 );
7173 })
7174 .log_err();
7175 }
7176 })
7177 .entry(condition_breakpoint_msg, None, {
7178 let breakpoint = breakpoint.clone();
7179 let weak_editor = weak_editor.clone();
7180 move |window, cx| {
7181 weak_editor
7182 .update(cx, |this, cx| {
7183 this.add_edit_breakpoint_block(
7184 anchor,
7185 breakpoint.as_ref(),
7186 BreakpointPromptEditAction::Condition,
7187 window,
7188 cx,
7189 );
7190 })
7191 .log_err();
7192 }
7193 })
7194 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7195 weak_editor
7196 .update(cx, |this, cx| {
7197 this.add_edit_breakpoint_block(
7198 anchor,
7199 breakpoint.as_ref(),
7200 BreakpointPromptEditAction::HitCondition,
7201 window,
7202 cx,
7203 );
7204 })
7205 .log_err();
7206 })
7207 })
7208 }
7209
7210 fn render_breakpoint(
7211 &self,
7212 position: Anchor,
7213 row: DisplayRow,
7214 breakpoint: &Breakpoint,
7215 cx: &mut Context<Self>,
7216 ) -> IconButton {
7217 // Is it a breakpoint that shows up when hovering over gutter?
7218 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7219 (false, false),
7220 |PhantomBreakpointIndicator {
7221 is_active,
7222 display_row,
7223 collides_with_existing_breakpoint,
7224 }| {
7225 (
7226 is_active && display_row == row,
7227 collides_with_existing_breakpoint,
7228 )
7229 },
7230 );
7231
7232 let (color, icon) = {
7233 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7234 (false, false) => ui::IconName::DebugBreakpoint,
7235 (true, false) => ui::IconName::DebugLogBreakpoint,
7236 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7237 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7238 };
7239
7240 let color = if is_phantom {
7241 Color::Hint
7242 } else {
7243 Color::Debugger
7244 };
7245
7246 (color, icon)
7247 };
7248
7249 let breakpoint = Arc::from(breakpoint.clone());
7250
7251 let alt_as_text = gpui::Keystroke {
7252 modifiers: Modifiers::secondary_key(),
7253 ..Default::default()
7254 };
7255 let primary_action_text = if breakpoint.is_disabled() {
7256 "enable"
7257 } else if is_phantom && !collides_with_existing {
7258 "set"
7259 } else {
7260 "unset"
7261 };
7262 let mut primary_text = format!("Click to {primary_action_text}");
7263 if collides_with_existing && !breakpoint.is_disabled() {
7264 use std::fmt::Write;
7265 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7266 }
7267 let primary_text = SharedString::from(primary_text);
7268 let focus_handle = self.focus_handle.clone();
7269 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7270 .icon_size(IconSize::XSmall)
7271 .size(ui::ButtonSize::None)
7272 .icon_color(color)
7273 .style(ButtonStyle::Transparent)
7274 .on_click(cx.listener({
7275 let breakpoint = breakpoint.clone();
7276
7277 move |editor, event: &ClickEvent, window, cx| {
7278 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7279 BreakpointEditAction::InvertState
7280 } else {
7281 BreakpointEditAction::Toggle
7282 };
7283
7284 window.focus(&editor.focus_handle(cx));
7285 editor.edit_breakpoint_at_anchor(
7286 position,
7287 breakpoint.as_ref().clone(),
7288 edit_action,
7289 cx,
7290 );
7291 }
7292 }))
7293 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7294 editor.set_breakpoint_context_menu(
7295 row,
7296 Some(position),
7297 event.down.position,
7298 window,
7299 cx,
7300 );
7301 }))
7302 .tooltip(move |window, cx| {
7303 Tooltip::with_meta_in(
7304 primary_text.clone(),
7305 None,
7306 "Right-click for more options",
7307 &focus_handle,
7308 window,
7309 cx,
7310 )
7311 })
7312 }
7313
7314 fn build_tasks_context(
7315 project: &Entity<Project>,
7316 buffer: &Entity<Buffer>,
7317 buffer_row: u32,
7318 tasks: &Arc<RunnableTasks>,
7319 cx: &mut Context<Self>,
7320 ) -> Task<Option<task::TaskContext>> {
7321 let position = Point::new(buffer_row, tasks.column);
7322 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7323 let location = Location {
7324 buffer: buffer.clone(),
7325 range: range_start..range_start,
7326 };
7327 // Fill in the environmental variables from the tree-sitter captures
7328 let mut captured_task_variables = TaskVariables::default();
7329 for (capture_name, value) in tasks.extra_variables.clone() {
7330 captured_task_variables.insert(
7331 task::VariableName::Custom(capture_name.into()),
7332 value.clone(),
7333 );
7334 }
7335 project.update(cx, |project, cx| {
7336 project.task_store().update(cx, |task_store, cx| {
7337 task_store.task_context_for_location(captured_task_variables, location, cx)
7338 })
7339 })
7340 }
7341
7342 pub fn spawn_nearest_task(
7343 &mut self,
7344 action: &SpawnNearestTask,
7345 window: &mut Window,
7346 cx: &mut Context<Self>,
7347 ) {
7348 let Some((workspace, _)) = self.workspace.clone() else {
7349 return;
7350 };
7351 let Some(project) = self.project.clone() else {
7352 return;
7353 };
7354
7355 // Try to find a closest, enclosing node using tree-sitter that has a
7356 // task
7357 let Some((buffer, buffer_row, tasks)) = self
7358 .find_enclosing_node_task(cx)
7359 // Or find the task that's closest in row-distance.
7360 .or_else(|| self.find_closest_task(cx))
7361 else {
7362 return;
7363 };
7364
7365 let reveal_strategy = action.reveal;
7366 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7367 cx.spawn_in(window, async move |_, cx| {
7368 let context = task_context.await?;
7369 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7370
7371 let resolved = &mut resolved_task.resolved;
7372 resolved.reveal = reveal_strategy;
7373
7374 workspace
7375 .update_in(cx, |workspace, window, cx| {
7376 workspace.schedule_resolved_task(
7377 task_source_kind,
7378 resolved_task,
7379 false,
7380 window,
7381 cx,
7382 );
7383 })
7384 .ok()
7385 })
7386 .detach();
7387 }
7388
7389 fn find_closest_task(
7390 &mut self,
7391 cx: &mut Context<Self>,
7392 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7393 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7394
7395 let ((buffer_id, row), tasks) = self
7396 .tasks
7397 .iter()
7398 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7399
7400 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7401 let tasks = Arc::new(tasks.to_owned());
7402 Some((buffer, *row, tasks))
7403 }
7404
7405 fn find_enclosing_node_task(
7406 &mut self,
7407 cx: &mut Context<Self>,
7408 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7409 let snapshot = self.buffer.read(cx).snapshot(cx);
7410 let offset = self.selections.newest::<usize>(cx).head();
7411 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7412 let buffer_id = excerpt.buffer().remote_id();
7413
7414 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7415 let mut cursor = layer.node().walk();
7416
7417 while cursor.goto_first_child_for_byte(offset).is_some() {
7418 if cursor.node().end_byte() == offset {
7419 cursor.goto_next_sibling();
7420 }
7421 }
7422
7423 // Ascend to the smallest ancestor that contains the range and has a task.
7424 loop {
7425 let node = cursor.node();
7426 let node_range = node.byte_range();
7427 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7428
7429 // Check if this node contains our offset
7430 if node_range.start <= offset && node_range.end >= offset {
7431 // If it contains offset, check for task
7432 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7433 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7434 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7435 }
7436 }
7437
7438 if !cursor.goto_parent() {
7439 break;
7440 }
7441 }
7442 None
7443 }
7444
7445 fn render_run_indicator(
7446 &self,
7447 _style: &EditorStyle,
7448 is_active: bool,
7449 row: DisplayRow,
7450 breakpoint: Option<(Anchor, Breakpoint)>,
7451 cx: &mut Context<Self>,
7452 ) -> IconButton {
7453 let color = Color::Muted;
7454 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7455
7456 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7457 .shape(ui::IconButtonShape::Square)
7458 .icon_size(IconSize::XSmall)
7459 .icon_color(color)
7460 .toggle_state(is_active)
7461 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7462 let quick_launch = e.down.button == MouseButton::Left;
7463 window.focus(&editor.focus_handle(cx));
7464 editor.toggle_code_actions(
7465 &ToggleCodeActions {
7466 deployed_from_indicator: Some(row),
7467 quick_launch,
7468 },
7469 window,
7470 cx,
7471 );
7472 }))
7473 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7474 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7475 }))
7476 }
7477
7478 pub fn context_menu_visible(&self) -> bool {
7479 !self.edit_prediction_preview_is_active()
7480 && self
7481 .context_menu
7482 .borrow()
7483 .as_ref()
7484 .map_or(false, |menu| menu.visible())
7485 }
7486
7487 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7488 self.context_menu
7489 .borrow()
7490 .as_ref()
7491 .map(|menu| menu.origin())
7492 }
7493
7494 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7495 self.context_menu_options = Some(options);
7496 }
7497
7498 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7499 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7500
7501 fn render_edit_prediction_popover(
7502 &mut self,
7503 text_bounds: &Bounds<Pixels>,
7504 content_origin: gpui::Point<Pixels>,
7505 right_margin: Pixels,
7506 editor_snapshot: &EditorSnapshot,
7507 visible_row_range: Range<DisplayRow>,
7508 scroll_top: f32,
7509 scroll_bottom: f32,
7510 line_layouts: &[LineWithInvisibles],
7511 line_height: Pixels,
7512 scroll_pixel_position: gpui::Point<Pixels>,
7513 newest_selection_head: Option<DisplayPoint>,
7514 editor_width: Pixels,
7515 style: &EditorStyle,
7516 window: &mut Window,
7517 cx: &mut App,
7518 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7519 if self.mode().is_minimap() {
7520 return None;
7521 }
7522 let active_inline_completion = self.active_inline_completion.as_ref()?;
7523
7524 if self.edit_prediction_visible_in_cursor_popover(true) {
7525 return None;
7526 }
7527
7528 match &active_inline_completion.completion {
7529 InlineCompletion::Move { target, .. } => {
7530 let target_display_point = target.to_display_point(editor_snapshot);
7531
7532 if self.edit_prediction_requires_modifier() {
7533 if !self.edit_prediction_preview_is_active() {
7534 return None;
7535 }
7536
7537 self.render_edit_prediction_modifier_jump_popover(
7538 text_bounds,
7539 content_origin,
7540 visible_row_range,
7541 line_layouts,
7542 line_height,
7543 scroll_pixel_position,
7544 newest_selection_head,
7545 target_display_point,
7546 window,
7547 cx,
7548 )
7549 } else {
7550 self.render_edit_prediction_eager_jump_popover(
7551 text_bounds,
7552 content_origin,
7553 editor_snapshot,
7554 visible_row_range,
7555 scroll_top,
7556 scroll_bottom,
7557 line_height,
7558 scroll_pixel_position,
7559 target_display_point,
7560 editor_width,
7561 window,
7562 cx,
7563 )
7564 }
7565 }
7566 InlineCompletion::Edit {
7567 display_mode: EditDisplayMode::Inline,
7568 ..
7569 } => None,
7570 InlineCompletion::Edit {
7571 display_mode: EditDisplayMode::TabAccept,
7572 edits,
7573 ..
7574 } => {
7575 let range = &edits.first()?.0;
7576 let target_display_point = range.end.to_display_point(editor_snapshot);
7577
7578 self.render_edit_prediction_end_of_line_popover(
7579 "Accept",
7580 editor_snapshot,
7581 visible_row_range,
7582 target_display_point,
7583 line_height,
7584 scroll_pixel_position,
7585 content_origin,
7586 editor_width,
7587 window,
7588 cx,
7589 )
7590 }
7591 InlineCompletion::Edit {
7592 edits,
7593 edit_preview,
7594 display_mode: EditDisplayMode::DiffPopover,
7595 snapshot,
7596 } => self.render_edit_prediction_diff_popover(
7597 text_bounds,
7598 content_origin,
7599 right_margin,
7600 editor_snapshot,
7601 visible_row_range,
7602 line_layouts,
7603 line_height,
7604 scroll_pixel_position,
7605 newest_selection_head,
7606 editor_width,
7607 style,
7608 edits,
7609 edit_preview,
7610 snapshot,
7611 window,
7612 cx,
7613 ),
7614 }
7615 }
7616
7617 fn render_edit_prediction_modifier_jump_popover(
7618 &mut self,
7619 text_bounds: &Bounds<Pixels>,
7620 content_origin: gpui::Point<Pixels>,
7621 visible_row_range: Range<DisplayRow>,
7622 line_layouts: &[LineWithInvisibles],
7623 line_height: Pixels,
7624 scroll_pixel_position: gpui::Point<Pixels>,
7625 newest_selection_head: Option<DisplayPoint>,
7626 target_display_point: DisplayPoint,
7627 window: &mut Window,
7628 cx: &mut App,
7629 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7630 let scrolled_content_origin =
7631 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7632
7633 const SCROLL_PADDING_Y: Pixels = px(12.);
7634
7635 if target_display_point.row() < visible_row_range.start {
7636 return self.render_edit_prediction_scroll_popover(
7637 |_| SCROLL_PADDING_Y,
7638 IconName::ArrowUp,
7639 visible_row_range,
7640 line_layouts,
7641 newest_selection_head,
7642 scrolled_content_origin,
7643 window,
7644 cx,
7645 );
7646 } else if target_display_point.row() >= visible_row_range.end {
7647 return self.render_edit_prediction_scroll_popover(
7648 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7649 IconName::ArrowDown,
7650 visible_row_range,
7651 line_layouts,
7652 newest_selection_head,
7653 scrolled_content_origin,
7654 window,
7655 cx,
7656 );
7657 }
7658
7659 const POLE_WIDTH: Pixels = px(2.);
7660
7661 let line_layout =
7662 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7663 let target_column = target_display_point.column() as usize;
7664
7665 let target_x = line_layout.x_for_index(target_column);
7666 let target_y =
7667 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7668
7669 let flag_on_right = target_x < text_bounds.size.width / 2.;
7670
7671 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7672 border_color.l += 0.001;
7673
7674 let mut element = v_flex()
7675 .items_end()
7676 .when(flag_on_right, |el| el.items_start())
7677 .child(if flag_on_right {
7678 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7679 .rounded_bl(px(0.))
7680 .rounded_tl(px(0.))
7681 .border_l_2()
7682 .border_color(border_color)
7683 } else {
7684 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7685 .rounded_br(px(0.))
7686 .rounded_tr(px(0.))
7687 .border_r_2()
7688 .border_color(border_color)
7689 })
7690 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7691 .into_any();
7692
7693 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7694
7695 let mut origin = scrolled_content_origin + point(target_x, target_y)
7696 - point(
7697 if flag_on_right {
7698 POLE_WIDTH
7699 } else {
7700 size.width - POLE_WIDTH
7701 },
7702 size.height - line_height,
7703 );
7704
7705 origin.x = origin.x.max(content_origin.x);
7706
7707 element.prepaint_at(origin, window, cx);
7708
7709 Some((element, origin))
7710 }
7711
7712 fn render_edit_prediction_scroll_popover(
7713 &mut self,
7714 to_y: impl Fn(Size<Pixels>) -> Pixels,
7715 scroll_icon: IconName,
7716 visible_row_range: Range<DisplayRow>,
7717 line_layouts: &[LineWithInvisibles],
7718 newest_selection_head: Option<DisplayPoint>,
7719 scrolled_content_origin: gpui::Point<Pixels>,
7720 window: &mut Window,
7721 cx: &mut App,
7722 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7723 let mut element = self
7724 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7725 .into_any();
7726
7727 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7728
7729 let cursor = newest_selection_head?;
7730 let cursor_row_layout =
7731 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7732 let cursor_column = cursor.column() as usize;
7733
7734 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7735
7736 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7737
7738 element.prepaint_at(origin, window, cx);
7739 Some((element, origin))
7740 }
7741
7742 fn render_edit_prediction_eager_jump_popover(
7743 &mut self,
7744 text_bounds: &Bounds<Pixels>,
7745 content_origin: gpui::Point<Pixels>,
7746 editor_snapshot: &EditorSnapshot,
7747 visible_row_range: Range<DisplayRow>,
7748 scroll_top: f32,
7749 scroll_bottom: f32,
7750 line_height: Pixels,
7751 scroll_pixel_position: gpui::Point<Pixels>,
7752 target_display_point: DisplayPoint,
7753 editor_width: Pixels,
7754 window: &mut Window,
7755 cx: &mut App,
7756 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7757 if target_display_point.row().as_f32() < scroll_top {
7758 let mut element = self
7759 .render_edit_prediction_line_popover(
7760 "Jump to Edit",
7761 Some(IconName::ArrowUp),
7762 window,
7763 cx,
7764 )?
7765 .into_any();
7766
7767 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7768 let offset = point(
7769 (text_bounds.size.width - size.width) / 2.,
7770 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7771 );
7772
7773 let origin = text_bounds.origin + offset;
7774 element.prepaint_at(origin, window, cx);
7775 Some((element, origin))
7776 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7777 let mut element = self
7778 .render_edit_prediction_line_popover(
7779 "Jump to Edit",
7780 Some(IconName::ArrowDown),
7781 window,
7782 cx,
7783 )?
7784 .into_any();
7785
7786 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7787 let offset = point(
7788 (text_bounds.size.width - size.width) / 2.,
7789 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7790 );
7791
7792 let origin = text_bounds.origin + offset;
7793 element.prepaint_at(origin, window, cx);
7794 Some((element, origin))
7795 } else {
7796 self.render_edit_prediction_end_of_line_popover(
7797 "Jump to Edit",
7798 editor_snapshot,
7799 visible_row_range,
7800 target_display_point,
7801 line_height,
7802 scroll_pixel_position,
7803 content_origin,
7804 editor_width,
7805 window,
7806 cx,
7807 )
7808 }
7809 }
7810
7811 fn render_edit_prediction_end_of_line_popover(
7812 self: &mut Editor,
7813 label: &'static str,
7814 editor_snapshot: &EditorSnapshot,
7815 visible_row_range: Range<DisplayRow>,
7816 target_display_point: DisplayPoint,
7817 line_height: Pixels,
7818 scroll_pixel_position: gpui::Point<Pixels>,
7819 content_origin: gpui::Point<Pixels>,
7820 editor_width: Pixels,
7821 window: &mut Window,
7822 cx: &mut App,
7823 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7824 let target_line_end = DisplayPoint::new(
7825 target_display_point.row(),
7826 editor_snapshot.line_len(target_display_point.row()),
7827 );
7828
7829 let mut element = self
7830 .render_edit_prediction_line_popover(label, None, window, cx)?
7831 .into_any();
7832
7833 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7834
7835 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7836
7837 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7838 let mut origin = start_point
7839 + line_origin
7840 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7841 origin.x = origin.x.max(content_origin.x);
7842
7843 let max_x = content_origin.x + editor_width - size.width;
7844
7845 if origin.x > max_x {
7846 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7847
7848 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7849 origin.y += offset;
7850 IconName::ArrowUp
7851 } else {
7852 origin.y -= offset;
7853 IconName::ArrowDown
7854 };
7855
7856 element = self
7857 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7858 .into_any();
7859
7860 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7861
7862 origin.x = content_origin.x + editor_width - size.width - px(2.);
7863 }
7864
7865 element.prepaint_at(origin, window, cx);
7866 Some((element, origin))
7867 }
7868
7869 fn render_edit_prediction_diff_popover(
7870 self: &Editor,
7871 text_bounds: &Bounds<Pixels>,
7872 content_origin: gpui::Point<Pixels>,
7873 right_margin: Pixels,
7874 editor_snapshot: &EditorSnapshot,
7875 visible_row_range: Range<DisplayRow>,
7876 line_layouts: &[LineWithInvisibles],
7877 line_height: Pixels,
7878 scroll_pixel_position: gpui::Point<Pixels>,
7879 newest_selection_head: Option<DisplayPoint>,
7880 editor_width: Pixels,
7881 style: &EditorStyle,
7882 edits: &Vec<(Range<Anchor>, String)>,
7883 edit_preview: &Option<language::EditPreview>,
7884 snapshot: &language::BufferSnapshot,
7885 window: &mut Window,
7886 cx: &mut App,
7887 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7888 let edit_start = edits
7889 .first()
7890 .unwrap()
7891 .0
7892 .start
7893 .to_display_point(editor_snapshot);
7894 let edit_end = edits
7895 .last()
7896 .unwrap()
7897 .0
7898 .end
7899 .to_display_point(editor_snapshot);
7900
7901 let is_visible = visible_row_range.contains(&edit_start.row())
7902 || visible_row_range.contains(&edit_end.row());
7903 if !is_visible {
7904 return None;
7905 }
7906
7907 let highlighted_edits =
7908 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7909
7910 let styled_text = highlighted_edits.to_styled_text(&style.text);
7911 let line_count = highlighted_edits.text.lines().count();
7912
7913 const BORDER_WIDTH: Pixels = px(1.);
7914
7915 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7916 let has_keybind = keybind.is_some();
7917
7918 let mut element = h_flex()
7919 .items_start()
7920 .child(
7921 h_flex()
7922 .bg(cx.theme().colors().editor_background)
7923 .border(BORDER_WIDTH)
7924 .shadow_sm()
7925 .border_color(cx.theme().colors().border)
7926 .rounded_l_lg()
7927 .when(line_count > 1, |el| el.rounded_br_lg())
7928 .pr_1()
7929 .child(styled_text),
7930 )
7931 .child(
7932 h_flex()
7933 .h(line_height + BORDER_WIDTH * 2.)
7934 .px_1p5()
7935 .gap_1()
7936 // Workaround: For some reason, there's a gap if we don't do this
7937 .ml(-BORDER_WIDTH)
7938 .shadow(smallvec![gpui::BoxShadow {
7939 color: gpui::black().opacity(0.05),
7940 offset: point(px(1.), px(1.)),
7941 blur_radius: px(2.),
7942 spread_radius: px(0.),
7943 }])
7944 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7945 .border(BORDER_WIDTH)
7946 .border_color(cx.theme().colors().border)
7947 .rounded_r_lg()
7948 .id("edit_prediction_diff_popover_keybind")
7949 .when(!has_keybind, |el| {
7950 let status_colors = cx.theme().status();
7951
7952 el.bg(status_colors.error_background)
7953 .border_color(status_colors.error.opacity(0.6))
7954 .child(Icon::new(IconName::Info).color(Color::Error))
7955 .cursor_default()
7956 .hoverable_tooltip(move |_window, cx| {
7957 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7958 })
7959 })
7960 .children(keybind),
7961 )
7962 .into_any();
7963
7964 let longest_row =
7965 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7966 let longest_line_width = if visible_row_range.contains(&longest_row) {
7967 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7968 } else {
7969 layout_line(
7970 longest_row,
7971 editor_snapshot,
7972 style,
7973 editor_width,
7974 |_| false,
7975 window,
7976 cx,
7977 )
7978 .width
7979 };
7980
7981 let viewport_bounds =
7982 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7983 right: -right_margin,
7984 ..Default::default()
7985 });
7986
7987 let x_after_longest =
7988 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7989 - scroll_pixel_position.x;
7990
7991 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7992
7993 // Fully visible if it can be displayed within the window (allow overlapping other
7994 // panes). However, this is only allowed if the popover starts within text_bounds.
7995 let can_position_to_the_right = x_after_longest < text_bounds.right()
7996 && x_after_longest + element_bounds.width < viewport_bounds.right();
7997
7998 let mut origin = if can_position_to_the_right {
7999 point(
8000 x_after_longest,
8001 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8002 - scroll_pixel_position.y,
8003 )
8004 } else {
8005 let cursor_row = newest_selection_head.map(|head| head.row());
8006 let above_edit = edit_start
8007 .row()
8008 .0
8009 .checked_sub(line_count as u32)
8010 .map(DisplayRow);
8011 let below_edit = Some(edit_end.row() + 1);
8012 let above_cursor =
8013 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8014 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8015
8016 // Place the edit popover adjacent to the edit if there is a location
8017 // available that is onscreen and does not obscure the cursor. Otherwise,
8018 // place it adjacent to the cursor.
8019 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8020 .into_iter()
8021 .flatten()
8022 .find(|&start_row| {
8023 let end_row = start_row + line_count as u32;
8024 visible_row_range.contains(&start_row)
8025 && visible_row_range.contains(&end_row)
8026 && cursor_row.map_or(true, |cursor_row| {
8027 !((start_row..end_row).contains(&cursor_row))
8028 })
8029 })?;
8030
8031 content_origin
8032 + point(
8033 -scroll_pixel_position.x,
8034 row_target.as_f32() * line_height - scroll_pixel_position.y,
8035 )
8036 };
8037
8038 origin.x -= BORDER_WIDTH;
8039
8040 window.defer_draw(element, origin, 1);
8041
8042 // Do not return an element, since it will already be drawn due to defer_draw.
8043 None
8044 }
8045
8046 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8047 px(30.)
8048 }
8049
8050 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8051 if self.read_only(cx) {
8052 cx.theme().players().read_only()
8053 } else {
8054 self.style.as_ref().unwrap().local_player
8055 }
8056 }
8057
8058 fn render_edit_prediction_accept_keybind(
8059 &self,
8060 window: &mut Window,
8061 cx: &App,
8062 ) -> Option<AnyElement> {
8063 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8064 let accept_keystroke = accept_binding.keystroke()?;
8065
8066 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8067
8068 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8069 Color::Accent
8070 } else {
8071 Color::Muted
8072 };
8073
8074 h_flex()
8075 .px_0p5()
8076 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8077 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8078 .text_size(TextSize::XSmall.rems(cx))
8079 .child(h_flex().children(ui::render_modifiers(
8080 &accept_keystroke.modifiers,
8081 PlatformStyle::platform(),
8082 Some(modifiers_color),
8083 Some(IconSize::XSmall.rems().into()),
8084 true,
8085 )))
8086 .when(is_platform_style_mac, |parent| {
8087 parent.child(accept_keystroke.key.clone())
8088 })
8089 .when(!is_platform_style_mac, |parent| {
8090 parent.child(
8091 Key::new(
8092 util::capitalize(&accept_keystroke.key),
8093 Some(Color::Default),
8094 )
8095 .size(Some(IconSize::XSmall.rems().into())),
8096 )
8097 })
8098 .into_any()
8099 .into()
8100 }
8101
8102 fn render_edit_prediction_line_popover(
8103 &self,
8104 label: impl Into<SharedString>,
8105 icon: Option<IconName>,
8106 window: &mut Window,
8107 cx: &App,
8108 ) -> Option<Stateful<Div>> {
8109 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8110
8111 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8112 let has_keybind = keybind.is_some();
8113
8114 let result = h_flex()
8115 .id("ep-line-popover")
8116 .py_0p5()
8117 .pl_1()
8118 .pr(padding_right)
8119 .gap_1()
8120 .rounded_md()
8121 .border_1()
8122 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8123 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8124 .shadow_sm()
8125 .when(!has_keybind, |el| {
8126 let status_colors = cx.theme().status();
8127
8128 el.bg(status_colors.error_background)
8129 .border_color(status_colors.error.opacity(0.6))
8130 .pl_2()
8131 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8132 .cursor_default()
8133 .hoverable_tooltip(move |_window, cx| {
8134 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8135 })
8136 })
8137 .children(keybind)
8138 .child(
8139 Label::new(label)
8140 .size(LabelSize::Small)
8141 .when(!has_keybind, |el| {
8142 el.color(cx.theme().status().error.into()).strikethrough()
8143 }),
8144 )
8145 .when(!has_keybind, |el| {
8146 el.child(
8147 h_flex().ml_1().child(
8148 Icon::new(IconName::Info)
8149 .size(IconSize::Small)
8150 .color(cx.theme().status().error.into()),
8151 ),
8152 )
8153 })
8154 .when_some(icon, |element, icon| {
8155 element.child(
8156 div()
8157 .mt(px(1.5))
8158 .child(Icon::new(icon).size(IconSize::Small)),
8159 )
8160 });
8161
8162 Some(result)
8163 }
8164
8165 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8166 let accent_color = cx.theme().colors().text_accent;
8167 let editor_bg_color = cx.theme().colors().editor_background;
8168 editor_bg_color.blend(accent_color.opacity(0.1))
8169 }
8170
8171 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8172 let accent_color = cx.theme().colors().text_accent;
8173 let editor_bg_color = cx.theme().colors().editor_background;
8174 editor_bg_color.blend(accent_color.opacity(0.6))
8175 }
8176
8177 fn render_edit_prediction_cursor_popover(
8178 &self,
8179 min_width: Pixels,
8180 max_width: Pixels,
8181 cursor_point: Point,
8182 style: &EditorStyle,
8183 accept_keystroke: Option<&gpui::Keystroke>,
8184 _window: &Window,
8185 cx: &mut Context<Editor>,
8186 ) -> Option<AnyElement> {
8187 let provider = self.edit_prediction_provider.as_ref()?;
8188
8189 if provider.provider.needs_terms_acceptance(cx) {
8190 return Some(
8191 h_flex()
8192 .min_w(min_width)
8193 .flex_1()
8194 .px_2()
8195 .py_1()
8196 .gap_3()
8197 .elevation_2(cx)
8198 .hover(|style| style.bg(cx.theme().colors().element_hover))
8199 .id("accept-terms")
8200 .cursor_pointer()
8201 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8202 .on_click(cx.listener(|this, _event, window, cx| {
8203 cx.stop_propagation();
8204 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8205 window.dispatch_action(
8206 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8207 cx,
8208 );
8209 }))
8210 .child(
8211 h_flex()
8212 .flex_1()
8213 .gap_2()
8214 .child(Icon::new(IconName::ZedPredict))
8215 .child(Label::new("Accept Terms of Service"))
8216 .child(div().w_full())
8217 .child(
8218 Icon::new(IconName::ArrowUpRight)
8219 .color(Color::Muted)
8220 .size(IconSize::Small),
8221 )
8222 .into_any_element(),
8223 )
8224 .into_any(),
8225 );
8226 }
8227
8228 let is_refreshing = provider.provider.is_refreshing(cx);
8229
8230 fn pending_completion_container() -> Div {
8231 h_flex()
8232 .h_full()
8233 .flex_1()
8234 .gap_2()
8235 .child(Icon::new(IconName::ZedPredict))
8236 }
8237
8238 let completion = match &self.active_inline_completion {
8239 Some(prediction) => {
8240 if !self.has_visible_completions_menu() {
8241 const RADIUS: Pixels = px(6.);
8242 const BORDER_WIDTH: Pixels = px(1.);
8243
8244 return Some(
8245 h_flex()
8246 .elevation_2(cx)
8247 .border(BORDER_WIDTH)
8248 .border_color(cx.theme().colors().border)
8249 .when(accept_keystroke.is_none(), |el| {
8250 el.border_color(cx.theme().status().error)
8251 })
8252 .rounded(RADIUS)
8253 .rounded_tl(px(0.))
8254 .overflow_hidden()
8255 .child(div().px_1p5().child(match &prediction.completion {
8256 InlineCompletion::Move { target, snapshot } => {
8257 use text::ToPoint as _;
8258 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8259 {
8260 Icon::new(IconName::ZedPredictDown)
8261 } else {
8262 Icon::new(IconName::ZedPredictUp)
8263 }
8264 }
8265 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8266 }))
8267 .child(
8268 h_flex()
8269 .gap_1()
8270 .py_1()
8271 .px_2()
8272 .rounded_r(RADIUS - BORDER_WIDTH)
8273 .border_l_1()
8274 .border_color(cx.theme().colors().border)
8275 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8276 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8277 el.child(
8278 Label::new("Hold")
8279 .size(LabelSize::Small)
8280 .when(accept_keystroke.is_none(), |el| {
8281 el.strikethrough()
8282 })
8283 .line_height_style(LineHeightStyle::UiLabel),
8284 )
8285 })
8286 .id("edit_prediction_cursor_popover_keybind")
8287 .when(accept_keystroke.is_none(), |el| {
8288 let status_colors = cx.theme().status();
8289
8290 el.bg(status_colors.error_background)
8291 .border_color(status_colors.error.opacity(0.6))
8292 .child(Icon::new(IconName::Info).color(Color::Error))
8293 .cursor_default()
8294 .hoverable_tooltip(move |_window, cx| {
8295 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8296 .into()
8297 })
8298 })
8299 .when_some(
8300 accept_keystroke.as_ref(),
8301 |el, accept_keystroke| {
8302 el.child(h_flex().children(ui::render_modifiers(
8303 &accept_keystroke.modifiers,
8304 PlatformStyle::platform(),
8305 Some(Color::Default),
8306 Some(IconSize::XSmall.rems().into()),
8307 false,
8308 )))
8309 },
8310 ),
8311 )
8312 .into_any(),
8313 );
8314 }
8315
8316 self.render_edit_prediction_cursor_popover_preview(
8317 prediction,
8318 cursor_point,
8319 style,
8320 cx,
8321 )?
8322 }
8323
8324 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8325 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8326 stale_completion,
8327 cursor_point,
8328 style,
8329 cx,
8330 )?,
8331
8332 None => {
8333 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8334 }
8335 },
8336
8337 None => pending_completion_container().child(Label::new("No Prediction")),
8338 };
8339
8340 let completion = if is_refreshing {
8341 completion
8342 .with_animation(
8343 "loading-completion",
8344 Animation::new(Duration::from_secs(2))
8345 .repeat()
8346 .with_easing(pulsating_between(0.4, 0.8)),
8347 |label, delta| label.opacity(delta),
8348 )
8349 .into_any_element()
8350 } else {
8351 completion.into_any_element()
8352 };
8353
8354 let has_completion = self.active_inline_completion.is_some();
8355
8356 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8357 Some(
8358 h_flex()
8359 .min_w(min_width)
8360 .max_w(max_width)
8361 .flex_1()
8362 .elevation_2(cx)
8363 .border_color(cx.theme().colors().border)
8364 .child(
8365 div()
8366 .flex_1()
8367 .py_1()
8368 .px_2()
8369 .overflow_hidden()
8370 .child(completion),
8371 )
8372 .when_some(accept_keystroke, |el, accept_keystroke| {
8373 if !accept_keystroke.modifiers.modified() {
8374 return el;
8375 }
8376
8377 el.child(
8378 h_flex()
8379 .h_full()
8380 .border_l_1()
8381 .rounded_r_lg()
8382 .border_color(cx.theme().colors().border)
8383 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8384 .gap_1()
8385 .py_1()
8386 .px_2()
8387 .child(
8388 h_flex()
8389 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8390 .when(is_platform_style_mac, |parent| parent.gap_1())
8391 .child(h_flex().children(ui::render_modifiers(
8392 &accept_keystroke.modifiers,
8393 PlatformStyle::platform(),
8394 Some(if !has_completion {
8395 Color::Muted
8396 } else {
8397 Color::Default
8398 }),
8399 None,
8400 false,
8401 ))),
8402 )
8403 .child(Label::new("Preview").into_any_element())
8404 .opacity(if has_completion { 1.0 } else { 0.4 }),
8405 )
8406 })
8407 .into_any(),
8408 )
8409 }
8410
8411 fn render_edit_prediction_cursor_popover_preview(
8412 &self,
8413 completion: &InlineCompletionState,
8414 cursor_point: Point,
8415 style: &EditorStyle,
8416 cx: &mut Context<Editor>,
8417 ) -> Option<Div> {
8418 use text::ToPoint as _;
8419
8420 fn render_relative_row_jump(
8421 prefix: impl Into<String>,
8422 current_row: u32,
8423 target_row: u32,
8424 ) -> Div {
8425 let (row_diff, arrow) = if target_row < current_row {
8426 (current_row - target_row, IconName::ArrowUp)
8427 } else {
8428 (target_row - current_row, IconName::ArrowDown)
8429 };
8430
8431 h_flex()
8432 .child(
8433 Label::new(format!("{}{}", prefix.into(), row_diff))
8434 .color(Color::Muted)
8435 .size(LabelSize::Small),
8436 )
8437 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8438 }
8439
8440 match &completion.completion {
8441 InlineCompletion::Move {
8442 target, snapshot, ..
8443 } => Some(
8444 h_flex()
8445 .px_2()
8446 .gap_2()
8447 .flex_1()
8448 .child(
8449 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8450 Icon::new(IconName::ZedPredictDown)
8451 } else {
8452 Icon::new(IconName::ZedPredictUp)
8453 },
8454 )
8455 .child(Label::new("Jump to Edit")),
8456 ),
8457
8458 InlineCompletion::Edit {
8459 edits,
8460 edit_preview,
8461 snapshot,
8462 display_mode: _,
8463 } => {
8464 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8465
8466 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8467 &snapshot,
8468 &edits,
8469 edit_preview.as_ref()?,
8470 true,
8471 cx,
8472 )
8473 .first_line_preview();
8474
8475 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8476 .with_default_highlights(&style.text, highlighted_edits.highlights);
8477
8478 let preview = h_flex()
8479 .gap_1()
8480 .min_w_16()
8481 .child(styled_text)
8482 .when(has_more_lines, |parent| parent.child("…"));
8483
8484 let left = if first_edit_row != cursor_point.row {
8485 render_relative_row_jump("", cursor_point.row, first_edit_row)
8486 .into_any_element()
8487 } else {
8488 Icon::new(IconName::ZedPredict).into_any_element()
8489 };
8490
8491 Some(
8492 h_flex()
8493 .h_full()
8494 .flex_1()
8495 .gap_2()
8496 .pr_1()
8497 .overflow_x_hidden()
8498 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8499 .child(left)
8500 .child(preview),
8501 )
8502 }
8503 }
8504 }
8505
8506 fn render_context_menu(
8507 &self,
8508 style: &EditorStyle,
8509 max_height_in_lines: u32,
8510 window: &mut Window,
8511 cx: &mut Context<Editor>,
8512 ) -> Option<AnyElement> {
8513 let menu = self.context_menu.borrow();
8514 let menu = menu.as_ref()?;
8515 if !menu.visible() {
8516 return None;
8517 };
8518 Some(menu.render(style, max_height_in_lines, window, cx))
8519 }
8520
8521 fn render_context_menu_aside(
8522 &mut self,
8523 max_size: Size<Pixels>,
8524 window: &mut Window,
8525 cx: &mut Context<Editor>,
8526 ) -> Option<AnyElement> {
8527 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8528 if menu.visible() {
8529 menu.render_aside(self, max_size, window, cx)
8530 } else {
8531 None
8532 }
8533 })
8534 }
8535
8536 fn hide_context_menu(
8537 &mut self,
8538 window: &mut Window,
8539 cx: &mut Context<Self>,
8540 ) -> Option<CodeContextMenu> {
8541 cx.notify();
8542 self.completion_tasks.clear();
8543 let context_menu = self.context_menu.borrow_mut().take();
8544 self.stale_inline_completion_in_menu.take();
8545 self.update_visible_inline_completion(window, cx);
8546 context_menu
8547 }
8548
8549 fn show_snippet_choices(
8550 &mut self,
8551 choices: &Vec<String>,
8552 selection: Range<Anchor>,
8553 cx: &mut Context<Self>,
8554 ) {
8555 if selection.start.buffer_id.is_none() {
8556 return;
8557 }
8558 let buffer_id = selection.start.buffer_id.unwrap();
8559 let buffer = self.buffer().read(cx).buffer(buffer_id);
8560 let id = post_inc(&mut self.next_completion_id);
8561 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8562
8563 if let Some(buffer) = buffer {
8564 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8565 CompletionsMenu::new_snippet_choices(
8566 id,
8567 true,
8568 choices,
8569 selection,
8570 buffer,
8571 snippet_sort_order,
8572 ),
8573 ));
8574 }
8575 }
8576
8577 pub fn insert_snippet(
8578 &mut self,
8579 insertion_ranges: &[Range<usize>],
8580 snippet: Snippet,
8581 window: &mut Window,
8582 cx: &mut Context<Self>,
8583 ) -> Result<()> {
8584 struct Tabstop<T> {
8585 is_end_tabstop: bool,
8586 ranges: Vec<Range<T>>,
8587 choices: Option<Vec<String>>,
8588 }
8589
8590 let tabstops = self.buffer.update(cx, |buffer, cx| {
8591 let snippet_text: Arc<str> = snippet.text.clone().into();
8592 let edits = insertion_ranges
8593 .iter()
8594 .cloned()
8595 .map(|range| (range, snippet_text.clone()));
8596 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8597
8598 let snapshot = &*buffer.read(cx);
8599 let snippet = &snippet;
8600 snippet
8601 .tabstops
8602 .iter()
8603 .map(|tabstop| {
8604 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8605 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8606 });
8607 let mut tabstop_ranges = tabstop
8608 .ranges
8609 .iter()
8610 .flat_map(|tabstop_range| {
8611 let mut delta = 0_isize;
8612 insertion_ranges.iter().map(move |insertion_range| {
8613 let insertion_start = insertion_range.start as isize + delta;
8614 delta +=
8615 snippet.text.len() as isize - insertion_range.len() as isize;
8616
8617 let start = ((insertion_start + tabstop_range.start) as usize)
8618 .min(snapshot.len());
8619 let end = ((insertion_start + tabstop_range.end) as usize)
8620 .min(snapshot.len());
8621 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8622 })
8623 })
8624 .collect::<Vec<_>>();
8625 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8626
8627 Tabstop {
8628 is_end_tabstop,
8629 ranges: tabstop_ranges,
8630 choices: tabstop.choices.clone(),
8631 }
8632 })
8633 .collect::<Vec<_>>()
8634 });
8635 if let Some(tabstop) = tabstops.first() {
8636 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8637 s.select_ranges(tabstop.ranges.iter().cloned());
8638 });
8639
8640 if let Some(choices) = &tabstop.choices {
8641 if let Some(selection) = tabstop.ranges.first() {
8642 self.show_snippet_choices(choices, selection.clone(), cx)
8643 }
8644 }
8645
8646 // If we're already at the last tabstop and it's at the end of the snippet,
8647 // we're done, we don't need to keep the state around.
8648 if !tabstop.is_end_tabstop {
8649 let choices = tabstops
8650 .iter()
8651 .map(|tabstop| tabstop.choices.clone())
8652 .collect();
8653
8654 let ranges = tabstops
8655 .into_iter()
8656 .map(|tabstop| tabstop.ranges)
8657 .collect::<Vec<_>>();
8658
8659 self.snippet_stack.push(SnippetState {
8660 active_index: 0,
8661 ranges,
8662 choices,
8663 });
8664 }
8665
8666 // Check whether the just-entered snippet ends with an auto-closable bracket.
8667 if self.autoclose_regions.is_empty() {
8668 let snapshot = self.buffer.read(cx).snapshot(cx);
8669 for selection in &mut self.selections.all::<Point>(cx) {
8670 let selection_head = selection.head();
8671 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8672 continue;
8673 };
8674
8675 let mut bracket_pair = None;
8676 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8677 let prev_chars = snapshot
8678 .reversed_chars_at(selection_head)
8679 .collect::<String>();
8680 for (pair, enabled) in scope.brackets() {
8681 if enabled
8682 && pair.close
8683 && prev_chars.starts_with(pair.start.as_str())
8684 && next_chars.starts_with(pair.end.as_str())
8685 {
8686 bracket_pair = Some(pair.clone());
8687 break;
8688 }
8689 }
8690 if let Some(pair) = bracket_pair {
8691 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8692 let autoclose_enabled =
8693 self.use_autoclose && snapshot_settings.use_autoclose;
8694 if autoclose_enabled {
8695 let start = snapshot.anchor_after(selection_head);
8696 let end = snapshot.anchor_after(selection_head);
8697 self.autoclose_regions.push(AutocloseRegion {
8698 selection_id: selection.id,
8699 range: start..end,
8700 pair,
8701 });
8702 }
8703 }
8704 }
8705 }
8706 }
8707 Ok(())
8708 }
8709
8710 pub fn move_to_next_snippet_tabstop(
8711 &mut self,
8712 window: &mut Window,
8713 cx: &mut Context<Self>,
8714 ) -> bool {
8715 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8716 }
8717
8718 pub fn move_to_prev_snippet_tabstop(
8719 &mut self,
8720 window: &mut Window,
8721 cx: &mut Context<Self>,
8722 ) -> bool {
8723 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8724 }
8725
8726 pub fn move_to_snippet_tabstop(
8727 &mut self,
8728 bias: Bias,
8729 window: &mut Window,
8730 cx: &mut Context<Self>,
8731 ) -> bool {
8732 if let Some(mut snippet) = self.snippet_stack.pop() {
8733 match bias {
8734 Bias::Left => {
8735 if snippet.active_index > 0 {
8736 snippet.active_index -= 1;
8737 } else {
8738 self.snippet_stack.push(snippet);
8739 return false;
8740 }
8741 }
8742 Bias::Right => {
8743 if snippet.active_index + 1 < snippet.ranges.len() {
8744 snippet.active_index += 1;
8745 } else {
8746 self.snippet_stack.push(snippet);
8747 return false;
8748 }
8749 }
8750 }
8751 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8752 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8753 s.select_anchor_ranges(current_ranges.iter().cloned())
8754 });
8755
8756 if let Some(choices) = &snippet.choices[snippet.active_index] {
8757 if let Some(selection) = current_ranges.first() {
8758 self.show_snippet_choices(&choices, selection.clone(), cx);
8759 }
8760 }
8761
8762 // If snippet state is not at the last tabstop, push it back on the stack
8763 if snippet.active_index + 1 < snippet.ranges.len() {
8764 self.snippet_stack.push(snippet);
8765 }
8766 return true;
8767 }
8768 }
8769
8770 false
8771 }
8772
8773 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8774 self.transact(window, cx, |this, window, cx| {
8775 this.select_all(&SelectAll, window, cx);
8776 this.insert("", window, cx);
8777 });
8778 }
8779
8780 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8781 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8782 self.transact(window, cx, |this, window, cx| {
8783 this.select_autoclose_pair(window, cx);
8784 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8785 if !this.linked_edit_ranges.is_empty() {
8786 let selections = this.selections.all::<MultiBufferPoint>(cx);
8787 let snapshot = this.buffer.read(cx).snapshot(cx);
8788
8789 for selection in selections.iter() {
8790 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8791 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8792 if selection_start.buffer_id != selection_end.buffer_id {
8793 continue;
8794 }
8795 if let Some(ranges) =
8796 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8797 {
8798 for (buffer, entries) in ranges {
8799 linked_ranges.entry(buffer).or_default().extend(entries);
8800 }
8801 }
8802 }
8803 }
8804
8805 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8806 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8807 for selection in &mut selections {
8808 if selection.is_empty() {
8809 let old_head = selection.head();
8810 let mut new_head =
8811 movement::left(&display_map, old_head.to_display_point(&display_map))
8812 .to_point(&display_map);
8813 if let Some((buffer, line_buffer_range)) = display_map
8814 .buffer_snapshot
8815 .buffer_line_for_row(MultiBufferRow(old_head.row))
8816 {
8817 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8818 let indent_len = match indent_size.kind {
8819 IndentKind::Space => {
8820 buffer.settings_at(line_buffer_range.start, cx).tab_size
8821 }
8822 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8823 };
8824 if old_head.column <= indent_size.len && old_head.column > 0 {
8825 let indent_len = indent_len.get();
8826 new_head = cmp::min(
8827 new_head,
8828 MultiBufferPoint::new(
8829 old_head.row,
8830 ((old_head.column - 1) / indent_len) * indent_len,
8831 ),
8832 );
8833 }
8834 }
8835
8836 selection.set_head(new_head, SelectionGoal::None);
8837 }
8838 }
8839
8840 this.signature_help_state.set_backspace_pressed(true);
8841 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8842 s.select(selections)
8843 });
8844 this.insert("", window, cx);
8845 let empty_str: Arc<str> = Arc::from("");
8846 for (buffer, edits) in linked_ranges {
8847 let snapshot = buffer.read(cx).snapshot();
8848 use text::ToPoint as TP;
8849
8850 let edits = edits
8851 .into_iter()
8852 .map(|range| {
8853 let end_point = TP::to_point(&range.end, &snapshot);
8854 let mut start_point = TP::to_point(&range.start, &snapshot);
8855
8856 if end_point == start_point {
8857 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8858 .saturating_sub(1);
8859 start_point =
8860 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8861 };
8862
8863 (start_point..end_point, empty_str.clone())
8864 })
8865 .sorted_by_key(|(range, _)| range.start)
8866 .collect::<Vec<_>>();
8867 buffer.update(cx, |this, cx| {
8868 this.edit(edits, None, cx);
8869 })
8870 }
8871 this.refresh_inline_completion(true, false, window, cx);
8872 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8873 });
8874 }
8875
8876 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8877 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8878 self.transact(window, cx, |this, window, cx| {
8879 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8880 s.move_with(|map, selection| {
8881 if selection.is_empty() {
8882 let cursor = movement::right(map, selection.head());
8883 selection.end = cursor;
8884 selection.reversed = true;
8885 selection.goal = SelectionGoal::None;
8886 }
8887 })
8888 });
8889 this.insert("", window, cx);
8890 this.refresh_inline_completion(true, false, window, cx);
8891 });
8892 }
8893
8894 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8895 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8896 if self.move_to_prev_snippet_tabstop(window, cx) {
8897 return;
8898 }
8899 self.outdent(&Outdent, window, cx);
8900 }
8901
8902 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8903 if self.move_to_next_snippet_tabstop(window, cx) {
8904 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8905 return;
8906 }
8907 if self.read_only(cx) {
8908 return;
8909 }
8910 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8911 let mut selections = self.selections.all_adjusted(cx);
8912 let buffer = self.buffer.read(cx);
8913 let snapshot = buffer.snapshot(cx);
8914 let rows_iter = selections.iter().map(|s| s.head().row);
8915 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8916
8917 let has_some_cursor_in_whitespace = selections
8918 .iter()
8919 .filter(|selection| selection.is_empty())
8920 .any(|selection| {
8921 let cursor = selection.head();
8922 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8923 cursor.column < current_indent.len
8924 });
8925
8926 let mut edits = Vec::new();
8927 let mut prev_edited_row = 0;
8928 let mut row_delta = 0;
8929 for selection in &mut selections {
8930 if selection.start.row != prev_edited_row {
8931 row_delta = 0;
8932 }
8933 prev_edited_row = selection.end.row;
8934
8935 // If the selection is non-empty, then increase the indentation of the selected lines.
8936 if !selection.is_empty() {
8937 row_delta =
8938 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8939 continue;
8940 }
8941
8942 let cursor = selection.head();
8943 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8944 if let Some(suggested_indent) =
8945 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8946 {
8947 // Don't do anything if already at suggested indent
8948 // and there is any other cursor which is not
8949 if has_some_cursor_in_whitespace
8950 && cursor.column == current_indent.len
8951 && current_indent.len == suggested_indent.len
8952 {
8953 continue;
8954 }
8955
8956 // Adjust line and move cursor to suggested indent
8957 // if cursor is not at suggested indent
8958 if cursor.column < suggested_indent.len
8959 && cursor.column <= current_indent.len
8960 && current_indent.len <= suggested_indent.len
8961 {
8962 selection.start = Point::new(cursor.row, suggested_indent.len);
8963 selection.end = selection.start;
8964 if row_delta == 0 {
8965 edits.extend(Buffer::edit_for_indent_size_adjustment(
8966 cursor.row,
8967 current_indent,
8968 suggested_indent,
8969 ));
8970 row_delta = suggested_indent.len - current_indent.len;
8971 }
8972 continue;
8973 }
8974
8975 // If current indent is more than suggested indent
8976 // only move cursor to current indent and skip indent
8977 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8978 selection.start = Point::new(cursor.row, current_indent.len);
8979 selection.end = selection.start;
8980 continue;
8981 }
8982 }
8983
8984 // Otherwise, insert a hard or soft tab.
8985 let settings = buffer.language_settings_at(cursor, cx);
8986 let tab_size = if settings.hard_tabs {
8987 IndentSize::tab()
8988 } else {
8989 let tab_size = settings.tab_size.get();
8990 let indent_remainder = snapshot
8991 .text_for_range(Point::new(cursor.row, 0)..cursor)
8992 .flat_map(str::chars)
8993 .fold(row_delta % tab_size, |counter: u32, c| {
8994 if c == '\t' {
8995 0
8996 } else {
8997 (counter + 1) % tab_size
8998 }
8999 });
9000
9001 let chars_to_next_tab_stop = tab_size - indent_remainder;
9002 IndentSize::spaces(chars_to_next_tab_stop)
9003 };
9004 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9005 selection.end = selection.start;
9006 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9007 row_delta += tab_size.len;
9008 }
9009
9010 self.transact(window, cx, |this, window, cx| {
9011 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9012 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9013 s.select(selections)
9014 });
9015 this.refresh_inline_completion(true, false, window, cx);
9016 });
9017 }
9018
9019 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9020 if self.read_only(cx) {
9021 return;
9022 }
9023 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9024 let mut selections = self.selections.all::<Point>(cx);
9025 let mut prev_edited_row = 0;
9026 let mut row_delta = 0;
9027 let mut edits = Vec::new();
9028 let buffer = self.buffer.read(cx);
9029 let snapshot = buffer.snapshot(cx);
9030 for selection in &mut selections {
9031 if selection.start.row != prev_edited_row {
9032 row_delta = 0;
9033 }
9034 prev_edited_row = selection.end.row;
9035
9036 row_delta =
9037 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9038 }
9039
9040 self.transact(window, cx, |this, window, cx| {
9041 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9042 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9043 s.select(selections)
9044 });
9045 });
9046 }
9047
9048 fn indent_selection(
9049 buffer: &MultiBuffer,
9050 snapshot: &MultiBufferSnapshot,
9051 selection: &mut Selection<Point>,
9052 edits: &mut Vec<(Range<Point>, String)>,
9053 delta_for_start_row: u32,
9054 cx: &App,
9055 ) -> u32 {
9056 let settings = buffer.language_settings_at(selection.start, cx);
9057 let tab_size = settings.tab_size.get();
9058 let indent_kind = if settings.hard_tabs {
9059 IndentKind::Tab
9060 } else {
9061 IndentKind::Space
9062 };
9063 let mut start_row = selection.start.row;
9064 let mut end_row = selection.end.row + 1;
9065
9066 // If a selection ends at the beginning of a line, don't indent
9067 // that last line.
9068 if selection.end.column == 0 && selection.end.row > selection.start.row {
9069 end_row -= 1;
9070 }
9071
9072 // Avoid re-indenting a row that has already been indented by a
9073 // previous selection, but still update this selection's column
9074 // to reflect that indentation.
9075 if delta_for_start_row > 0 {
9076 start_row += 1;
9077 selection.start.column += delta_for_start_row;
9078 if selection.end.row == selection.start.row {
9079 selection.end.column += delta_for_start_row;
9080 }
9081 }
9082
9083 let mut delta_for_end_row = 0;
9084 let has_multiple_rows = start_row + 1 != end_row;
9085 for row in start_row..end_row {
9086 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9087 let indent_delta = match (current_indent.kind, indent_kind) {
9088 (IndentKind::Space, IndentKind::Space) => {
9089 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9090 IndentSize::spaces(columns_to_next_tab_stop)
9091 }
9092 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9093 (_, IndentKind::Tab) => IndentSize::tab(),
9094 };
9095
9096 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9097 0
9098 } else {
9099 selection.start.column
9100 };
9101 let row_start = Point::new(row, start);
9102 edits.push((
9103 row_start..row_start,
9104 indent_delta.chars().collect::<String>(),
9105 ));
9106
9107 // Update this selection's endpoints to reflect the indentation.
9108 if row == selection.start.row {
9109 selection.start.column += indent_delta.len;
9110 }
9111 if row == selection.end.row {
9112 selection.end.column += indent_delta.len;
9113 delta_for_end_row = indent_delta.len;
9114 }
9115 }
9116
9117 if selection.start.row == selection.end.row {
9118 delta_for_start_row + delta_for_end_row
9119 } else {
9120 delta_for_end_row
9121 }
9122 }
9123
9124 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9125 if self.read_only(cx) {
9126 return;
9127 }
9128 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9129 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9130 let selections = self.selections.all::<Point>(cx);
9131 let mut deletion_ranges = Vec::new();
9132 let mut last_outdent = None;
9133 {
9134 let buffer = self.buffer.read(cx);
9135 let snapshot = buffer.snapshot(cx);
9136 for selection in &selections {
9137 let settings = buffer.language_settings_at(selection.start, cx);
9138 let tab_size = settings.tab_size.get();
9139 let mut rows = selection.spanned_rows(false, &display_map);
9140
9141 // Avoid re-outdenting a row that has already been outdented by a
9142 // previous selection.
9143 if let Some(last_row) = last_outdent {
9144 if last_row == rows.start {
9145 rows.start = rows.start.next_row();
9146 }
9147 }
9148 let has_multiple_rows = rows.len() > 1;
9149 for row in rows.iter_rows() {
9150 let indent_size = snapshot.indent_size_for_line(row);
9151 if indent_size.len > 0 {
9152 let deletion_len = match indent_size.kind {
9153 IndentKind::Space => {
9154 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9155 if columns_to_prev_tab_stop == 0 {
9156 tab_size
9157 } else {
9158 columns_to_prev_tab_stop
9159 }
9160 }
9161 IndentKind::Tab => 1,
9162 };
9163 let start = if has_multiple_rows
9164 || deletion_len > selection.start.column
9165 || indent_size.len < selection.start.column
9166 {
9167 0
9168 } else {
9169 selection.start.column - deletion_len
9170 };
9171 deletion_ranges.push(
9172 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9173 );
9174 last_outdent = Some(row);
9175 }
9176 }
9177 }
9178 }
9179
9180 self.transact(window, cx, |this, window, cx| {
9181 this.buffer.update(cx, |buffer, cx| {
9182 let empty_str: Arc<str> = Arc::default();
9183 buffer.edit(
9184 deletion_ranges
9185 .into_iter()
9186 .map(|range| (range, empty_str.clone())),
9187 None,
9188 cx,
9189 );
9190 });
9191 let selections = this.selections.all::<usize>(cx);
9192 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9193 s.select(selections)
9194 });
9195 });
9196 }
9197
9198 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9199 if self.read_only(cx) {
9200 return;
9201 }
9202 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9203 let selections = self
9204 .selections
9205 .all::<usize>(cx)
9206 .into_iter()
9207 .map(|s| s.range());
9208
9209 self.transact(window, cx, |this, window, cx| {
9210 this.buffer.update(cx, |buffer, cx| {
9211 buffer.autoindent_ranges(selections, cx);
9212 });
9213 let selections = this.selections.all::<usize>(cx);
9214 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9215 s.select(selections)
9216 });
9217 });
9218 }
9219
9220 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9221 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9222 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9223 let selections = self.selections.all::<Point>(cx);
9224
9225 let mut new_cursors = Vec::new();
9226 let mut edit_ranges = Vec::new();
9227 let mut selections = selections.iter().peekable();
9228 while let Some(selection) = selections.next() {
9229 let mut rows = selection.spanned_rows(false, &display_map);
9230 let goal_display_column = selection.head().to_display_point(&display_map).column();
9231
9232 // Accumulate contiguous regions of rows that we want to delete.
9233 while let Some(next_selection) = selections.peek() {
9234 let next_rows = next_selection.spanned_rows(false, &display_map);
9235 if next_rows.start <= rows.end {
9236 rows.end = next_rows.end;
9237 selections.next().unwrap();
9238 } else {
9239 break;
9240 }
9241 }
9242
9243 let buffer = &display_map.buffer_snapshot;
9244 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9245 let edit_end;
9246 let cursor_buffer_row;
9247 if buffer.max_point().row >= rows.end.0 {
9248 // If there's a line after the range, delete the \n from the end of the row range
9249 // and position the cursor on the next line.
9250 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9251 cursor_buffer_row = rows.end;
9252 } else {
9253 // If there isn't a line after the range, delete the \n from the line before the
9254 // start of the row range and position the cursor there.
9255 edit_start = edit_start.saturating_sub(1);
9256 edit_end = buffer.len();
9257 cursor_buffer_row = rows.start.previous_row();
9258 }
9259
9260 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9261 *cursor.column_mut() =
9262 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9263
9264 new_cursors.push((
9265 selection.id,
9266 buffer.anchor_after(cursor.to_point(&display_map)),
9267 ));
9268 edit_ranges.push(edit_start..edit_end);
9269 }
9270
9271 self.transact(window, cx, |this, window, cx| {
9272 let buffer = this.buffer.update(cx, |buffer, cx| {
9273 let empty_str: Arc<str> = Arc::default();
9274 buffer.edit(
9275 edit_ranges
9276 .into_iter()
9277 .map(|range| (range, empty_str.clone())),
9278 None,
9279 cx,
9280 );
9281 buffer.snapshot(cx)
9282 });
9283 let new_selections = new_cursors
9284 .into_iter()
9285 .map(|(id, cursor)| {
9286 let cursor = cursor.to_point(&buffer);
9287 Selection {
9288 id,
9289 start: cursor,
9290 end: cursor,
9291 reversed: false,
9292 goal: SelectionGoal::None,
9293 }
9294 })
9295 .collect();
9296
9297 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9298 s.select(new_selections);
9299 });
9300 });
9301 }
9302
9303 pub fn join_lines_impl(
9304 &mut self,
9305 insert_whitespace: bool,
9306 window: &mut Window,
9307 cx: &mut Context<Self>,
9308 ) {
9309 if self.read_only(cx) {
9310 return;
9311 }
9312 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9313 for selection in self.selections.all::<Point>(cx) {
9314 let start = MultiBufferRow(selection.start.row);
9315 // Treat single line selections as if they include the next line. Otherwise this action
9316 // would do nothing for single line selections individual cursors.
9317 let end = if selection.start.row == selection.end.row {
9318 MultiBufferRow(selection.start.row + 1)
9319 } else {
9320 MultiBufferRow(selection.end.row)
9321 };
9322
9323 if let Some(last_row_range) = row_ranges.last_mut() {
9324 if start <= last_row_range.end {
9325 last_row_range.end = end;
9326 continue;
9327 }
9328 }
9329 row_ranges.push(start..end);
9330 }
9331
9332 let snapshot = self.buffer.read(cx).snapshot(cx);
9333 let mut cursor_positions = Vec::new();
9334 for row_range in &row_ranges {
9335 let anchor = snapshot.anchor_before(Point::new(
9336 row_range.end.previous_row().0,
9337 snapshot.line_len(row_range.end.previous_row()),
9338 ));
9339 cursor_positions.push(anchor..anchor);
9340 }
9341
9342 self.transact(window, cx, |this, window, cx| {
9343 for row_range in row_ranges.into_iter().rev() {
9344 for row in row_range.iter_rows().rev() {
9345 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9346 let next_line_row = row.next_row();
9347 let indent = snapshot.indent_size_for_line(next_line_row);
9348 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9349
9350 let replace =
9351 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9352 " "
9353 } else {
9354 ""
9355 };
9356
9357 this.buffer.update(cx, |buffer, cx| {
9358 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9359 });
9360 }
9361 }
9362
9363 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9364 s.select_anchor_ranges(cursor_positions)
9365 });
9366 });
9367 }
9368
9369 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9370 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9371 self.join_lines_impl(true, window, cx);
9372 }
9373
9374 pub fn sort_lines_case_sensitive(
9375 &mut self,
9376 _: &SortLinesCaseSensitive,
9377 window: &mut Window,
9378 cx: &mut Context<Self>,
9379 ) {
9380 self.manipulate_lines(window, cx, |lines| lines.sort())
9381 }
9382
9383 pub fn sort_lines_case_insensitive(
9384 &mut self,
9385 _: &SortLinesCaseInsensitive,
9386 window: &mut Window,
9387 cx: &mut Context<Self>,
9388 ) {
9389 self.manipulate_lines(window, cx, |lines| {
9390 lines.sort_by_key(|line| line.to_lowercase())
9391 })
9392 }
9393
9394 pub fn unique_lines_case_insensitive(
9395 &mut self,
9396 _: &UniqueLinesCaseInsensitive,
9397 window: &mut Window,
9398 cx: &mut Context<Self>,
9399 ) {
9400 self.manipulate_lines(window, cx, |lines| {
9401 let mut seen = HashSet::default();
9402 lines.retain(|line| seen.insert(line.to_lowercase()));
9403 })
9404 }
9405
9406 pub fn unique_lines_case_sensitive(
9407 &mut self,
9408 _: &UniqueLinesCaseSensitive,
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));
9415 })
9416 }
9417
9418 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9419 let Some(project) = self.project.clone() else {
9420 return;
9421 };
9422 self.reload(project, window, cx)
9423 .detach_and_notify_err(window, cx);
9424 }
9425
9426 pub fn restore_file(
9427 &mut self,
9428 _: &::git::RestoreFile,
9429 window: &mut Window,
9430 cx: &mut Context<Self>,
9431 ) {
9432 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9433 let mut buffer_ids = HashSet::default();
9434 let snapshot = self.buffer().read(cx).snapshot(cx);
9435 for selection in self.selections.all::<usize>(cx) {
9436 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9437 }
9438
9439 let buffer = self.buffer().read(cx);
9440 let ranges = buffer_ids
9441 .into_iter()
9442 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9443 .collect::<Vec<_>>();
9444
9445 self.restore_hunks_in_ranges(ranges, window, cx);
9446 }
9447
9448 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9449 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9450 let selections = self
9451 .selections
9452 .all(cx)
9453 .into_iter()
9454 .map(|s| s.range())
9455 .collect();
9456 self.restore_hunks_in_ranges(selections, window, cx);
9457 }
9458
9459 pub fn restore_hunks_in_ranges(
9460 &mut self,
9461 ranges: Vec<Range<Point>>,
9462 window: &mut Window,
9463 cx: &mut Context<Editor>,
9464 ) {
9465 let mut revert_changes = HashMap::default();
9466 let chunk_by = self
9467 .snapshot(window, cx)
9468 .hunks_for_ranges(ranges)
9469 .into_iter()
9470 .chunk_by(|hunk| hunk.buffer_id);
9471 for (buffer_id, hunks) in &chunk_by {
9472 let hunks = hunks.collect::<Vec<_>>();
9473 for hunk in &hunks {
9474 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9475 }
9476 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9477 }
9478 drop(chunk_by);
9479 if !revert_changes.is_empty() {
9480 self.transact(window, cx, |editor, window, cx| {
9481 editor.restore(revert_changes, window, cx);
9482 });
9483 }
9484 }
9485
9486 pub fn open_active_item_in_terminal(
9487 &mut self,
9488 _: &OpenInTerminal,
9489 window: &mut Window,
9490 cx: &mut Context<Self>,
9491 ) {
9492 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9493 let project_path = buffer.read(cx).project_path(cx)?;
9494 let project = self.project.as_ref()?.read(cx);
9495 let entry = project.entry_for_path(&project_path, cx)?;
9496 let parent = match &entry.canonical_path {
9497 Some(canonical_path) => canonical_path.to_path_buf(),
9498 None => project.absolute_path(&project_path, cx)?,
9499 }
9500 .parent()?
9501 .to_path_buf();
9502 Some(parent)
9503 }) {
9504 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9505 }
9506 }
9507
9508 fn set_breakpoint_context_menu(
9509 &mut self,
9510 display_row: DisplayRow,
9511 position: Option<Anchor>,
9512 clicked_point: gpui::Point<Pixels>,
9513 window: &mut Window,
9514 cx: &mut Context<Self>,
9515 ) {
9516 if !cx.has_flag::<DebuggerFeatureFlag>() {
9517 return;
9518 }
9519 let source = self
9520 .buffer
9521 .read(cx)
9522 .snapshot(cx)
9523 .anchor_before(Point::new(display_row.0, 0u32));
9524
9525 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9526
9527 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9528 self,
9529 source,
9530 clicked_point,
9531 context_menu,
9532 window,
9533 cx,
9534 );
9535 }
9536
9537 fn add_edit_breakpoint_block(
9538 &mut self,
9539 anchor: Anchor,
9540 breakpoint: &Breakpoint,
9541 edit_action: BreakpointPromptEditAction,
9542 window: &mut Window,
9543 cx: &mut Context<Self>,
9544 ) {
9545 let weak_editor = cx.weak_entity();
9546 let bp_prompt = cx.new(|cx| {
9547 BreakpointPromptEditor::new(
9548 weak_editor,
9549 anchor,
9550 breakpoint.clone(),
9551 edit_action,
9552 window,
9553 cx,
9554 )
9555 });
9556
9557 let height = bp_prompt.update(cx, |this, cx| {
9558 this.prompt
9559 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9560 });
9561 let cloned_prompt = bp_prompt.clone();
9562 let blocks = vec![BlockProperties {
9563 style: BlockStyle::Sticky,
9564 placement: BlockPlacement::Above(anchor),
9565 height: Some(height),
9566 render: Arc::new(move |cx| {
9567 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9568 cloned_prompt.clone().into_any_element()
9569 }),
9570 priority: 0,
9571 render_in_minimap: true,
9572 }];
9573
9574 let focus_handle = bp_prompt.focus_handle(cx);
9575 window.focus(&focus_handle);
9576
9577 let block_ids = self.insert_blocks(blocks, None, cx);
9578 bp_prompt.update(cx, |prompt, _| {
9579 prompt.add_block_ids(block_ids);
9580 });
9581 }
9582
9583 pub(crate) fn breakpoint_at_row(
9584 &self,
9585 row: u32,
9586 window: &mut Window,
9587 cx: &mut Context<Self>,
9588 ) -> Option<(Anchor, Breakpoint)> {
9589 let snapshot = self.snapshot(window, cx);
9590 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9591
9592 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9593 }
9594
9595 pub(crate) fn breakpoint_at_anchor(
9596 &self,
9597 breakpoint_position: Anchor,
9598 snapshot: &EditorSnapshot,
9599 cx: &mut Context<Self>,
9600 ) -> Option<(Anchor, Breakpoint)> {
9601 let project = self.project.clone()?;
9602
9603 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9604 snapshot
9605 .buffer_snapshot
9606 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9607 })?;
9608
9609 let enclosing_excerpt = breakpoint_position.excerpt_id;
9610 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9611 let buffer_snapshot = buffer.read(cx).snapshot();
9612
9613 let row = buffer_snapshot
9614 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9615 .row;
9616
9617 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9618 let anchor_end = snapshot
9619 .buffer_snapshot
9620 .anchor_after(Point::new(row, line_len));
9621
9622 let bp = self
9623 .breakpoint_store
9624 .as_ref()?
9625 .read_with(cx, |breakpoint_store, cx| {
9626 breakpoint_store
9627 .breakpoints(
9628 &buffer,
9629 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9630 &buffer_snapshot,
9631 cx,
9632 )
9633 .next()
9634 .and_then(|(anchor, bp)| {
9635 let breakpoint_row = buffer_snapshot
9636 .summary_for_anchor::<text::PointUtf16>(anchor)
9637 .row;
9638
9639 if breakpoint_row == row {
9640 snapshot
9641 .buffer_snapshot
9642 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9643 .map(|anchor| (anchor, bp.clone()))
9644 } else {
9645 None
9646 }
9647 })
9648 });
9649 bp
9650 }
9651
9652 pub fn edit_log_breakpoint(
9653 &mut self,
9654 _: &EditLogBreakpoint,
9655 window: &mut Window,
9656 cx: &mut Context<Self>,
9657 ) {
9658 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9659 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9660 message: None,
9661 state: BreakpointState::Enabled,
9662 condition: None,
9663 hit_condition: None,
9664 });
9665
9666 self.add_edit_breakpoint_block(
9667 anchor,
9668 &breakpoint,
9669 BreakpointPromptEditAction::Log,
9670 window,
9671 cx,
9672 );
9673 }
9674 }
9675
9676 fn breakpoints_at_cursors(
9677 &self,
9678 window: &mut Window,
9679 cx: &mut Context<Self>,
9680 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9681 let snapshot = self.snapshot(window, cx);
9682 let cursors = self
9683 .selections
9684 .disjoint_anchors()
9685 .into_iter()
9686 .map(|selection| {
9687 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9688
9689 let breakpoint_position = self
9690 .breakpoint_at_row(cursor_position.row, window, cx)
9691 .map(|bp| bp.0)
9692 .unwrap_or_else(|| {
9693 snapshot
9694 .display_snapshot
9695 .buffer_snapshot
9696 .anchor_after(Point::new(cursor_position.row, 0))
9697 });
9698
9699 let breakpoint = self
9700 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9701 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9702
9703 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9704 })
9705 // 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.
9706 .collect::<HashMap<Anchor, _>>();
9707
9708 cursors.into_iter().collect()
9709 }
9710
9711 pub fn enable_breakpoint(
9712 &mut self,
9713 _: &crate::actions::EnableBreakpoint,
9714 window: &mut Window,
9715 cx: &mut Context<Self>,
9716 ) {
9717 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9718 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9719 continue;
9720 };
9721 self.edit_breakpoint_at_anchor(
9722 anchor,
9723 breakpoint,
9724 BreakpointEditAction::InvertState,
9725 cx,
9726 );
9727 }
9728 }
9729
9730 pub fn disable_breakpoint(
9731 &mut self,
9732 _: &crate::actions::DisableBreakpoint,
9733 window: &mut Window,
9734 cx: &mut Context<Self>,
9735 ) {
9736 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9737 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9738 continue;
9739 };
9740 self.edit_breakpoint_at_anchor(
9741 anchor,
9742 breakpoint,
9743 BreakpointEditAction::InvertState,
9744 cx,
9745 );
9746 }
9747 }
9748
9749 pub fn toggle_breakpoint(
9750 &mut self,
9751 _: &crate::actions::ToggleBreakpoint,
9752 window: &mut Window,
9753 cx: &mut Context<Self>,
9754 ) {
9755 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9756 if let Some(breakpoint) = breakpoint {
9757 self.edit_breakpoint_at_anchor(
9758 anchor,
9759 breakpoint,
9760 BreakpointEditAction::Toggle,
9761 cx,
9762 );
9763 } else {
9764 self.edit_breakpoint_at_anchor(
9765 anchor,
9766 Breakpoint::new_standard(),
9767 BreakpointEditAction::Toggle,
9768 cx,
9769 );
9770 }
9771 }
9772 }
9773
9774 pub fn edit_breakpoint_at_anchor(
9775 &mut self,
9776 breakpoint_position: Anchor,
9777 breakpoint: Breakpoint,
9778 edit_action: BreakpointEditAction,
9779 cx: &mut Context<Self>,
9780 ) {
9781 let Some(breakpoint_store) = &self.breakpoint_store else {
9782 return;
9783 };
9784
9785 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9786 if breakpoint_position == Anchor::min() {
9787 self.buffer()
9788 .read(cx)
9789 .excerpt_buffer_ids()
9790 .into_iter()
9791 .next()
9792 } else {
9793 None
9794 }
9795 }) else {
9796 return;
9797 };
9798
9799 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9800 return;
9801 };
9802
9803 breakpoint_store.update(cx, |breakpoint_store, cx| {
9804 breakpoint_store.toggle_breakpoint(
9805 buffer,
9806 (breakpoint_position.text_anchor, breakpoint),
9807 edit_action,
9808 cx,
9809 );
9810 });
9811
9812 cx.notify();
9813 }
9814
9815 #[cfg(any(test, feature = "test-support"))]
9816 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9817 self.breakpoint_store.clone()
9818 }
9819
9820 pub fn prepare_restore_change(
9821 &self,
9822 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9823 hunk: &MultiBufferDiffHunk,
9824 cx: &mut App,
9825 ) -> Option<()> {
9826 if hunk.is_created_file() {
9827 return None;
9828 }
9829 let buffer = self.buffer.read(cx);
9830 let diff = buffer.diff_for(hunk.buffer_id)?;
9831 let buffer = buffer.buffer(hunk.buffer_id)?;
9832 let buffer = buffer.read(cx);
9833 let original_text = diff
9834 .read(cx)
9835 .base_text()
9836 .as_rope()
9837 .slice(hunk.diff_base_byte_range.clone());
9838 let buffer_snapshot = buffer.snapshot();
9839 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9840 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9841 probe
9842 .0
9843 .start
9844 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9845 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9846 }) {
9847 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9848 Some(())
9849 } else {
9850 None
9851 }
9852 }
9853
9854 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9855 self.manipulate_lines(window, cx, |lines| lines.reverse())
9856 }
9857
9858 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9859 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9860 }
9861
9862 fn manipulate_lines<Fn>(
9863 &mut self,
9864 window: &mut Window,
9865 cx: &mut Context<Self>,
9866 mut callback: Fn,
9867 ) where
9868 Fn: FnMut(&mut Vec<&str>),
9869 {
9870 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9871
9872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9873 let buffer = self.buffer.read(cx).snapshot(cx);
9874
9875 let mut edits = Vec::new();
9876
9877 let selections = self.selections.all::<Point>(cx);
9878 let mut selections = selections.iter().peekable();
9879 let mut contiguous_row_selections = Vec::new();
9880 let mut new_selections = Vec::new();
9881 let mut added_lines = 0;
9882 let mut removed_lines = 0;
9883
9884 while let Some(selection) = selections.next() {
9885 let (start_row, end_row) = consume_contiguous_rows(
9886 &mut contiguous_row_selections,
9887 selection,
9888 &display_map,
9889 &mut selections,
9890 );
9891
9892 let start_point = Point::new(start_row.0, 0);
9893 let end_point = Point::new(
9894 end_row.previous_row().0,
9895 buffer.line_len(end_row.previous_row()),
9896 );
9897 let text = buffer
9898 .text_for_range(start_point..end_point)
9899 .collect::<String>();
9900
9901 let mut lines = text.split('\n').collect_vec();
9902
9903 let lines_before = lines.len();
9904 callback(&mut lines);
9905 let lines_after = lines.len();
9906
9907 edits.push((start_point..end_point, lines.join("\n")));
9908
9909 // Selections must change based on added and removed line count
9910 let start_row =
9911 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9912 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9913 new_selections.push(Selection {
9914 id: selection.id,
9915 start: start_row,
9916 end: end_row,
9917 goal: SelectionGoal::None,
9918 reversed: selection.reversed,
9919 });
9920
9921 if lines_after > lines_before {
9922 added_lines += lines_after - lines_before;
9923 } else if lines_before > lines_after {
9924 removed_lines += lines_before - lines_after;
9925 }
9926 }
9927
9928 self.transact(window, cx, |this, window, cx| {
9929 let buffer = this.buffer.update(cx, |buffer, cx| {
9930 buffer.edit(edits, None, cx);
9931 buffer.snapshot(cx)
9932 });
9933
9934 // Recalculate offsets on newly edited buffer
9935 let new_selections = new_selections
9936 .iter()
9937 .map(|s| {
9938 let start_point = Point::new(s.start.0, 0);
9939 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9940 Selection {
9941 id: s.id,
9942 start: buffer.point_to_offset(start_point),
9943 end: buffer.point_to_offset(end_point),
9944 goal: s.goal,
9945 reversed: s.reversed,
9946 }
9947 })
9948 .collect();
9949
9950 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9951 s.select(new_selections);
9952 });
9953
9954 this.request_autoscroll(Autoscroll::fit(), cx);
9955 });
9956 }
9957
9958 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9959 self.manipulate_text(window, cx, |text| {
9960 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9961 if has_upper_case_characters {
9962 text.to_lowercase()
9963 } else {
9964 text.to_uppercase()
9965 }
9966 })
9967 }
9968
9969 pub fn convert_to_upper_case(
9970 &mut self,
9971 _: &ConvertToUpperCase,
9972 window: &mut Window,
9973 cx: &mut Context<Self>,
9974 ) {
9975 self.manipulate_text(window, cx, |text| text.to_uppercase())
9976 }
9977
9978 pub fn convert_to_lower_case(
9979 &mut self,
9980 _: &ConvertToLowerCase,
9981 window: &mut Window,
9982 cx: &mut Context<Self>,
9983 ) {
9984 self.manipulate_text(window, cx, |text| text.to_lowercase())
9985 }
9986
9987 pub fn convert_to_title_case(
9988 &mut self,
9989 _: &ConvertToTitleCase,
9990 window: &mut Window,
9991 cx: &mut Context<Self>,
9992 ) {
9993 self.manipulate_text(window, cx, |text| {
9994 text.split('\n')
9995 .map(|line| line.to_case(Case::Title))
9996 .join("\n")
9997 })
9998 }
9999
10000 pub fn convert_to_snake_case(
10001 &mut self,
10002 _: &ConvertToSnakeCase,
10003 window: &mut Window,
10004 cx: &mut Context<Self>,
10005 ) {
10006 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10007 }
10008
10009 pub fn convert_to_kebab_case(
10010 &mut self,
10011 _: &ConvertToKebabCase,
10012 window: &mut Window,
10013 cx: &mut Context<Self>,
10014 ) {
10015 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10016 }
10017
10018 pub fn convert_to_upper_camel_case(
10019 &mut self,
10020 _: &ConvertToUpperCamelCase,
10021 window: &mut Window,
10022 cx: &mut Context<Self>,
10023 ) {
10024 self.manipulate_text(window, cx, |text| {
10025 text.split('\n')
10026 .map(|line| line.to_case(Case::UpperCamel))
10027 .join("\n")
10028 })
10029 }
10030
10031 pub fn convert_to_lower_camel_case(
10032 &mut self,
10033 _: &ConvertToLowerCamelCase,
10034 window: &mut Window,
10035 cx: &mut Context<Self>,
10036 ) {
10037 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10038 }
10039
10040 pub fn convert_to_opposite_case(
10041 &mut self,
10042 _: &ConvertToOppositeCase,
10043 window: &mut Window,
10044 cx: &mut Context<Self>,
10045 ) {
10046 self.manipulate_text(window, cx, |text| {
10047 text.chars()
10048 .fold(String::with_capacity(text.len()), |mut t, c| {
10049 if c.is_uppercase() {
10050 t.extend(c.to_lowercase());
10051 } else {
10052 t.extend(c.to_uppercase());
10053 }
10054 t
10055 })
10056 })
10057 }
10058
10059 pub fn convert_to_rot13(
10060 &mut self,
10061 _: &ConvertToRot13,
10062 window: &mut Window,
10063 cx: &mut Context<Self>,
10064 ) {
10065 self.manipulate_text(window, cx, |text| {
10066 text.chars()
10067 .map(|c| match c {
10068 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10069 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10070 _ => c,
10071 })
10072 .collect()
10073 })
10074 }
10075
10076 pub fn convert_to_rot47(
10077 &mut self,
10078 _: &ConvertToRot47,
10079 window: &mut Window,
10080 cx: &mut Context<Self>,
10081 ) {
10082 self.manipulate_text(window, cx, |text| {
10083 text.chars()
10084 .map(|c| {
10085 let code_point = c as u32;
10086 if code_point >= 33 && code_point <= 126 {
10087 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10088 }
10089 c
10090 })
10091 .collect()
10092 })
10093 }
10094
10095 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10096 where
10097 Fn: FnMut(&str) -> String,
10098 {
10099 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10100 let buffer = self.buffer.read(cx).snapshot(cx);
10101
10102 let mut new_selections = Vec::new();
10103 let mut edits = Vec::new();
10104 let mut selection_adjustment = 0i32;
10105
10106 for selection in self.selections.all::<usize>(cx) {
10107 let selection_is_empty = selection.is_empty();
10108
10109 let (start, end) = if selection_is_empty {
10110 let word_range = movement::surrounding_word(
10111 &display_map,
10112 selection.start.to_display_point(&display_map),
10113 );
10114 let start = word_range.start.to_offset(&display_map, Bias::Left);
10115 let end = word_range.end.to_offset(&display_map, Bias::Left);
10116 (start, end)
10117 } else {
10118 (selection.start, selection.end)
10119 };
10120
10121 let text = buffer.text_for_range(start..end).collect::<String>();
10122 let old_length = text.len() as i32;
10123 let text = callback(&text);
10124
10125 new_selections.push(Selection {
10126 start: (start as i32 - selection_adjustment) as usize,
10127 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10128 goal: SelectionGoal::None,
10129 ..selection
10130 });
10131
10132 selection_adjustment += old_length - text.len() as i32;
10133
10134 edits.push((start..end, text));
10135 }
10136
10137 self.transact(window, cx, |this, window, cx| {
10138 this.buffer.update(cx, |buffer, cx| {
10139 buffer.edit(edits, None, cx);
10140 });
10141
10142 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10143 s.select(new_selections);
10144 });
10145
10146 this.request_autoscroll(Autoscroll::fit(), cx);
10147 });
10148 }
10149
10150 pub fn duplicate(
10151 &mut self,
10152 upwards: bool,
10153 whole_lines: bool,
10154 window: &mut Window,
10155 cx: &mut Context<Self>,
10156 ) {
10157 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10158
10159 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10160 let buffer = &display_map.buffer_snapshot;
10161 let selections = self.selections.all::<Point>(cx);
10162
10163 let mut edits = Vec::new();
10164 let mut selections_iter = selections.iter().peekable();
10165 while let Some(selection) = selections_iter.next() {
10166 let mut rows = selection.spanned_rows(false, &display_map);
10167 // duplicate line-wise
10168 if whole_lines || selection.start == selection.end {
10169 // Avoid duplicating the same lines twice.
10170 while let Some(next_selection) = selections_iter.peek() {
10171 let next_rows = next_selection.spanned_rows(false, &display_map);
10172 if next_rows.start < rows.end {
10173 rows.end = next_rows.end;
10174 selections_iter.next().unwrap();
10175 } else {
10176 break;
10177 }
10178 }
10179
10180 // Copy the text from the selected row region and splice it either at the start
10181 // or end of the region.
10182 let start = Point::new(rows.start.0, 0);
10183 let end = Point::new(
10184 rows.end.previous_row().0,
10185 buffer.line_len(rows.end.previous_row()),
10186 );
10187 let text = buffer
10188 .text_for_range(start..end)
10189 .chain(Some("\n"))
10190 .collect::<String>();
10191 let insert_location = if upwards {
10192 Point::new(rows.end.0, 0)
10193 } else {
10194 start
10195 };
10196 edits.push((insert_location..insert_location, text));
10197 } else {
10198 // duplicate character-wise
10199 let start = selection.start;
10200 let end = selection.end;
10201 let text = buffer.text_for_range(start..end).collect::<String>();
10202 edits.push((selection.end..selection.end, text));
10203 }
10204 }
10205
10206 self.transact(window, cx, |this, _, cx| {
10207 this.buffer.update(cx, |buffer, cx| {
10208 buffer.edit(edits, None, cx);
10209 });
10210
10211 this.request_autoscroll(Autoscroll::fit(), cx);
10212 });
10213 }
10214
10215 pub fn duplicate_line_up(
10216 &mut self,
10217 _: &DuplicateLineUp,
10218 window: &mut Window,
10219 cx: &mut Context<Self>,
10220 ) {
10221 self.duplicate(true, true, window, cx);
10222 }
10223
10224 pub fn duplicate_line_down(
10225 &mut self,
10226 _: &DuplicateLineDown,
10227 window: &mut Window,
10228 cx: &mut Context<Self>,
10229 ) {
10230 self.duplicate(false, true, window, cx);
10231 }
10232
10233 pub fn duplicate_selection(
10234 &mut self,
10235 _: &DuplicateSelection,
10236 window: &mut Window,
10237 cx: &mut Context<Self>,
10238 ) {
10239 self.duplicate(false, false, window, cx);
10240 }
10241
10242 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10243 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10244
10245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10246 let buffer = self.buffer.read(cx).snapshot(cx);
10247
10248 let mut edits = Vec::new();
10249 let mut unfold_ranges = Vec::new();
10250 let mut refold_creases = Vec::new();
10251
10252 let selections = self.selections.all::<Point>(cx);
10253 let mut selections = selections.iter().peekable();
10254 let mut contiguous_row_selections = Vec::new();
10255 let mut new_selections = Vec::new();
10256
10257 while let Some(selection) = selections.next() {
10258 // Find all the selections that span a contiguous row range
10259 let (start_row, end_row) = consume_contiguous_rows(
10260 &mut contiguous_row_selections,
10261 selection,
10262 &display_map,
10263 &mut selections,
10264 );
10265
10266 // Move the text spanned by the row range to be before the line preceding the row range
10267 if start_row.0 > 0 {
10268 let range_to_move = Point::new(
10269 start_row.previous_row().0,
10270 buffer.line_len(start_row.previous_row()),
10271 )
10272 ..Point::new(
10273 end_row.previous_row().0,
10274 buffer.line_len(end_row.previous_row()),
10275 );
10276 let insertion_point = display_map
10277 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10278 .0;
10279
10280 // Don't move lines across excerpts
10281 if buffer
10282 .excerpt_containing(insertion_point..range_to_move.end)
10283 .is_some()
10284 {
10285 let text = buffer
10286 .text_for_range(range_to_move.clone())
10287 .flat_map(|s| s.chars())
10288 .skip(1)
10289 .chain(['\n'])
10290 .collect::<String>();
10291
10292 edits.push((
10293 buffer.anchor_after(range_to_move.start)
10294 ..buffer.anchor_before(range_to_move.end),
10295 String::new(),
10296 ));
10297 let insertion_anchor = buffer.anchor_after(insertion_point);
10298 edits.push((insertion_anchor..insertion_anchor, text));
10299
10300 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10301
10302 // Move selections up
10303 new_selections.extend(contiguous_row_selections.drain(..).map(
10304 |mut selection| {
10305 selection.start.row -= row_delta;
10306 selection.end.row -= row_delta;
10307 selection
10308 },
10309 ));
10310
10311 // Move folds up
10312 unfold_ranges.push(range_to_move.clone());
10313 for fold in display_map.folds_in_range(
10314 buffer.anchor_before(range_to_move.start)
10315 ..buffer.anchor_after(range_to_move.end),
10316 ) {
10317 let mut start = fold.range.start.to_point(&buffer);
10318 let mut end = fold.range.end.to_point(&buffer);
10319 start.row -= row_delta;
10320 end.row -= row_delta;
10321 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10322 }
10323 }
10324 }
10325
10326 // If we didn't move line(s), preserve the existing selections
10327 new_selections.append(&mut contiguous_row_selections);
10328 }
10329
10330 self.transact(window, cx, |this, window, cx| {
10331 this.unfold_ranges(&unfold_ranges, true, true, cx);
10332 this.buffer.update(cx, |buffer, cx| {
10333 for (range, text) in edits {
10334 buffer.edit([(range, text)], None, cx);
10335 }
10336 });
10337 this.fold_creases(refold_creases, true, window, cx);
10338 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10339 s.select(new_selections);
10340 })
10341 });
10342 }
10343
10344 pub fn move_line_down(
10345 &mut self,
10346 _: &MoveLineDown,
10347 window: &mut Window,
10348 cx: &mut Context<Self>,
10349 ) {
10350 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10351
10352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10353 let buffer = self.buffer.read(cx).snapshot(cx);
10354
10355 let mut edits = Vec::new();
10356 let mut unfold_ranges = Vec::new();
10357 let mut refold_creases = Vec::new();
10358
10359 let selections = self.selections.all::<Point>(cx);
10360 let mut selections = selections.iter().peekable();
10361 let mut contiguous_row_selections = Vec::new();
10362 let mut new_selections = Vec::new();
10363
10364 while let Some(selection) = selections.next() {
10365 // Find all the selections that span a contiguous row range
10366 let (start_row, end_row) = consume_contiguous_rows(
10367 &mut contiguous_row_selections,
10368 selection,
10369 &display_map,
10370 &mut selections,
10371 );
10372
10373 // Move the text spanned by the row range to be after the last line of the row range
10374 if end_row.0 <= buffer.max_point().row {
10375 let range_to_move =
10376 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10377 let insertion_point = display_map
10378 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10379 .0;
10380
10381 // Don't move lines across excerpt boundaries
10382 if buffer
10383 .excerpt_containing(range_to_move.start..insertion_point)
10384 .is_some()
10385 {
10386 let mut text = String::from("\n");
10387 text.extend(buffer.text_for_range(range_to_move.clone()));
10388 text.pop(); // Drop trailing newline
10389 edits.push((
10390 buffer.anchor_after(range_to_move.start)
10391 ..buffer.anchor_before(range_to_move.end),
10392 String::new(),
10393 ));
10394 let insertion_anchor = buffer.anchor_after(insertion_point);
10395 edits.push((insertion_anchor..insertion_anchor, text));
10396
10397 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10398
10399 // Move selections down
10400 new_selections.extend(contiguous_row_selections.drain(..).map(
10401 |mut selection| {
10402 selection.start.row += row_delta;
10403 selection.end.row += row_delta;
10404 selection
10405 },
10406 ));
10407
10408 // Move folds down
10409 unfold_ranges.push(range_to_move.clone());
10410 for fold in display_map.folds_in_range(
10411 buffer.anchor_before(range_to_move.start)
10412 ..buffer.anchor_after(range_to_move.end),
10413 ) {
10414 let mut start = fold.range.start.to_point(&buffer);
10415 let mut end = fold.range.end.to_point(&buffer);
10416 start.row += row_delta;
10417 end.row += row_delta;
10418 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10419 }
10420 }
10421 }
10422
10423 // If we didn't move line(s), preserve the existing selections
10424 new_selections.append(&mut contiguous_row_selections);
10425 }
10426
10427 self.transact(window, cx, |this, window, cx| {
10428 this.unfold_ranges(&unfold_ranges, true, true, cx);
10429 this.buffer.update(cx, |buffer, cx| {
10430 for (range, text) in edits {
10431 buffer.edit([(range, text)], None, cx);
10432 }
10433 });
10434 this.fold_creases(refold_creases, true, window, cx);
10435 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10436 s.select(new_selections)
10437 });
10438 });
10439 }
10440
10441 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10442 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10443 let text_layout_details = &self.text_layout_details(window);
10444 self.transact(window, cx, |this, window, cx| {
10445 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10446 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10447 s.move_with(|display_map, selection| {
10448 if !selection.is_empty() {
10449 return;
10450 }
10451
10452 let mut head = selection.head();
10453 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10454 if head.column() == display_map.line_len(head.row()) {
10455 transpose_offset = display_map
10456 .buffer_snapshot
10457 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10458 }
10459
10460 if transpose_offset == 0 {
10461 return;
10462 }
10463
10464 *head.column_mut() += 1;
10465 head = display_map.clip_point(head, Bias::Right);
10466 let goal = SelectionGoal::HorizontalPosition(
10467 display_map
10468 .x_for_display_point(head, text_layout_details)
10469 .into(),
10470 );
10471 selection.collapse_to(head, goal);
10472
10473 let transpose_start = display_map
10474 .buffer_snapshot
10475 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10476 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10477 let transpose_end = display_map
10478 .buffer_snapshot
10479 .clip_offset(transpose_offset + 1, Bias::Right);
10480 if let Some(ch) =
10481 display_map.buffer_snapshot.chars_at(transpose_start).next()
10482 {
10483 edits.push((transpose_start..transpose_offset, String::new()));
10484 edits.push((transpose_end..transpose_end, ch.to_string()));
10485 }
10486 }
10487 });
10488 edits
10489 });
10490 this.buffer
10491 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10492 let selections = this.selections.all::<usize>(cx);
10493 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10494 s.select(selections);
10495 });
10496 });
10497 }
10498
10499 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10500 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10501 self.rewrap_impl(RewrapOptions::default(), cx)
10502 }
10503
10504 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10505 let buffer = self.buffer.read(cx).snapshot(cx);
10506 let selections = self.selections.all::<Point>(cx);
10507 let mut selections = selections.iter().peekable();
10508
10509 let mut edits = Vec::new();
10510 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10511
10512 while let Some(selection) = selections.next() {
10513 let mut start_row = selection.start.row;
10514 let mut end_row = selection.end.row;
10515
10516 // Skip selections that overlap with a range that has already been rewrapped.
10517 let selection_range = start_row..end_row;
10518 if rewrapped_row_ranges
10519 .iter()
10520 .any(|range| range.overlaps(&selection_range))
10521 {
10522 continue;
10523 }
10524
10525 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10526
10527 // Since not all lines in the selection may be at the same indent
10528 // level, choose the indent size that is the most common between all
10529 // of the lines.
10530 //
10531 // If there is a tie, we use the deepest indent.
10532 let (indent_size, indent_end) = {
10533 let mut indent_size_occurrences = HashMap::default();
10534 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10535
10536 for row in start_row..=end_row {
10537 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10538 rows_by_indent_size.entry(indent).or_default().push(row);
10539 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10540 }
10541
10542 let indent_size = indent_size_occurrences
10543 .into_iter()
10544 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10545 .map(|(indent, _)| indent)
10546 .unwrap_or_default();
10547 let row = rows_by_indent_size[&indent_size][0];
10548 let indent_end = Point::new(row, indent_size.len);
10549
10550 (indent_size, indent_end)
10551 };
10552
10553 let mut line_prefix = indent_size.chars().collect::<String>();
10554
10555 let mut inside_comment = false;
10556 if let Some(comment_prefix) =
10557 buffer
10558 .language_scope_at(selection.head())
10559 .and_then(|language| {
10560 language
10561 .line_comment_prefixes()
10562 .iter()
10563 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10564 .cloned()
10565 })
10566 {
10567 line_prefix.push_str(&comment_prefix);
10568 inside_comment = true;
10569 }
10570
10571 let language_settings = buffer.language_settings_at(selection.head(), cx);
10572 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10573 RewrapBehavior::InComments => inside_comment,
10574 RewrapBehavior::InSelections => !selection.is_empty(),
10575 RewrapBehavior::Anywhere => true,
10576 };
10577
10578 let should_rewrap = options.override_language_settings
10579 || allow_rewrap_based_on_language
10580 || self.hard_wrap.is_some();
10581 if !should_rewrap {
10582 continue;
10583 }
10584
10585 if selection.is_empty() {
10586 'expand_upwards: while start_row > 0 {
10587 let prev_row = start_row - 1;
10588 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10589 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10590 {
10591 start_row = prev_row;
10592 } else {
10593 break 'expand_upwards;
10594 }
10595 }
10596
10597 'expand_downwards: while end_row < buffer.max_point().row {
10598 let next_row = end_row + 1;
10599 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10600 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10601 {
10602 end_row = next_row;
10603 } else {
10604 break 'expand_downwards;
10605 }
10606 }
10607 }
10608
10609 let start = Point::new(start_row, 0);
10610 let start_offset = start.to_offset(&buffer);
10611 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10612 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10613 let Some(lines_without_prefixes) = selection_text
10614 .lines()
10615 .map(|line| {
10616 line.strip_prefix(&line_prefix)
10617 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10618 .ok_or_else(|| {
10619 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10620 })
10621 })
10622 .collect::<Result<Vec<_>, _>>()
10623 .log_err()
10624 else {
10625 continue;
10626 };
10627
10628 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10629 buffer
10630 .language_settings_at(Point::new(start_row, 0), cx)
10631 .preferred_line_length as usize
10632 });
10633 let wrapped_text = wrap_with_prefix(
10634 line_prefix,
10635 lines_without_prefixes.join("\n"),
10636 wrap_column,
10637 tab_size,
10638 options.preserve_existing_whitespace,
10639 );
10640
10641 // TODO: should always use char-based diff while still supporting cursor behavior that
10642 // matches vim.
10643 let mut diff_options = DiffOptions::default();
10644 if options.override_language_settings {
10645 diff_options.max_word_diff_len = 0;
10646 diff_options.max_word_diff_line_count = 0;
10647 } else {
10648 diff_options.max_word_diff_len = usize::MAX;
10649 diff_options.max_word_diff_line_count = usize::MAX;
10650 }
10651
10652 for (old_range, new_text) in
10653 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10654 {
10655 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10656 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10657 edits.push((edit_start..edit_end, new_text));
10658 }
10659
10660 rewrapped_row_ranges.push(start_row..=end_row);
10661 }
10662
10663 self.buffer
10664 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10665 }
10666
10667 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10668 let mut text = String::new();
10669 let buffer = self.buffer.read(cx).snapshot(cx);
10670 let mut selections = self.selections.all::<Point>(cx);
10671 let mut clipboard_selections = Vec::with_capacity(selections.len());
10672 {
10673 let max_point = buffer.max_point();
10674 let mut is_first = true;
10675 for selection in &mut selections {
10676 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10677 if is_entire_line {
10678 selection.start = Point::new(selection.start.row, 0);
10679 if !selection.is_empty() && selection.end.column == 0 {
10680 selection.end = cmp::min(max_point, selection.end);
10681 } else {
10682 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10683 }
10684 selection.goal = SelectionGoal::None;
10685 }
10686 if is_first {
10687 is_first = false;
10688 } else {
10689 text += "\n";
10690 }
10691 let mut len = 0;
10692 for chunk in buffer.text_for_range(selection.start..selection.end) {
10693 text.push_str(chunk);
10694 len += chunk.len();
10695 }
10696 clipboard_selections.push(ClipboardSelection {
10697 len,
10698 is_entire_line,
10699 first_line_indent: buffer
10700 .indent_size_for_line(MultiBufferRow(selection.start.row))
10701 .len,
10702 });
10703 }
10704 }
10705
10706 self.transact(window, cx, |this, window, cx| {
10707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10708 s.select(selections);
10709 });
10710 this.insert("", window, cx);
10711 });
10712 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10713 }
10714
10715 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10716 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10717 let item = self.cut_common(window, cx);
10718 cx.write_to_clipboard(item);
10719 }
10720
10721 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10722 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10723 self.change_selections(None, window, cx, |s| {
10724 s.move_with(|snapshot, sel| {
10725 if sel.is_empty() {
10726 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10727 }
10728 });
10729 });
10730 let item = self.cut_common(window, cx);
10731 cx.set_global(KillRing(item))
10732 }
10733
10734 pub fn kill_ring_yank(
10735 &mut self,
10736 _: &KillRingYank,
10737 window: &mut Window,
10738 cx: &mut Context<Self>,
10739 ) {
10740 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10741 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10742 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10743 (kill_ring.text().to_string(), kill_ring.metadata_json())
10744 } else {
10745 return;
10746 }
10747 } else {
10748 return;
10749 };
10750 self.do_paste(&text, metadata, false, window, cx);
10751 }
10752
10753 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10754 self.do_copy(true, cx);
10755 }
10756
10757 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10758 self.do_copy(false, cx);
10759 }
10760
10761 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10762 let selections = self.selections.all::<Point>(cx);
10763 let buffer = self.buffer.read(cx).read(cx);
10764 let mut text = String::new();
10765
10766 let mut clipboard_selections = Vec::with_capacity(selections.len());
10767 {
10768 let max_point = buffer.max_point();
10769 let mut is_first = true;
10770 for selection in &selections {
10771 let mut start = selection.start;
10772 let mut end = selection.end;
10773 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10774 if is_entire_line {
10775 start = Point::new(start.row, 0);
10776 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10777 }
10778
10779 let mut trimmed_selections = Vec::new();
10780 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10781 let row = MultiBufferRow(start.row);
10782 let first_indent = buffer.indent_size_for_line(row);
10783 if first_indent.len == 0 || start.column > first_indent.len {
10784 trimmed_selections.push(start..end);
10785 } else {
10786 trimmed_selections.push(
10787 Point::new(row.0, first_indent.len)
10788 ..Point::new(row.0, buffer.line_len(row)),
10789 );
10790 for row in start.row + 1..=end.row {
10791 let mut line_len = buffer.line_len(MultiBufferRow(row));
10792 if row == end.row {
10793 line_len = end.column;
10794 }
10795 if line_len == 0 {
10796 trimmed_selections
10797 .push(Point::new(row, 0)..Point::new(row, line_len));
10798 continue;
10799 }
10800 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10801 if row_indent_size.len >= first_indent.len {
10802 trimmed_selections.push(
10803 Point::new(row, first_indent.len)..Point::new(row, line_len),
10804 );
10805 } else {
10806 trimmed_selections.clear();
10807 trimmed_selections.push(start..end);
10808 break;
10809 }
10810 }
10811 }
10812 } else {
10813 trimmed_selections.push(start..end);
10814 }
10815
10816 for trimmed_range in trimmed_selections {
10817 if is_first {
10818 is_first = false;
10819 } else {
10820 text += "\n";
10821 }
10822 let mut len = 0;
10823 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10824 text.push_str(chunk);
10825 len += chunk.len();
10826 }
10827 clipboard_selections.push(ClipboardSelection {
10828 len,
10829 is_entire_line,
10830 first_line_indent: buffer
10831 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10832 .len,
10833 });
10834 }
10835 }
10836 }
10837
10838 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10839 text,
10840 clipboard_selections,
10841 ));
10842 }
10843
10844 pub fn do_paste(
10845 &mut self,
10846 text: &String,
10847 clipboard_selections: Option<Vec<ClipboardSelection>>,
10848 handle_entire_lines: bool,
10849 window: &mut Window,
10850 cx: &mut Context<Self>,
10851 ) {
10852 if self.read_only(cx) {
10853 return;
10854 }
10855
10856 let clipboard_text = Cow::Borrowed(text);
10857
10858 self.transact(window, cx, |this, window, cx| {
10859 if let Some(mut clipboard_selections) = clipboard_selections {
10860 let old_selections = this.selections.all::<usize>(cx);
10861 let all_selections_were_entire_line =
10862 clipboard_selections.iter().all(|s| s.is_entire_line);
10863 let first_selection_indent_column =
10864 clipboard_selections.first().map(|s| s.first_line_indent);
10865 if clipboard_selections.len() != old_selections.len() {
10866 clipboard_selections.drain(..);
10867 }
10868 let cursor_offset = this.selections.last::<usize>(cx).head();
10869 let mut auto_indent_on_paste = true;
10870
10871 this.buffer.update(cx, |buffer, cx| {
10872 let snapshot = buffer.read(cx);
10873 auto_indent_on_paste = snapshot
10874 .language_settings_at(cursor_offset, cx)
10875 .auto_indent_on_paste;
10876
10877 let mut start_offset = 0;
10878 let mut edits = Vec::new();
10879 let mut original_indent_columns = Vec::new();
10880 for (ix, selection) in old_selections.iter().enumerate() {
10881 let to_insert;
10882 let entire_line;
10883 let original_indent_column;
10884 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10885 let end_offset = start_offset + clipboard_selection.len;
10886 to_insert = &clipboard_text[start_offset..end_offset];
10887 entire_line = clipboard_selection.is_entire_line;
10888 start_offset = end_offset + 1;
10889 original_indent_column = Some(clipboard_selection.first_line_indent);
10890 } else {
10891 to_insert = clipboard_text.as_str();
10892 entire_line = all_selections_were_entire_line;
10893 original_indent_column = first_selection_indent_column
10894 }
10895
10896 // If the corresponding selection was empty when this slice of the
10897 // clipboard text was written, then the entire line containing the
10898 // selection was copied. If this selection is also currently empty,
10899 // then paste the line before the current line of the buffer.
10900 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10901 let column = selection.start.to_point(&snapshot).column as usize;
10902 let line_start = selection.start - column;
10903 line_start..line_start
10904 } else {
10905 selection.range()
10906 };
10907
10908 edits.push((range, to_insert));
10909 original_indent_columns.push(original_indent_column);
10910 }
10911 drop(snapshot);
10912
10913 buffer.edit(
10914 edits,
10915 if auto_indent_on_paste {
10916 Some(AutoindentMode::Block {
10917 original_indent_columns,
10918 })
10919 } else {
10920 None
10921 },
10922 cx,
10923 );
10924 });
10925
10926 let selections = this.selections.all::<usize>(cx);
10927 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10928 s.select(selections)
10929 });
10930 } else {
10931 this.insert(&clipboard_text, window, cx);
10932 }
10933 });
10934 }
10935
10936 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10937 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10938 if let Some(item) = cx.read_from_clipboard() {
10939 let entries = item.entries();
10940
10941 match entries.first() {
10942 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10943 // of all the pasted entries.
10944 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10945 .do_paste(
10946 clipboard_string.text(),
10947 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10948 true,
10949 window,
10950 cx,
10951 ),
10952 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10953 }
10954 }
10955 }
10956
10957 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10958 if self.read_only(cx) {
10959 return;
10960 }
10961
10962 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10963
10964 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10965 if let Some((selections, _)) =
10966 self.selection_history.transaction(transaction_id).cloned()
10967 {
10968 self.change_selections(None, window, cx, |s| {
10969 s.select_anchors(selections.to_vec());
10970 });
10971 } else {
10972 log::error!(
10973 "No entry in selection_history found for undo. \
10974 This may correspond to a bug where undo does not update the selection. \
10975 If this is occurring, please add details to \
10976 https://github.com/zed-industries/zed/issues/22692"
10977 );
10978 }
10979 self.request_autoscroll(Autoscroll::fit(), cx);
10980 self.unmark_text(window, cx);
10981 self.refresh_inline_completion(true, false, window, cx);
10982 cx.emit(EditorEvent::Edited { transaction_id });
10983 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10984 }
10985 }
10986
10987 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10988 if self.read_only(cx) {
10989 return;
10990 }
10991
10992 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10993
10994 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10995 if let Some((_, Some(selections))) =
10996 self.selection_history.transaction(transaction_id).cloned()
10997 {
10998 self.change_selections(None, window, cx, |s| {
10999 s.select_anchors(selections.to_vec());
11000 });
11001 } else {
11002 log::error!(
11003 "No entry in selection_history found for redo. \
11004 This may correspond to a bug where undo does not update the selection. \
11005 If this is occurring, please add details to \
11006 https://github.com/zed-industries/zed/issues/22692"
11007 );
11008 }
11009 self.request_autoscroll(Autoscroll::fit(), cx);
11010 self.unmark_text(window, cx);
11011 self.refresh_inline_completion(true, false, window, cx);
11012 cx.emit(EditorEvent::Edited { transaction_id });
11013 }
11014 }
11015
11016 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11017 self.buffer
11018 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11019 }
11020
11021 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11022 self.buffer
11023 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11024 }
11025
11026 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11027 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11028 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11029 s.move_with(|map, selection| {
11030 let cursor = if selection.is_empty() {
11031 movement::left(map, selection.start)
11032 } else {
11033 selection.start
11034 };
11035 selection.collapse_to(cursor, SelectionGoal::None);
11036 });
11037 })
11038 }
11039
11040 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11041 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11043 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11044 })
11045 }
11046
11047 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11048 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11049 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11050 s.move_with(|map, selection| {
11051 let cursor = if selection.is_empty() {
11052 movement::right(map, selection.end)
11053 } else {
11054 selection.end
11055 };
11056 selection.collapse_to(cursor, SelectionGoal::None)
11057 });
11058 })
11059 }
11060
11061 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11062 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11063 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11064 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11065 })
11066 }
11067
11068 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11069 if self.take_rename(true, window, cx).is_some() {
11070 return;
11071 }
11072
11073 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11074 cx.propagate();
11075 return;
11076 }
11077
11078 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11079
11080 let text_layout_details = &self.text_layout_details(window);
11081 let selection_count = self.selections.count();
11082 let first_selection = self.selections.first_anchor();
11083
11084 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11085 s.move_with(|map, selection| {
11086 if !selection.is_empty() {
11087 selection.goal = SelectionGoal::None;
11088 }
11089 let (cursor, goal) = movement::up(
11090 map,
11091 selection.start,
11092 selection.goal,
11093 false,
11094 text_layout_details,
11095 );
11096 selection.collapse_to(cursor, goal);
11097 });
11098 });
11099
11100 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11101 {
11102 cx.propagate();
11103 }
11104 }
11105
11106 pub fn move_up_by_lines(
11107 &mut self,
11108 action: &MoveUpByLines,
11109 window: &mut Window,
11110 cx: &mut Context<Self>,
11111 ) {
11112 if self.take_rename(true, window, cx).is_some() {
11113 return;
11114 }
11115
11116 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11117 cx.propagate();
11118 return;
11119 }
11120
11121 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11122
11123 let text_layout_details = &self.text_layout_details(window);
11124
11125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11126 s.move_with(|map, selection| {
11127 if !selection.is_empty() {
11128 selection.goal = SelectionGoal::None;
11129 }
11130 let (cursor, goal) = movement::up_by_rows(
11131 map,
11132 selection.start,
11133 action.lines,
11134 selection.goal,
11135 false,
11136 text_layout_details,
11137 );
11138 selection.collapse_to(cursor, goal);
11139 });
11140 })
11141 }
11142
11143 pub fn move_down_by_lines(
11144 &mut self,
11145 action: &MoveDownByLines,
11146 window: &mut Window,
11147 cx: &mut Context<Self>,
11148 ) {
11149 if self.take_rename(true, window, cx).is_some() {
11150 return;
11151 }
11152
11153 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11154 cx.propagate();
11155 return;
11156 }
11157
11158 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11159
11160 let text_layout_details = &self.text_layout_details(window);
11161
11162 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11163 s.move_with(|map, selection| {
11164 if !selection.is_empty() {
11165 selection.goal = SelectionGoal::None;
11166 }
11167 let (cursor, goal) = movement::down_by_rows(
11168 map,
11169 selection.start,
11170 action.lines,
11171 selection.goal,
11172 false,
11173 text_layout_details,
11174 );
11175 selection.collapse_to(cursor, goal);
11176 });
11177 })
11178 }
11179
11180 pub fn select_down_by_lines(
11181 &mut self,
11182 action: &SelectDownByLines,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11187 let text_layout_details = &self.text_layout_details(window);
11188 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11189 s.move_heads_with(|map, head, goal| {
11190 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11191 })
11192 })
11193 }
11194
11195 pub fn select_up_by_lines(
11196 &mut self,
11197 action: &SelectUpByLines,
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::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11206 })
11207 })
11208 }
11209
11210 pub fn select_page_up(
11211 &mut self,
11212 _: &SelectPageUp,
11213 window: &mut Window,
11214 cx: &mut Context<Self>,
11215 ) {
11216 let Some(row_count) = self.visible_row_count() else {
11217 return;
11218 };
11219
11220 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11221
11222 let text_layout_details = &self.text_layout_details(window);
11223
11224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11225 s.move_heads_with(|map, head, goal| {
11226 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11227 })
11228 })
11229 }
11230
11231 pub fn move_page_up(
11232 &mut self,
11233 action: &MovePageUp,
11234 window: &mut Window,
11235 cx: &mut Context<Self>,
11236 ) {
11237 if self.take_rename(true, window, cx).is_some() {
11238 return;
11239 }
11240
11241 if self
11242 .context_menu
11243 .borrow_mut()
11244 .as_mut()
11245 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11246 .unwrap_or(false)
11247 {
11248 return;
11249 }
11250
11251 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11252 cx.propagate();
11253 return;
11254 }
11255
11256 let Some(row_count) = self.visible_row_count() else {
11257 return;
11258 };
11259
11260 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11261
11262 let autoscroll = if action.center_cursor {
11263 Autoscroll::center()
11264 } else {
11265 Autoscroll::fit()
11266 };
11267
11268 let text_layout_details = &self.text_layout_details(window);
11269
11270 self.change_selections(Some(autoscroll), window, cx, |s| {
11271 s.move_with(|map, selection| {
11272 if !selection.is_empty() {
11273 selection.goal = SelectionGoal::None;
11274 }
11275 let (cursor, goal) = movement::up_by_rows(
11276 map,
11277 selection.end,
11278 row_count,
11279 selection.goal,
11280 false,
11281 text_layout_details,
11282 );
11283 selection.collapse_to(cursor, goal);
11284 });
11285 });
11286 }
11287
11288 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11290 let text_layout_details = &self.text_layout_details(window);
11291 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11292 s.move_heads_with(|map, head, goal| {
11293 movement::up(map, head, goal, false, text_layout_details)
11294 })
11295 })
11296 }
11297
11298 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11299 self.take_rename(true, window, cx);
11300
11301 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11302 cx.propagate();
11303 return;
11304 }
11305
11306 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11307
11308 let text_layout_details = &self.text_layout_details(window);
11309 let selection_count = self.selections.count();
11310 let first_selection = self.selections.first_anchor();
11311
11312 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11313 s.move_with(|map, selection| {
11314 if !selection.is_empty() {
11315 selection.goal = SelectionGoal::None;
11316 }
11317 let (cursor, goal) = movement::down(
11318 map,
11319 selection.end,
11320 selection.goal,
11321 false,
11322 text_layout_details,
11323 );
11324 selection.collapse_to(cursor, goal);
11325 });
11326 });
11327
11328 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11329 {
11330 cx.propagate();
11331 }
11332 }
11333
11334 pub fn select_page_down(
11335 &mut self,
11336 _: &SelectPageDown,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 let Some(row_count) = self.visible_row_count() else {
11341 return;
11342 };
11343
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11345
11346 let text_layout_details = &self.text_layout_details(window);
11347
11348 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11349 s.move_heads_with(|map, head, goal| {
11350 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11351 })
11352 })
11353 }
11354
11355 pub fn move_page_down(
11356 &mut self,
11357 action: &MovePageDown,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 ) {
11361 if self.take_rename(true, window, cx).is_some() {
11362 return;
11363 }
11364
11365 if self
11366 .context_menu
11367 .borrow_mut()
11368 .as_mut()
11369 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11370 .unwrap_or(false)
11371 {
11372 return;
11373 }
11374
11375 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11376 cx.propagate();
11377 return;
11378 }
11379
11380 let Some(row_count) = self.visible_row_count() else {
11381 return;
11382 };
11383
11384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11385
11386 let autoscroll = if action.center_cursor {
11387 Autoscroll::center()
11388 } else {
11389 Autoscroll::fit()
11390 };
11391
11392 let text_layout_details = &self.text_layout_details(window);
11393 self.change_selections(Some(autoscroll), window, cx, |s| {
11394 s.move_with(|map, selection| {
11395 if !selection.is_empty() {
11396 selection.goal = SelectionGoal::None;
11397 }
11398 let (cursor, goal) = movement::down_by_rows(
11399 map,
11400 selection.end,
11401 row_count,
11402 selection.goal,
11403 false,
11404 text_layout_details,
11405 );
11406 selection.collapse_to(cursor, goal);
11407 });
11408 });
11409 }
11410
11411 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11412 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11413 let text_layout_details = &self.text_layout_details(window);
11414 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11415 s.move_heads_with(|map, head, goal| {
11416 movement::down(map, head, goal, false, text_layout_details)
11417 })
11418 });
11419 }
11420
11421 pub fn context_menu_first(
11422 &mut self,
11423 _: &ContextMenuFirst,
11424 _window: &mut Window,
11425 cx: &mut Context<Self>,
11426 ) {
11427 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11428 context_menu.select_first(self.completion_provider.as_deref(), cx);
11429 }
11430 }
11431
11432 pub fn context_menu_prev(
11433 &mut self,
11434 _: &ContextMenuPrevious,
11435 _window: &mut Window,
11436 cx: &mut Context<Self>,
11437 ) {
11438 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11439 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11440 }
11441 }
11442
11443 pub fn context_menu_next(
11444 &mut self,
11445 _: &ContextMenuNext,
11446 _window: &mut Window,
11447 cx: &mut Context<Self>,
11448 ) {
11449 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11450 context_menu.select_next(self.completion_provider.as_deref(), cx);
11451 }
11452 }
11453
11454 pub fn context_menu_last(
11455 &mut self,
11456 _: &ContextMenuLast,
11457 _window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11461 context_menu.select_last(self.completion_provider.as_deref(), cx);
11462 }
11463 }
11464
11465 pub fn move_to_previous_word_start(
11466 &mut self,
11467 _: &MoveToPreviousWordStart,
11468 window: &mut Window,
11469 cx: &mut Context<Self>,
11470 ) {
11471 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11472 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11473 s.move_cursors_with(|map, head, _| {
11474 (
11475 movement::previous_word_start(map, head),
11476 SelectionGoal::None,
11477 )
11478 });
11479 })
11480 }
11481
11482 pub fn move_to_previous_subword_start(
11483 &mut self,
11484 _: &MoveToPreviousSubwordStart,
11485 window: &mut Window,
11486 cx: &mut Context<Self>,
11487 ) {
11488 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11490 s.move_cursors_with(|map, head, _| {
11491 (
11492 movement::previous_subword_start(map, head),
11493 SelectionGoal::None,
11494 )
11495 });
11496 })
11497 }
11498
11499 pub fn select_to_previous_word_start(
11500 &mut self,
11501 _: &SelectToPreviousWordStart,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11506 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11507 s.move_heads_with(|map, head, _| {
11508 (
11509 movement::previous_word_start(map, head),
11510 SelectionGoal::None,
11511 )
11512 });
11513 })
11514 }
11515
11516 pub fn select_to_previous_subword_start(
11517 &mut self,
11518 _: &SelectToPreviousSubwordStart,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11523 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11524 s.move_heads_with(|map, head, _| {
11525 (
11526 movement::previous_subword_start(map, head),
11527 SelectionGoal::None,
11528 )
11529 });
11530 })
11531 }
11532
11533 pub fn delete_to_previous_word_start(
11534 &mut self,
11535 action: &DeleteToPreviousWordStart,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11540 self.transact(window, cx, |this, window, cx| {
11541 this.select_autoclose_pair(window, cx);
11542 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11543 s.move_with(|map, selection| {
11544 if selection.is_empty() {
11545 let cursor = if action.ignore_newlines {
11546 movement::previous_word_start(map, selection.head())
11547 } else {
11548 movement::previous_word_start_or_newline(map, selection.head())
11549 };
11550 selection.set_head(cursor, SelectionGoal::None);
11551 }
11552 });
11553 });
11554 this.insert("", window, cx);
11555 });
11556 }
11557
11558 pub fn delete_to_previous_subword_start(
11559 &mut self,
11560 _: &DeleteToPreviousSubwordStart,
11561 window: &mut Window,
11562 cx: &mut Context<Self>,
11563 ) {
11564 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11565 self.transact(window, cx, |this, window, cx| {
11566 this.select_autoclose_pair(window, cx);
11567 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11568 s.move_with(|map, selection| {
11569 if selection.is_empty() {
11570 let cursor = movement::previous_subword_start(map, selection.head());
11571 selection.set_head(cursor, SelectionGoal::None);
11572 }
11573 });
11574 });
11575 this.insert("", window, cx);
11576 });
11577 }
11578
11579 pub fn move_to_next_word_end(
11580 &mut self,
11581 _: &MoveToNextWordEnd,
11582 window: &mut Window,
11583 cx: &mut Context<Self>,
11584 ) {
11585 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11586 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11587 s.move_cursors_with(|map, head, _| {
11588 (movement::next_word_end(map, head), SelectionGoal::None)
11589 });
11590 })
11591 }
11592
11593 pub fn move_to_next_subword_end(
11594 &mut self,
11595 _: &MoveToNextSubwordEnd,
11596 window: &mut Window,
11597 cx: &mut Context<Self>,
11598 ) {
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11601 s.move_cursors_with(|map, head, _| {
11602 (movement::next_subword_end(map, head), SelectionGoal::None)
11603 });
11604 })
11605 }
11606
11607 pub fn select_to_next_word_end(
11608 &mut self,
11609 _: &SelectToNextWordEnd,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11614 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11615 s.move_heads_with(|map, head, _| {
11616 (movement::next_word_end(map, head), SelectionGoal::None)
11617 });
11618 })
11619 }
11620
11621 pub fn select_to_next_subword_end(
11622 &mut self,
11623 _: &SelectToNextSubwordEnd,
11624 window: &mut Window,
11625 cx: &mut Context<Self>,
11626 ) {
11627 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11628 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11629 s.move_heads_with(|map, head, _| {
11630 (movement::next_subword_end(map, head), SelectionGoal::None)
11631 });
11632 })
11633 }
11634
11635 pub fn delete_to_next_word_end(
11636 &mut self,
11637 action: &DeleteToNextWordEnd,
11638 window: &mut Window,
11639 cx: &mut Context<Self>,
11640 ) {
11641 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11642 self.transact(window, cx, |this, window, cx| {
11643 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_with(|map, selection| {
11645 if selection.is_empty() {
11646 let cursor = if action.ignore_newlines {
11647 movement::next_word_end(map, selection.head())
11648 } else {
11649 movement::next_word_end_or_newline(map, selection.head())
11650 };
11651 selection.set_head(cursor, SelectionGoal::None);
11652 }
11653 });
11654 });
11655 this.insert("", window, cx);
11656 });
11657 }
11658
11659 pub fn delete_to_next_subword_end(
11660 &mut self,
11661 _: &DeleteToNextSubwordEnd,
11662 window: &mut Window,
11663 cx: &mut Context<Self>,
11664 ) {
11665 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11666 self.transact(window, cx, |this, window, cx| {
11667 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11668 s.move_with(|map, selection| {
11669 if selection.is_empty() {
11670 let cursor = movement::next_subword_end(map, selection.head());
11671 selection.set_head(cursor, SelectionGoal::None);
11672 }
11673 });
11674 });
11675 this.insert("", window, cx);
11676 });
11677 }
11678
11679 pub fn move_to_beginning_of_line(
11680 &mut self,
11681 action: &MoveToBeginningOfLine,
11682 window: &mut Window,
11683 cx: &mut Context<Self>,
11684 ) {
11685 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11686 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11687 s.move_cursors_with(|map, head, _| {
11688 (
11689 movement::indented_line_beginning(
11690 map,
11691 head,
11692 action.stop_at_soft_wraps,
11693 action.stop_at_indent,
11694 ),
11695 SelectionGoal::None,
11696 )
11697 });
11698 })
11699 }
11700
11701 pub fn select_to_beginning_of_line(
11702 &mut self,
11703 action: &SelectToBeginningOfLine,
11704 window: &mut Window,
11705 cx: &mut Context<Self>,
11706 ) {
11707 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11708 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11709 s.move_heads_with(|map, head, _| {
11710 (
11711 movement::indented_line_beginning(
11712 map,
11713 head,
11714 action.stop_at_soft_wraps,
11715 action.stop_at_indent,
11716 ),
11717 SelectionGoal::None,
11718 )
11719 });
11720 });
11721 }
11722
11723 pub fn delete_to_beginning_of_line(
11724 &mut self,
11725 action: &DeleteToBeginningOfLine,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11730 self.transact(window, cx, |this, window, cx| {
11731 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11732 s.move_with(|_, selection| {
11733 selection.reversed = true;
11734 });
11735 });
11736
11737 this.select_to_beginning_of_line(
11738 &SelectToBeginningOfLine {
11739 stop_at_soft_wraps: false,
11740 stop_at_indent: action.stop_at_indent,
11741 },
11742 window,
11743 cx,
11744 );
11745 this.backspace(&Backspace, window, cx);
11746 });
11747 }
11748
11749 pub fn move_to_end_of_line(
11750 &mut self,
11751 action: &MoveToEndOfLine,
11752 window: &mut Window,
11753 cx: &mut Context<Self>,
11754 ) {
11755 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11756 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11757 s.move_cursors_with(|map, head, _| {
11758 (
11759 movement::line_end(map, head, action.stop_at_soft_wraps),
11760 SelectionGoal::None,
11761 )
11762 });
11763 })
11764 }
11765
11766 pub fn select_to_end_of_line(
11767 &mut self,
11768 action: &SelectToEndOfLine,
11769 window: &mut Window,
11770 cx: &mut Context<Self>,
11771 ) {
11772 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11773 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11774 s.move_heads_with(|map, head, _| {
11775 (
11776 movement::line_end(map, head, action.stop_at_soft_wraps),
11777 SelectionGoal::None,
11778 )
11779 });
11780 })
11781 }
11782
11783 pub fn delete_to_end_of_line(
11784 &mut self,
11785 _: &DeleteToEndOfLine,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11790 self.transact(window, cx, |this, window, cx| {
11791 this.select_to_end_of_line(
11792 &SelectToEndOfLine {
11793 stop_at_soft_wraps: false,
11794 },
11795 window,
11796 cx,
11797 );
11798 this.delete(&Delete, window, cx);
11799 });
11800 }
11801
11802 pub fn cut_to_end_of_line(
11803 &mut self,
11804 _: &CutToEndOfLine,
11805 window: &mut Window,
11806 cx: &mut Context<Self>,
11807 ) {
11808 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11809 self.transact(window, cx, |this, window, cx| {
11810 this.select_to_end_of_line(
11811 &SelectToEndOfLine {
11812 stop_at_soft_wraps: false,
11813 },
11814 window,
11815 cx,
11816 );
11817 this.cut(&Cut, window, cx);
11818 });
11819 }
11820
11821 pub fn move_to_start_of_paragraph(
11822 &mut self,
11823 _: &MoveToStartOfParagraph,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 ) {
11827 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11828 cx.propagate();
11829 return;
11830 }
11831 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11833 s.move_with(|map, selection| {
11834 selection.collapse_to(
11835 movement::start_of_paragraph(map, selection.head(), 1),
11836 SelectionGoal::None,
11837 )
11838 });
11839 })
11840 }
11841
11842 pub fn move_to_end_of_paragraph(
11843 &mut self,
11844 _: &MoveToEndOfParagraph,
11845 window: &mut Window,
11846 cx: &mut Context<Self>,
11847 ) {
11848 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11849 cx.propagate();
11850 return;
11851 }
11852 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11853 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11854 s.move_with(|map, selection| {
11855 selection.collapse_to(
11856 movement::end_of_paragraph(map, selection.head(), 1),
11857 SelectionGoal::None,
11858 )
11859 });
11860 })
11861 }
11862
11863 pub fn select_to_start_of_paragraph(
11864 &mut self,
11865 _: &SelectToStartOfParagraph,
11866 window: &mut Window,
11867 cx: &mut Context<Self>,
11868 ) {
11869 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11870 cx.propagate();
11871 return;
11872 }
11873 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11874 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11875 s.move_heads_with(|map, head, _| {
11876 (
11877 movement::start_of_paragraph(map, head, 1),
11878 SelectionGoal::None,
11879 )
11880 });
11881 })
11882 }
11883
11884 pub fn select_to_end_of_paragraph(
11885 &mut self,
11886 _: &SelectToEndOfParagraph,
11887 window: &mut Window,
11888 cx: &mut Context<Self>,
11889 ) {
11890 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11891 cx.propagate();
11892 return;
11893 }
11894 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11895 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11896 s.move_heads_with(|map, head, _| {
11897 (
11898 movement::end_of_paragraph(map, head, 1),
11899 SelectionGoal::None,
11900 )
11901 });
11902 })
11903 }
11904
11905 pub fn move_to_start_of_excerpt(
11906 &mut self,
11907 _: &MoveToStartOfExcerpt,
11908 window: &mut Window,
11909 cx: &mut Context<Self>,
11910 ) {
11911 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11912 cx.propagate();
11913 return;
11914 }
11915 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11916 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11917 s.move_with(|map, selection| {
11918 selection.collapse_to(
11919 movement::start_of_excerpt(
11920 map,
11921 selection.head(),
11922 workspace::searchable::Direction::Prev,
11923 ),
11924 SelectionGoal::None,
11925 )
11926 });
11927 })
11928 }
11929
11930 pub fn move_to_start_of_next_excerpt(
11931 &mut self,
11932 _: &MoveToStartOfNextExcerpt,
11933 window: &mut Window,
11934 cx: &mut Context<Self>,
11935 ) {
11936 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11937 cx.propagate();
11938 return;
11939 }
11940
11941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11942 s.move_with(|map, selection| {
11943 selection.collapse_to(
11944 movement::start_of_excerpt(
11945 map,
11946 selection.head(),
11947 workspace::searchable::Direction::Next,
11948 ),
11949 SelectionGoal::None,
11950 )
11951 });
11952 })
11953 }
11954
11955 pub fn move_to_end_of_excerpt(
11956 &mut self,
11957 _: &MoveToEndOfExcerpt,
11958 window: &mut Window,
11959 cx: &mut Context<Self>,
11960 ) {
11961 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11962 cx.propagate();
11963 return;
11964 }
11965 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11967 s.move_with(|map, selection| {
11968 selection.collapse_to(
11969 movement::end_of_excerpt(
11970 map,
11971 selection.head(),
11972 workspace::searchable::Direction::Next,
11973 ),
11974 SelectionGoal::None,
11975 )
11976 });
11977 })
11978 }
11979
11980 pub fn move_to_end_of_previous_excerpt(
11981 &mut self,
11982 _: &MoveToEndOfPreviousExcerpt,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) {
11986 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11987 cx.propagate();
11988 return;
11989 }
11990 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11991 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11992 s.move_with(|map, selection| {
11993 selection.collapse_to(
11994 movement::end_of_excerpt(
11995 map,
11996 selection.head(),
11997 workspace::searchable::Direction::Prev,
11998 ),
11999 SelectionGoal::None,
12000 )
12001 });
12002 })
12003 }
12004
12005 pub fn select_to_start_of_excerpt(
12006 &mut self,
12007 _: &SelectToStartOfExcerpt,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) {
12011 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12012 cx.propagate();
12013 return;
12014 }
12015 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12016 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12017 s.move_heads_with(|map, head, _| {
12018 (
12019 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12020 SelectionGoal::None,
12021 )
12022 });
12023 })
12024 }
12025
12026 pub fn select_to_start_of_next_excerpt(
12027 &mut self,
12028 _: &SelectToStartOfNextExcerpt,
12029 window: &mut Window,
12030 cx: &mut Context<Self>,
12031 ) {
12032 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12033 cx.propagate();
12034 return;
12035 }
12036 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12038 s.move_heads_with(|map, head, _| {
12039 (
12040 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12041 SelectionGoal::None,
12042 )
12043 });
12044 })
12045 }
12046
12047 pub fn select_to_end_of_excerpt(
12048 &mut self,
12049 _: &SelectToEndOfExcerpt,
12050 window: &mut Window,
12051 cx: &mut Context<Self>,
12052 ) {
12053 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12054 cx.propagate();
12055 return;
12056 }
12057 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12058 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12059 s.move_heads_with(|map, head, _| {
12060 (
12061 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12062 SelectionGoal::None,
12063 )
12064 });
12065 })
12066 }
12067
12068 pub fn select_to_end_of_previous_excerpt(
12069 &mut self,
12070 _: &SelectToEndOfPreviousExcerpt,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) {
12074 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12075 cx.propagate();
12076 return;
12077 }
12078 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12079 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12080 s.move_heads_with(|map, head, _| {
12081 (
12082 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12083 SelectionGoal::None,
12084 )
12085 });
12086 })
12087 }
12088
12089 pub fn move_to_beginning(
12090 &mut self,
12091 _: &MoveToBeginning,
12092 window: &mut Window,
12093 cx: &mut Context<Self>,
12094 ) {
12095 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12096 cx.propagate();
12097 return;
12098 }
12099 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12100 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12101 s.select_ranges(vec![0..0]);
12102 });
12103 }
12104
12105 pub fn select_to_beginning(
12106 &mut self,
12107 _: &SelectToBeginning,
12108 window: &mut Window,
12109 cx: &mut Context<Self>,
12110 ) {
12111 let mut selection = self.selections.last::<Point>(cx);
12112 selection.set_head(Point::zero(), SelectionGoal::None);
12113 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12114 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12115 s.select(vec![selection]);
12116 });
12117 }
12118
12119 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12120 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12121 cx.propagate();
12122 return;
12123 }
12124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12125 let cursor = self.buffer.read(cx).read(cx).len();
12126 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12127 s.select_ranges(vec![cursor..cursor])
12128 });
12129 }
12130
12131 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12132 self.nav_history = nav_history;
12133 }
12134
12135 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12136 self.nav_history.as_ref()
12137 }
12138
12139 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12140 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12141 }
12142
12143 fn push_to_nav_history(
12144 &mut self,
12145 cursor_anchor: Anchor,
12146 new_position: Option<Point>,
12147 is_deactivate: bool,
12148 cx: &mut Context<Self>,
12149 ) {
12150 if let Some(nav_history) = self.nav_history.as_mut() {
12151 let buffer = self.buffer.read(cx).read(cx);
12152 let cursor_position = cursor_anchor.to_point(&buffer);
12153 let scroll_state = self.scroll_manager.anchor();
12154 let scroll_top_row = scroll_state.top_row(&buffer);
12155 drop(buffer);
12156
12157 if let Some(new_position) = new_position {
12158 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12159 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12160 return;
12161 }
12162 }
12163
12164 nav_history.push(
12165 Some(NavigationData {
12166 cursor_anchor,
12167 cursor_position,
12168 scroll_anchor: scroll_state,
12169 scroll_top_row,
12170 }),
12171 cx,
12172 );
12173 cx.emit(EditorEvent::PushedToNavHistory {
12174 anchor: cursor_anchor,
12175 is_deactivate,
12176 })
12177 }
12178 }
12179
12180 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12181 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12182 let buffer = self.buffer.read(cx).snapshot(cx);
12183 let mut selection = self.selections.first::<usize>(cx);
12184 selection.set_head(buffer.len(), SelectionGoal::None);
12185 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12186 s.select(vec![selection]);
12187 });
12188 }
12189
12190 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12191 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12192 let end = self.buffer.read(cx).read(cx).len();
12193 self.change_selections(None, window, cx, |s| {
12194 s.select_ranges(vec![0..end]);
12195 });
12196 }
12197
12198 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12199 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12200 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12201 let mut selections = self.selections.all::<Point>(cx);
12202 let max_point = display_map.buffer_snapshot.max_point();
12203 for selection in &mut selections {
12204 let rows = selection.spanned_rows(true, &display_map);
12205 selection.start = Point::new(rows.start.0, 0);
12206 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12207 selection.reversed = false;
12208 }
12209 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12210 s.select(selections);
12211 });
12212 }
12213
12214 pub fn split_selection_into_lines(
12215 &mut self,
12216 _: &SplitSelectionIntoLines,
12217 window: &mut Window,
12218 cx: &mut Context<Self>,
12219 ) {
12220 let selections = self
12221 .selections
12222 .all::<Point>(cx)
12223 .into_iter()
12224 .map(|selection| selection.start..selection.end)
12225 .collect::<Vec<_>>();
12226 self.unfold_ranges(&selections, true, true, cx);
12227
12228 let mut new_selection_ranges = Vec::new();
12229 {
12230 let buffer = self.buffer.read(cx).read(cx);
12231 for selection in selections {
12232 for row in selection.start.row..selection.end.row {
12233 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12234 new_selection_ranges.push(cursor..cursor);
12235 }
12236
12237 let is_multiline_selection = selection.start.row != selection.end.row;
12238 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12239 // so this action feels more ergonomic when paired with other selection operations
12240 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12241 if !should_skip_last {
12242 new_selection_ranges.push(selection.end..selection.end);
12243 }
12244 }
12245 }
12246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12247 s.select_ranges(new_selection_ranges);
12248 });
12249 }
12250
12251 pub fn add_selection_above(
12252 &mut self,
12253 _: &AddSelectionAbove,
12254 window: &mut Window,
12255 cx: &mut Context<Self>,
12256 ) {
12257 self.add_selection(true, window, cx);
12258 }
12259
12260 pub fn add_selection_below(
12261 &mut self,
12262 _: &AddSelectionBelow,
12263 window: &mut Window,
12264 cx: &mut Context<Self>,
12265 ) {
12266 self.add_selection(false, window, cx);
12267 }
12268
12269 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12270 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12271
12272 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12273 let mut selections = self.selections.all::<Point>(cx);
12274 let text_layout_details = self.text_layout_details(window);
12275 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12276 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12277 let range = oldest_selection.display_range(&display_map).sorted();
12278
12279 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12280 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12281 let positions = start_x.min(end_x)..start_x.max(end_x);
12282
12283 selections.clear();
12284 let mut stack = Vec::new();
12285 for row in range.start.row().0..=range.end.row().0 {
12286 if let Some(selection) = self.selections.build_columnar_selection(
12287 &display_map,
12288 DisplayRow(row),
12289 &positions,
12290 oldest_selection.reversed,
12291 &text_layout_details,
12292 ) {
12293 stack.push(selection.id);
12294 selections.push(selection);
12295 }
12296 }
12297
12298 if above {
12299 stack.reverse();
12300 }
12301
12302 AddSelectionsState { above, stack }
12303 });
12304
12305 let last_added_selection = *state.stack.last().unwrap();
12306 let mut new_selections = Vec::new();
12307 if above == state.above {
12308 let end_row = if above {
12309 DisplayRow(0)
12310 } else {
12311 display_map.max_point().row()
12312 };
12313
12314 'outer: for selection in selections {
12315 if selection.id == last_added_selection {
12316 let range = selection.display_range(&display_map).sorted();
12317 debug_assert_eq!(range.start.row(), range.end.row());
12318 let mut row = range.start.row();
12319 let positions =
12320 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12321 px(start)..px(end)
12322 } else {
12323 let start_x =
12324 display_map.x_for_display_point(range.start, &text_layout_details);
12325 let end_x =
12326 display_map.x_for_display_point(range.end, &text_layout_details);
12327 start_x.min(end_x)..start_x.max(end_x)
12328 };
12329
12330 while row != end_row {
12331 if above {
12332 row.0 -= 1;
12333 } else {
12334 row.0 += 1;
12335 }
12336
12337 if let Some(new_selection) = self.selections.build_columnar_selection(
12338 &display_map,
12339 row,
12340 &positions,
12341 selection.reversed,
12342 &text_layout_details,
12343 ) {
12344 state.stack.push(new_selection.id);
12345 if above {
12346 new_selections.push(new_selection);
12347 new_selections.push(selection);
12348 } else {
12349 new_selections.push(selection);
12350 new_selections.push(new_selection);
12351 }
12352
12353 continue 'outer;
12354 }
12355 }
12356 }
12357
12358 new_selections.push(selection);
12359 }
12360 } else {
12361 new_selections = selections;
12362 new_selections.retain(|s| s.id != last_added_selection);
12363 state.stack.pop();
12364 }
12365
12366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12367 s.select(new_selections);
12368 });
12369 if state.stack.len() > 1 {
12370 self.add_selections_state = Some(state);
12371 }
12372 }
12373
12374 fn select_match_ranges(
12375 &mut self,
12376 range: Range<usize>,
12377 reversed: bool,
12378 replace_newest: bool,
12379 auto_scroll: Option<Autoscroll>,
12380 window: &mut Window,
12381 cx: &mut Context<Editor>,
12382 ) {
12383 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12384 self.change_selections(auto_scroll, window, cx, |s| {
12385 if replace_newest {
12386 s.delete(s.newest_anchor().id);
12387 }
12388 if reversed {
12389 s.insert_range(range.end..range.start);
12390 } else {
12391 s.insert_range(range);
12392 }
12393 });
12394 }
12395
12396 pub fn select_next_match_internal(
12397 &mut self,
12398 display_map: &DisplaySnapshot,
12399 replace_newest: bool,
12400 autoscroll: Option<Autoscroll>,
12401 window: &mut Window,
12402 cx: &mut Context<Self>,
12403 ) -> Result<()> {
12404 let buffer = &display_map.buffer_snapshot;
12405 let mut selections = self.selections.all::<usize>(cx);
12406 if let Some(mut select_next_state) = self.select_next_state.take() {
12407 let query = &select_next_state.query;
12408 if !select_next_state.done {
12409 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12410 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12411 let mut next_selected_range = None;
12412
12413 let bytes_after_last_selection =
12414 buffer.bytes_in_range(last_selection.end..buffer.len());
12415 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12416 let query_matches = query
12417 .stream_find_iter(bytes_after_last_selection)
12418 .map(|result| (last_selection.end, result))
12419 .chain(
12420 query
12421 .stream_find_iter(bytes_before_first_selection)
12422 .map(|result| (0, result)),
12423 );
12424
12425 for (start_offset, query_match) in query_matches {
12426 let query_match = query_match.unwrap(); // can only fail due to I/O
12427 let offset_range =
12428 start_offset + query_match.start()..start_offset + query_match.end();
12429 let display_range = offset_range.start.to_display_point(display_map)
12430 ..offset_range.end.to_display_point(display_map);
12431
12432 if !select_next_state.wordwise
12433 || (!movement::is_inside_word(display_map, display_range.start)
12434 && !movement::is_inside_word(display_map, display_range.end))
12435 {
12436 // TODO: This is n^2, because we might check all the selections
12437 if !selections
12438 .iter()
12439 .any(|selection| selection.range().overlaps(&offset_range))
12440 {
12441 next_selected_range = Some(offset_range);
12442 break;
12443 }
12444 }
12445 }
12446
12447 if let Some(next_selected_range) = next_selected_range {
12448 self.select_match_ranges(
12449 next_selected_range,
12450 last_selection.reversed,
12451 replace_newest,
12452 autoscroll,
12453 window,
12454 cx,
12455 );
12456 } else {
12457 select_next_state.done = true;
12458 }
12459 }
12460
12461 self.select_next_state = Some(select_next_state);
12462 } else {
12463 let mut only_carets = true;
12464 let mut same_text_selected = true;
12465 let mut selected_text = None;
12466
12467 let mut selections_iter = selections.iter().peekable();
12468 while let Some(selection) = selections_iter.next() {
12469 if selection.start != selection.end {
12470 only_carets = false;
12471 }
12472
12473 if same_text_selected {
12474 if selected_text.is_none() {
12475 selected_text =
12476 Some(buffer.text_for_range(selection.range()).collect::<String>());
12477 }
12478
12479 if let Some(next_selection) = selections_iter.peek() {
12480 if next_selection.range().len() == selection.range().len() {
12481 let next_selected_text = buffer
12482 .text_for_range(next_selection.range())
12483 .collect::<String>();
12484 if Some(next_selected_text) != selected_text {
12485 same_text_selected = false;
12486 selected_text = None;
12487 }
12488 } else {
12489 same_text_selected = false;
12490 selected_text = None;
12491 }
12492 }
12493 }
12494 }
12495
12496 if only_carets {
12497 for selection in &mut selections {
12498 let word_range = movement::surrounding_word(
12499 display_map,
12500 selection.start.to_display_point(display_map),
12501 );
12502 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12503 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12504 selection.goal = SelectionGoal::None;
12505 selection.reversed = false;
12506 self.select_match_ranges(
12507 selection.start..selection.end,
12508 selection.reversed,
12509 replace_newest,
12510 autoscroll,
12511 window,
12512 cx,
12513 );
12514 }
12515
12516 if selections.len() == 1 {
12517 let selection = selections
12518 .last()
12519 .expect("ensured that there's only one selection");
12520 let query = buffer
12521 .text_for_range(selection.start..selection.end)
12522 .collect::<String>();
12523 let is_empty = query.is_empty();
12524 let select_state = SelectNextState {
12525 query: AhoCorasick::new(&[query])?,
12526 wordwise: true,
12527 done: is_empty,
12528 };
12529 self.select_next_state = Some(select_state);
12530 } else {
12531 self.select_next_state = None;
12532 }
12533 } else if let Some(selected_text) = selected_text {
12534 self.select_next_state = Some(SelectNextState {
12535 query: AhoCorasick::new(&[selected_text])?,
12536 wordwise: false,
12537 done: false,
12538 });
12539 self.select_next_match_internal(
12540 display_map,
12541 replace_newest,
12542 autoscroll,
12543 window,
12544 cx,
12545 )?;
12546 }
12547 }
12548 Ok(())
12549 }
12550
12551 pub fn select_all_matches(
12552 &mut self,
12553 _action: &SelectAllMatches,
12554 window: &mut Window,
12555 cx: &mut Context<Self>,
12556 ) -> Result<()> {
12557 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12558
12559 self.push_to_selection_history();
12560 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12561
12562 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12563 let Some(select_next_state) = self.select_next_state.as_mut() else {
12564 return Ok(());
12565 };
12566 if select_next_state.done {
12567 return Ok(());
12568 }
12569
12570 let mut new_selections = Vec::new();
12571
12572 let reversed = self.selections.oldest::<usize>(cx).reversed;
12573 let buffer = &display_map.buffer_snapshot;
12574 let query_matches = select_next_state
12575 .query
12576 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12577
12578 for query_match in query_matches.into_iter() {
12579 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12580 let offset_range = if reversed {
12581 query_match.end()..query_match.start()
12582 } else {
12583 query_match.start()..query_match.end()
12584 };
12585 let display_range = offset_range.start.to_display_point(&display_map)
12586 ..offset_range.end.to_display_point(&display_map);
12587
12588 if !select_next_state.wordwise
12589 || (!movement::is_inside_word(&display_map, display_range.start)
12590 && !movement::is_inside_word(&display_map, display_range.end))
12591 {
12592 new_selections.push(offset_range.start..offset_range.end);
12593 }
12594 }
12595
12596 select_next_state.done = true;
12597 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12598 self.change_selections(None, window, cx, |selections| {
12599 selections.select_ranges(new_selections)
12600 });
12601
12602 Ok(())
12603 }
12604
12605 pub fn select_next(
12606 &mut self,
12607 action: &SelectNext,
12608 window: &mut Window,
12609 cx: &mut Context<Self>,
12610 ) -> Result<()> {
12611 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12612 self.push_to_selection_history();
12613 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12614 self.select_next_match_internal(
12615 &display_map,
12616 action.replace_newest,
12617 Some(Autoscroll::newest()),
12618 window,
12619 cx,
12620 )?;
12621 Ok(())
12622 }
12623
12624 pub fn select_previous(
12625 &mut self,
12626 action: &SelectPrevious,
12627 window: &mut Window,
12628 cx: &mut Context<Self>,
12629 ) -> Result<()> {
12630 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12631 self.push_to_selection_history();
12632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12633 let buffer = &display_map.buffer_snapshot;
12634 let mut selections = self.selections.all::<usize>(cx);
12635 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12636 let query = &select_prev_state.query;
12637 if !select_prev_state.done {
12638 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12639 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12640 let mut next_selected_range = None;
12641 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12642 let bytes_before_last_selection =
12643 buffer.reversed_bytes_in_range(0..last_selection.start);
12644 let bytes_after_first_selection =
12645 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12646 let query_matches = query
12647 .stream_find_iter(bytes_before_last_selection)
12648 .map(|result| (last_selection.start, result))
12649 .chain(
12650 query
12651 .stream_find_iter(bytes_after_first_selection)
12652 .map(|result| (buffer.len(), result)),
12653 );
12654 for (end_offset, query_match) in query_matches {
12655 let query_match = query_match.unwrap(); // can only fail due to I/O
12656 let offset_range =
12657 end_offset - query_match.end()..end_offset - query_match.start();
12658 let display_range = offset_range.start.to_display_point(&display_map)
12659 ..offset_range.end.to_display_point(&display_map);
12660
12661 if !select_prev_state.wordwise
12662 || (!movement::is_inside_word(&display_map, display_range.start)
12663 && !movement::is_inside_word(&display_map, display_range.end))
12664 {
12665 next_selected_range = Some(offset_range);
12666 break;
12667 }
12668 }
12669
12670 if let Some(next_selected_range) = next_selected_range {
12671 self.select_match_ranges(
12672 next_selected_range,
12673 last_selection.reversed,
12674 action.replace_newest,
12675 Some(Autoscroll::newest()),
12676 window,
12677 cx,
12678 );
12679 } else {
12680 select_prev_state.done = true;
12681 }
12682 }
12683
12684 self.select_prev_state = Some(select_prev_state);
12685 } else {
12686 let mut only_carets = true;
12687 let mut same_text_selected = true;
12688 let mut selected_text = None;
12689
12690 let mut selections_iter = selections.iter().peekable();
12691 while let Some(selection) = selections_iter.next() {
12692 if selection.start != selection.end {
12693 only_carets = false;
12694 }
12695
12696 if same_text_selected {
12697 if selected_text.is_none() {
12698 selected_text =
12699 Some(buffer.text_for_range(selection.range()).collect::<String>());
12700 }
12701
12702 if let Some(next_selection) = selections_iter.peek() {
12703 if next_selection.range().len() == selection.range().len() {
12704 let next_selected_text = buffer
12705 .text_for_range(next_selection.range())
12706 .collect::<String>();
12707 if Some(next_selected_text) != selected_text {
12708 same_text_selected = false;
12709 selected_text = None;
12710 }
12711 } else {
12712 same_text_selected = false;
12713 selected_text = None;
12714 }
12715 }
12716 }
12717 }
12718
12719 if only_carets {
12720 for selection in &mut selections {
12721 let word_range = movement::surrounding_word(
12722 &display_map,
12723 selection.start.to_display_point(&display_map),
12724 );
12725 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12726 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12727 selection.goal = SelectionGoal::None;
12728 selection.reversed = false;
12729 self.select_match_ranges(
12730 selection.start..selection.end,
12731 selection.reversed,
12732 action.replace_newest,
12733 Some(Autoscroll::newest()),
12734 window,
12735 cx,
12736 );
12737 }
12738 if selections.len() == 1 {
12739 let selection = selections
12740 .last()
12741 .expect("ensured that there's only one selection");
12742 let query = buffer
12743 .text_for_range(selection.start..selection.end)
12744 .collect::<String>();
12745 let is_empty = query.is_empty();
12746 let select_state = SelectNextState {
12747 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12748 wordwise: true,
12749 done: is_empty,
12750 };
12751 self.select_prev_state = Some(select_state);
12752 } else {
12753 self.select_prev_state = None;
12754 }
12755 } else if let Some(selected_text) = selected_text {
12756 self.select_prev_state = Some(SelectNextState {
12757 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12758 wordwise: false,
12759 done: false,
12760 });
12761 self.select_previous(action, window, cx)?;
12762 }
12763 }
12764 Ok(())
12765 }
12766
12767 pub fn find_next_match(
12768 &mut self,
12769 _: &FindNextMatch,
12770 window: &mut Window,
12771 cx: &mut Context<Self>,
12772 ) -> Result<()> {
12773 let selections = self.selections.disjoint_anchors();
12774 match selections.first() {
12775 Some(first) if selections.len() >= 2 => {
12776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12777 s.select_ranges([first.range()]);
12778 });
12779 }
12780 _ => self.select_next(
12781 &SelectNext {
12782 replace_newest: true,
12783 },
12784 window,
12785 cx,
12786 )?,
12787 }
12788 Ok(())
12789 }
12790
12791 pub fn find_previous_match(
12792 &mut self,
12793 _: &FindPreviousMatch,
12794 window: &mut Window,
12795 cx: &mut Context<Self>,
12796 ) -> Result<()> {
12797 let selections = self.selections.disjoint_anchors();
12798 match selections.last() {
12799 Some(last) if selections.len() >= 2 => {
12800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12801 s.select_ranges([last.range()]);
12802 });
12803 }
12804 _ => self.select_previous(
12805 &SelectPrevious {
12806 replace_newest: true,
12807 },
12808 window,
12809 cx,
12810 )?,
12811 }
12812 Ok(())
12813 }
12814
12815 pub fn toggle_comments(
12816 &mut self,
12817 action: &ToggleComments,
12818 window: &mut Window,
12819 cx: &mut Context<Self>,
12820 ) {
12821 if self.read_only(cx) {
12822 return;
12823 }
12824 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12825 let text_layout_details = &self.text_layout_details(window);
12826 self.transact(window, cx, |this, window, cx| {
12827 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12828 let mut edits = Vec::new();
12829 let mut selection_edit_ranges = Vec::new();
12830 let mut last_toggled_row = None;
12831 let snapshot = this.buffer.read(cx).read(cx);
12832 let empty_str: Arc<str> = Arc::default();
12833 let mut suffixes_inserted = Vec::new();
12834 let ignore_indent = action.ignore_indent;
12835
12836 fn comment_prefix_range(
12837 snapshot: &MultiBufferSnapshot,
12838 row: MultiBufferRow,
12839 comment_prefix: &str,
12840 comment_prefix_whitespace: &str,
12841 ignore_indent: bool,
12842 ) -> Range<Point> {
12843 let indent_size = if ignore_indent {
12844 0
12845 } else {
12846 snapshot.indent_size_for_line(row).len
12847 };
12848
12849 let start = Point::new(row.0, indent_size);
12850
12851 let mut line_bytes = snapshot
12852 .bytes_in_range(start..snapshot.max_point())
12853 .flatten()
12854 .copied();
12855
12856 // If this line currently begins with the line comment prefix, then record
12857 // the range containing the prefix.
12858 if line_bytes
12859 .by_ref()
12860 .take(comment_prefix.len())
12861 .eq(comment_prefix.bytes())
12862 {
12863 // Include any whitespace that matches the comment prefix.
12864 let matching_whitespace_len = line_bytes
12865 .zip(comment_prefix_whitespace.bytes())
12866 .take_while(|(a, b)| a == b)
12867 .count() as u32;
12868 let end = Point::new(
12869 start.row,
12870 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12871 );
12872 start..end
12873 } else {
12874 start..start
12875 }
12876 }
12877
12878 fn comment_suffix_range(
12879 snapshot: &MultiBufferSnapshot,
12880 row: MultiBufferRow,
12881 comment_suffix: &str,
12882 comment_suffix_has_leading_space: bool,
12883 ) -> Range<Point> {
12884 let end = Point::new(row.0, snapshot.line_len(row));
12885 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12886
12887 let mut line_end_bytes = snapshot
12888 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12889 .flatten()
12890 .copied();
12891
12892 let leading_space_len = if suffix_start_column > 0
12893 && line_end_bytes.next() == Some(b' ')
12894 && comment_suffix_has_leading_space
12895 {
12896 1
12897 } else {
12898 0
12899 };
12900
12901 // If this line currently begins with the line comment prefix, then record
12902 // the range containing the prefix.
12903 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12904 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12905 start..end
12906 } else {
12907 end..end
12908 }
12909 }
12910
12911 // TODO: Handle selections that cross excerpts
12912 for selection in &mut selections {
12913 let start_column = snapshot
12914 .indent_size_for_line(MultiBufferRow(selection.start.row))
12915 .len;
12916 let language = if let Some(language) =
12917 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12918 {
12919 language
12920 } else {
12921 continue;
12922 };
12923
12924 selection_edit_ranges.clear();
12925
12926 // If multiple selections contain a given row, avoid processing that
12927 // row more than once.
12928 let mut start_row = MultiBufferRow(selection.start.row);
12929 if last_toggled_row == Some(start_row) {
12930 start_row = start_row.next_row();
12931 }
12932 let end_row =
12933 if selection.end.row > selection.start.row && selection.end.column == 0 {
12934 MultiBufferRow(selection.end.row - 1)
12935 } else {
12936 MultiBufferRow(selection.end.row)
12937 };
12938 last_toggled_row = Some(end_row);
12939
12940 if start_row > end_row {
12941 continue;
12942 }
12943
12944 // If the language has line comments, toggle those.
12945 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12946
12947 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12948 if ignore_indent {
12949 full_comment_prefixes = full_comment_prefixes
12950 .into_iter()
12951 .map(|s| Arc::from(s.trim_end()))
12952 .collect();
12953 }
12954
12955 if !full_comment_prefixes.is_empty() {
12956 let first_prefix = full_comment_prefixes
12957 .first()
12958 .expect("prefixes is non-empty");
12959 let prefix_trimmed_lengths = full_comment_prefixes
12960 .iter()
12961 .map(|p| p.trim_end_matches(' ').len())
12962 .collect::<SmallVec<[usize; 4]>>();
12963
12964 let mut all_selection_lines_are_comments = true;
12965
12966 for row in start_row.0..=end_row.0 {
12967 let row = MultiBufferRow(row);
12968 if start_row < end_row && snapshot.is_line_blank(row) {
12969 continue;
12970 }
12971
12972 let prefix_range = full_comment_prefixes
12973 .iter()
12974 .zip(prefix_trimmed_lengths.iter().copied())
12975 .map(|(prefix, trimmed_prefix_len)| {
12976 comment_prefix_range(
12977 snapshot.deref(),
12978 row,
12979 &prefix[..trimmed_prefix_len],
12980 &prefix[trimmed_prefix_len..],
12981 ignore_indent,
12982 )
12983 })
12984 .max_by_key(|range| range.end.column - range.start.column)
12985 .expect("prefixes is non-empty");
12986
12987 if prefix_range.is_empty() {
12988 all_selection_lines_are_comments = false;
12989 }
12990
12991 selection_edit_ranges.push(prefix_range);
12992 }
12993
12994 if all_selection_lines_are_comments {
12995 edits.extend(
12996 selection_edit_ranges
12997 .iter()
12998 .cloned()
12999 .map(|range| (range, empty_str.clone())),
13000 );
13001 } else {
13002 let min_column = selection_edit_ranges
13003 .iter()
13004 .map(|range| range.start.column)
13005 .min()
13006 .unwrap_or(0);
13007 edits.extend(selection_edit_ranges.iter().map(|range| {
13008 let position = Point::new(range.start.row, min_column);
13009 (position..position, first_prefix.clone())
13010 }));
13011 }
13012 } else if let Some((full_comment_prefix, comment_suffix)) =
13013 language.block_comment_delimiters()
13014 {
13015 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13016 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13017 let prefix_range = comment_prefix_range(
13018 snapshot.deref(),
13019 start_row,
13020 comment_prefix,
13021 comment_prefix_whitespace,
13022 ignore_indent,
13023 );
13024 let suffix_range = comment_suffix_range(
13025 snapshot.deref(),
13026 end_row,
13027 comment_suffix.trim_start_matches(' '),
13028 comment_suffix.starts_with(' '),
13029 );
13030
13031 if prefix_range.is_empty() || suffix_range.is_empty() {
13032 edits.push((
13033 prefix_range.start..prefix_range.start,
13034 full_comment_prefix.clone(),
13035 ));
13036 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13037 suffixes_inserted.push((end_row, comment_suffix.len()));
13038 } else {
13039 edits.push((prefix_range, empty_str.clone()));
13040 edits.push((suffix_range, empty_str.clone()));
13041 }
13042 } else {
13043 continue;
13044 }
13045 }
13046
13047 drop(snapshot);
13048 this.buffer.update(cx, |buffer, cx| {
13049 buffer.edit(edits, None, cx);
13050 });
13051
13052 // Adjust selections so that they end before any comment suffixes that
13053 // were inserted.
13054 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13055 let mut selections = this.selections.all::<Point>(cx);
13056 let snapshot = this.buffer.read(cx).read(cx);
13057 for selection in &mut selections {
13058 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13059 match row.cmp(&MultiBufferRow(selection.end.row)) {
13060 Ordering::Less => {
13061 suffixes_inserted.next();
13062 continue;
13063 }
13064 Ordering::Greater => break,
13065 Ordering::Equal => {
13066 if selection.end.column == snapshot.line_len(row) {
13067 if selection.is_empty() {
13068 selection.start.column -= suffix_len as u32;
13069 }
13070 selection.end.column -= suffix_len as u32;
13071 }
13072 break;
13073 }
13074 }
13075 }
13076 }
13077
13078 drop(snapshot);
13079 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13080 s.select(selections)
13081 });
13082
13083 let selections = this.selections.all::<Point>(cx);
13084 let selections_on_single_row = selections.windows(2).all(|selections| {
13085 selections[0].start.row == selections[1].start.row
13086 && selections[0].end.row == selections[1].end.row
13087 && selections[0].start.row == selections[0].end.row
13088 });
13089 let selections_selecting = selections
13090 .iter()
13091 .any(|selection| selection.start != selection.end);
13092 let advance_downwards = action.advance_downwards
13093 && selections_on_single_row
13094 && !selections_selecting
13095 && !matches!(this.mode, EditorMode::SingleLine { .. });
13096
13097 if advance_downwards {
13098 let snapshot = this.buffer.read(cx).snapshot(cx);
13099
13100 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13101 s.move_cursors_with(|display_snapshot, display_point, _| {
13102 let mut point = display_point.to_point(display_snapshot);
13103 point.row += 1;
13104 point = snapshot.clip_point(point, Bias::Left);
13105 let display_point = point.to_display_point(display_snapshot);
13106 let goal = SelectionGoal::HorizontalPosition(
13107 display_snapshot
13108 .x_for_display_point(display_point, text_layout_details)
13109 .into(),
13110 );
13111 (display_point, goal)
13112 })
13113 });
13114 }
13115 });
13116 }
13117
13118 pub fn select_enclosing_symbol(
13119 &mut self,
13120 _: &SelectEnclosingSymbol,
13121 window: &mut Window,
13122 cx: &mut Context<Self>,
13123 ) {
13124 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13125
13126 let buffer = self.buffer.read(cx).snapshot(cx);
13127 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13128
13129 fn update_selection(
13130 selection: &Selection<usize>,
13131 buffer_snap: &MultiBufferSnapshot,
13132 ) -> Option<Selection<usize>> {
13133 let cursor = selection.head();
13134 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13135 for symbol in symbols.iter().rev() {
13136 let start = symbol.range.start.to_offset(buffer_snap);
13137 let end = symbol.range.end.to_offset(buffer_snap);
13138 let new_range = start..end;
13139 if start < selection.start || end > selection.end {
13140 return Some(Selection {
13141 id: selection.id,
13142 start: new_range.start,
13143 end: new_range.end,
13144 goal: SelectionGoal::None,
13145 reversed: selection.reversed,
13146 });
13147 }
13148 }
13149 None
13150 }
13151
13152 let mut selected_larger_symbol = false;
13153 let new_selections = old_selections
13154 .iter()
13155 .map(|selection| match update_selection(selection, &buffer) {
13156 Some(new_selection) => {
13157 if new_selection.range() != selection.range() {
13158 selected_larger_symbol = true;
13159 }
13160 new_selection
13161 }
13162 None => selection.clone(),
13163 })
13164 .collect::<Vec<_>>();
13165
13166 if selected_larger_symbol {
13167 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13168 s.select(new_selections);
13169 });
13170 }
13171 }
13172
13173 pub fn select_larger_syntax_node(
13174 &mut self,
13175 _: &SelectLargerSyntaxNode,
13176 window: &mut Window,
13177 cx: &mut Context<Self>,
13178 ) {
13179 let Some(visible_row_count) = self.visible_row_count() else {
13180 return;
13181 };
13182 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13183 if old_selections.is_empty() {
13184 return;
13185 }
13186
13187 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13188
13189 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13190 let buffer = self.buffer.read(cx).snapshot(cx);
13191
13192 let mut selected_larger_node = false;
13193 let mut new_selections = old_selections
13194 .iter()
13195 .map(|selection| {
13196 let old_range = selection.start..selection.end;
13197
13198 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13199 // manually select word at selection
13200 if ["string_content", "inline"].contains(&node.kind()) {
13201 let word_range = {
13202 let display_point = buffer
13203 .offset_to_point(old_range.start)
13204 .to_display_point(&display_map);
13205 let Range { start, end } =
13206 movement::surrounding_word(&display_map, display_point);
13207 start.to_point(&display_map).to_offset(&buffer)
13208 ..end.to_point(&display_map).to_offset(&buffer)
13209 };
13210 // ignore if word is already selected
13211 if !word_range.is_empty() && old_range != word_range {
13212 let last_word_range = {
13213 let display_point = buffer
13214 .offset_to_point(old_range.end)
13215 .to_display_point(&display_map);
13216 let Range { start, end } =
13217 movement::surrounding_word(&display_map, display_point);
13218 start.to_point(&display_map).to_offset(&buffer)
13219 ..end.to_point(&display_map).to_offset(&buffer)
13220 };
13221 // only select word if start and end point belongs to same word
13222 if word_range == last_word_range {
13223 selected_larger_node = true;
13224 return Selection {
13225 id: selection.id,
13226 start: word_range.start,
13227 end: word_range.end,
13228 goal: SelectionGoal::None,
13229 reversed: selection.reversed,
13230 };
13231 }
13232 }
13233 }
13234 }
13235
13236 let mut new_range = old_range.clone();
13237 while let Some((_node, containing_range)) =
13238 buffer.syntax_ancestor(new_range.clone())
13239 {
13240 new_range = match containing_range {
13241 MultiOrSingleBufferOffsetRange::Single(_) => break,
13242 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13243 };
13244 if !display_map.intersects_fold(new_range.start)
13245 && !display_map.intersects_fold(new_range.end)
13246 {
13247 break;
13248 }
13249 }
13250
13251 selected_larger_node |= new_range != old_range;
13252 Selection {
13253 id: selection.id,
13254 start: new_range.start,
13255 end: new_range.end,
13256 goal: SelectionGoal::None,
13257 reversed: selection.reversed,
13258 }
13259 })
13260 .collect::<Vec<_>>();
13261
13262 if !selected_larger_node {
13263 return; // don't put this call in the history
13264 }
13265
13266 // scroll based on transformation done to the last selection created by the user
13267 let (last_old, last_new) = old_selections
13268 .last()
13269 .zip(new_selections.last().cloned())
13270 .expect("old_selections isn't empty");
13271
13272 // revert selection
13273 let is_selection_reversed = {
13274 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13275 new_selections.last_mut().expect("checked above").reversed =
13276 should_newest_selection_be_reversed;
13277 should_newest_selection_be_reversed
13278 };
13279
13280 if selected_larger_node {
13281 self.select_syntax_node_history.disable_clearing = true;
13282 self.change_selections(None, window, cx, |s| {
13283 s.select(new_selections.clone());
13284 });
13285 self.select_syntax_node_history.disable_clearing = false;
13286 }
13287
13288 let start_row = last_new.start.to_display_point(&display_map).row().0;
13289 let end_row = last_new.end.to_display_point(&display_map).row().0;
13290 let selection_height = end_row - start_row + 1;
13291 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13292
13293 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13294 let scroll_behavior = if fits_on_the_screen {
13295 self.request_autoscroll(Autoscroll::fit(), cx);
13296 SelectSyntaxNodeScrollBehavior::FitSelection
13297 } else if is_selection_reversed {
13298 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13299 SelectSyntaxNodeScrollBehavior::CursorTop
13300 } else {
13301 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13302 SelectSyntaxNodeScrollBehavior::CursorBottom
13303 };
13304
13305 self.select_syntax_node_history.push((
13306 old_selections,
13307 scroll_behavior,
13308 is_selection_reversed,
13309 ));
13310 }
13311
13312 pub fn select_smaller_syntax_node(
13313 &mut self,
13314 _: &SelectSmallerSyntaxNode,
13315 window: &mut Window,
13316 cx: &mut Context<Self>,
13317 ) {
13318 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13319
13320 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13321 self.select_syntax_node_history.pop()
13322 {
13323 if let Some(selection) = selections.last_mut() {
13324 selection.reversed = is_selection_reversed;
13325 }
13326
13327 self.select_syntax_node_history.disable_clearing = true;
13328 self.change_selections(None, window, cx, |s| {
13329 s.select(selections.to_vec());
13330 });
13331 self.select_syntax_node_history.disable_clearing = false;
13332
13333 match scroll_behavior {
13334 SelectSyntaxNodeScrollBehavior::CursorTop => {
13335 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13336 }
13337 SelectSyntaxNodeScrollBehavior::FitSelection => {
13338 self.request_autoscroll(Autoscroll::fit(), cx);
13339 }
13340 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13341 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13342 }
13343 }
13344 }
13345 }
13346
13347 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13348 if !EditorSettings::get_global(cx).gutter.runnables {
13349 self.clear_tasks();
13350 return Task::ready(());
13351 }
13352 let project = self.project.as_ref().map(Entity::downgrade);
13353 let task_sources = self.lsp_task_sources(cx);
13354 cx.spawn_in(window, async move |editor, cx| {
13355 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13356 let Some(project) = project.and_then(|p| p.upgrade()) else {
13357 return;
13358 };
13359 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13360 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13361 }) else {
13362 return;
13363 };
13364
13365 let hide_runnables = project
13366 .update(cx, |project, cx| {
13367 // Do not display any test indicators in non-dev server remote projects.
13368 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13369 })
13370 .unwrap_or(true);
13371 if hide_runnables {
13372 return;
13373 }
13374 let new_rows =
13375 cx.background_spawn({
13376 let snapshot = display_snapshot.clone();
13377 async move {
13378 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13379 }
13380 })
13381 .await;
13382 let Ok(lsp_tasks) =
13383 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13384 else {
13385 return;
13386 };
13387 let lsp_tasks = lsp_tasks.await;
13388
13389 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13390 lsp_tasks
13391 .into_iter()
13392 .flat_map(|(kind, tasks)| {
13393 tasks.into_iter().filter_map(move |(location, task)| {
13394 Some((kind.clone(), location?, task))
13395 })
13396 })
13397 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13398 let buffer = location.target.buffer;
13399 let buffer_snapshot = buffer.read(cx).snapshot();
13400 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13401 |(excerpt_id, snapshot, _)| {
13402 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13403 display_snapshot
13404 .buffer_snapshot
13405 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13406 } else {
13407 None
13408 }
13409 },
13410 );
13411 if let Some(offset) = offset {
13412 let task_buffer_range =
13413 location.target.range.to_point(&buffer_snapshot);
13414 let context_buffer_range =
13415 task_buffer_range.to_offset(&buffer_snapshot);
13416 let context_range = BufferOffset(context_buffer_range.start)
13417 ..BufferOffset(context_buffer_range.end);
13418
13419 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13420 .or_insert_with(|| RunnableTasks {
13421 templates: Vec::new(),
13422 offset,
13423 column: task_buffer_range.start.column,
13424 extra_variables: HashMap::default(),
13425 context_range,
13426 })
13427 .templates
13428 .push((kind, task.original_task().clone()));
13429 }
13430
13431 acc
13432 })
13433 }) else {
13434 return;
13435 };
13436
13437 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13438 editor
13439 .update(cx, |editor, _| {
13440 editor.clear_tasks();
13441 for (key, mut value) in rows {
13442 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13443 value.templates.extend(lsp_tasks.templates);
13444 }
13445
13446 editor.insert_tasks(key, value);
13447 }
13448 for (key, value) in lsp_tasks_by_rows {
13449 editor.insert_tasks(key, value);
13450 }
13451 })
13452 .ok();
13453 })
13454 }
13455 fn fetch_runnable_ranges(
13456 snapshot: &DisplaySnapshot,
13457 range: Range<Anchor>,
13458 ) -> Vec<language::RunnableRange> {
13459 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13460 }
13461
13462 fn runnable_rows(
13463 project: Entity<Project>,
13464 snapshot: DisplaySnapshot,
13465 runnable_ranges: Vec<RunnableRange>,
13466 mut cx: AsyncWindowContext,
13467 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13468 runnable_ranges
13469 .into_iter()
13470 .filter_map(|mut runnable| {
13471 let tasks = cx
13472 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13473 .ok()?;
13474 if tasks.is_empty() {
13475 return None;
13476 }
13477
13478 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13479
13480 let row = snapshot
13481 .buffer_snapshot
13482 .buffer_line_for_row(MultiBufferRow(point.row))?
13483 .1
13484 .start
13485 .row;
13486
13487 let context_range =
13488 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13489 Some((
13490 (runnable.buffer_id, row),
13491 RunnableTasks {
13492 templates: tasks,
13493 offset: snapshot
13494 .buffer_snapshot
13495 .anchor_before(runnable.run_range.start),
13496 context_range,
13497 column: point.column,
13498 extra_variables: runnable.extra_captures,
13499 },
13500 ))
13501 })
13502 .collect()
13503 }
13504
13505 fn templates_with_tags(
13506 project: &Entity<Project>,
13507 runnable: &mut Runnable,
13508 cx: &mut App,
13509 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13510 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13511 let (worktree_id, file) = project
13512 .buffer_for_id(runnable.buffer, cx)
13513 .and_then(|buffer| buffer.read(cx).file())
13514 .map(|file| (file.worktree_id(cx), file.clone()))
13515 .unzip();
13516
13517 (
13518 project.task_store().read(cx).task_inventory().cloned(),
13519 worktree_id,
13520 file,
13521 )
13522 });
13523
13524 let mut templates_with_tags = mem::take(&mut runnable.tags)
13525 .into_iter()
13526 .flat_map(|RunnableTag(tag)| {
13527 inventory
13528 .as_ref()
13529 .into_iter()
13530 .flat_map(|inventory| {
13531 inventory.read(cx).list_tasks(
13532 file.clone(),
13533 Some(runnable.language.clone()),
13534 worktree_id,
13535 cx,
13536 )
13537 })
13538 .filter(move |(_, template)| {
13539 template.tags.iter().any(|source_tag| source_tag == &tag)
13540 })
13541 })
13542 .sorted_by_key(|(kind, _)| kind.to_owned())
13543 .collect::<Vec<_>>();
13544 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13545 // Strongest source wins; if we have worktree tag binding, prefer that to
13546 // global and language bindings;
13547 // if we have a global binding, prefer that to language binding.
13548 let first_mismatch = templates_with_tags
13549 .iter()
13550 .position(|(tag_source, _)| tag_source != leading_tag_source);
13551 if let Some(index) = first_mismatch {
13552 templates_with_tags.truncate(index);
13553 }
13554 }
13555
13556 templates_with_tags
13557 }
13558
13559 pub fn move_to_enclosing_bracket(
13560 &mut self,
13561 _: &MoveToEnclosingBracket,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13566 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13567 s.move_offsets_with(|snapshot, selection| {
13568 let Some(enclosing_bracket_ranges) =
13569 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13570 else {
13571 return;
13572 };
13573
13574 let mut best_length = usize::MAX;
13575 let mut best_inside = false;
13576 let mut best_in_bracket_range = false;
13577 let mut best_destination = None;
13578 for (open, close) in enclosing_bracket_ranges {
13579 let close = close.to_inclusive();
13580 let length = close.end() - open.start;
13581 let inside = selection.start >= open.end && selection.end <= *close.start();
13582 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13583 || close.contains(&selection.head());
13584
13585 // If best is next to a bracket and current isn't, skip
13586 if !in_bracket_range && best_in_bracket_range {
13587 continue;
13588 }
13589
13590 // Prefer smaller lengths unless best is inside and current isn't
13591 if length > best_length && (best_inside || !inside) {
13592 continue;
13593 }
13594
13595 best_length = length;
13596 best_inside = inside;
13597 best_in_bracket_range = in_bracket_range;
13598 best_destination = Some(
13599 if close.contains(&selection.start) && close.contains(&selection.end) {
13600 if inside { open.end } else { open.start }
13601 } else if inside {
13602 *close.start()
13603 } else {
13604 *close.end()
13605 },
13606 );
13607 }
13608
13609 if let Some(destination) = best_destination {
13610 selection.collapse_to(destination, SelectionGoal::None);
13611 }
13612 })
13613 });
13614 }
13615
13616 pub fn undo_selection(
13617 &mut self,
13618 _: &UndoSelection,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13623 self.end_selection(window, cx);
13624 self.selection_history.mode = SelectionHistoryMode::Undoing;
13625 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13626 self.change_selections(None, window, cx, |s| {
13627 s.select_anchors(entry.selections.to_vec())
13628 });
13629 self.select_next_state = entry.select_next_state;
13630 self.select_prev_state = entry.select_prev_state;
13631 self.add_selections_state = entry.add_selections_state;
13632 self.request_autoscroll(Autoscroll::newest(), cx);
13633 }
13634 self.selection_history.mode = SelectionHistoryMode::Normal;
13635 }
13636
13637 pub fn redo_selection(
13638 &mut self,
13639 _: &RedoSelection,
13640 window: &mut Window,
13641 cx: &mut Context<Self>,
13642 ) {
13643 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13644 self.end_selection(window, cx);
13645 self.selection_history.mode = SelectionHistoryMode::Redoing;
13646 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13647 self.change_selections(None, window, cx, |s| {
13648 s.select_anchors(entry.selections.to_vec())
13649 });
13650 self.select_next_state = entry.select_next_state;
13651 self.select_prev_state = entry.select_prev_state;
13652 self.add_selections_state = entry.add_selections_state;
13653 self.request_autoscroll(Autoscroll::newest(), cx);
13654 }
13655 self.selection_history.mode = SelectionHistoryMode::Normal;
13656 }
13657
13658 pub fn expand_excerpts(
13659 &mut self,
13660 action: &ExpandExcerpts,
13661 _: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13665 }
13666
13667 pub fn expand_excerpts_down(
13668 &mut self,
13669 action: &ExpandExcerptsDown,
13670 _: &mut Window,
13671 cx: &mut Context<Self>,
13672 ) {
13673 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13674 }
13675
13676 pub fn expand_excerpts_up(
13677 &mut self,
13678 action: &ExpandExcerptsUp,
13679 _: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13683 }
13684
13685 pub fn expand_excerpts_for_direction(
13686 &mut self,
13687 lines: u32,
13688 direction: ExpandExcerptDirection,
13689
13690 cx: &mut Context<Self>,
13691 ) {
13692 let selections = self.selections.disjoint_anchors();
13693
13694 let lines = if lines == 0 {
13695 EditorSettings::get_global(cx).expand_excerpt_lines
13696 } else {
13697 lines
13698 };
13699
13700 self.buffer.update(cx, |buffer, cx| {
13701 let snapshot = buffer.snapshot(cx);
13702 let mut excerpt_ids = selections
13703 .iter()
13704 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13705 .collect::<Vec<_>>();
13706 excerpt_ids.sort();
13707 excerpt_ids.dedup();
13708 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13709 })
13710 }
13711
13712 pub fn expand_excerpt(
13713 &mut self,
13714 excerpt: ExcerptId,
13715 direction: ExpandExcerptDirection,
13716 window: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 let current_scroll_position = self.scroll_position(cx);
13720 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13721 let mut should_scroll_up = false;
13722
13723 if direction == ExpandExcerptDirection::Down {
13724 let multi_buffer = self.buffer.read(cx);
13725 let snapshot = multi_buffer.snapshot(cx);
13726 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13727 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13728 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13729 let buffer_snapshot = buffer.read(cx).snapshot();
13730 let excerpt_end_row =
13731 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13732 let last_row = buffer_snapshot.max_point().row;
13733 let lines_below = last_row.saturating_sub(excerpt_end_row);
13734 should_scroll_up = lines_below >= lines_to_expand;
13735 }
13736 }
13737 }
13738 }
13739
13740 self.buffer.update(cx, |buffer, cx| {
13741 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13742 });
13743
13744 if should_scroll_up {
13745 let new_scroll_position =
13746 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13747 self.set_scroll_position(new_scroll_position, window, cx);
13748 }
13749 }
13750
13751 pub fn go_to_singleton_buffer_point(
13752 &mut self,
13753 point: Point,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) {
13757 self.go_to_singleton_buffer_range(point..point, window, cx);
13758 }
13759
13760 pub fn go_to_singleton_buffer_range(
13761 &mut self,
13762 range: Range<Point>,
13763 window: &mut Window,
13764 cx: &mut Context<Self>,
13765 ) {
13766 let multibuffer = self.buffer().read(cx);
13767 let Some(buffer) = multibuffer.as_singleton() else {
13768 return;
13769 };
13770 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13771 return;
13772 };
13773 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13774 return;
13775 };
13776 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13777 s.select_anchor_ranges([start..end])
13778 });
13779 }
13780
13781 pub fn go_to_diagnostic(
13782 &mut self,
13783 _: &GoToDiagnostic,
13784 window: &mut Window,
13785 cx: &mut Context<Self>,
13786 ) {
13787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13788 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13789 }
13790
13791 pub fn go_to_prev_diagnostic(
13792 &mut self,
13793 _: &GoToPreviousDiagnostic,
13794 window: &mut Window,
13795 cx: &mut Context<Self>,
13796 ) {
13797 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13798 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13799 }
13800
13801 pub fn go_to_diagnostic_impl(
13802 &mut self,
13803 direction: Direction,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 let buffer = self.buffer.read(cx).snapshot(cx);
13808 let selection = self.selections.newest::<usize>(cx);
13809
13810 let mut active_group_id = None;
13811 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13812 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13813 active_group_id = Some(active_group.group_id);
13814 }
13815 }
13816
13817 fn filtered(
13818 snapshot: EditorSnapshot,
13819 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13820 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13821 diagnostics
13822 .filter(|entry| entry.range.start != entry.range.end)
13823 .filter(|entry| !entry.diagnostic.is_unnecessary)
13824 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13825 }
13826
13827 let snapshot = self.snapshot(window, cx);
13828 let before = filtered(
13829 snapshot.clone(),
13830 buffer
13831 .diagnostics_in_range(0..selection.start)
13832 .filter(|entry| entry.range.start <= selection.start),
13833 );
13834 let after = filtered(
13835 snapshot,
13836 buffer
13837 .diagnostics_in_range(selection.start..buffer.len())
13838 .filter(|entry| entry.range.start >= selection.start),
13839 );
13840
13841 let mut found: Option<DiagnosticEntry<usize>> = None;
13842 if direction == Direction::Prev {
13843 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13844 {
13845 for diagnostic in prev_diagnostics.into_iter().rev() {
13846 if diagnostic.range.start != selection.start
13847 || active_group_id
13848 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13849 {
13850 found = Some(diagnostic);
13851 break 'outer;
13852 }
13853 }
13854 }
13855 } else {
13856 for diagnostic in after.chain(before) {
13857 if diagnostic.range.start != selection.start
13858 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13859 {
13860 found = Some(diagnostic);
13861 break;
13862 }
13863 }
13864 }
13865 let Some(next_diagnostic) = found else {
13866 return;
13867 };
13868
13869 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13870 return;
13871 };
13872 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13873 s.select_ranges(vec![
13874 next_diagnostic.range.start..next_diagnostic.range.start,
13875 ])
13876 });
13877 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13878 self.refresh_inline_completion(false, true, window, cx);
13879 }
13880
13881 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13882 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13883 let snapshot = self.snapshot(window, cx);
13884 let selection = self.selections.newest::<Point>(cx);
13885 self.go_to_hunk_before_or_after_position(
13886 &snapshot,
13887 selection.head(),
13888 Direction::Next,
13889 window,
13890 cx,
13891 );
13892 }
13893
13894 pub fn go_to_hunk_before_or_after_position(
13895 &mut self,
13896 snapshot: &EditorSnapshot,
13897 position: Point,
13898 direction: Direction,
13899 window: &mut Window,
13900 cx: &mut Context<Editor>,
13901 ) {
13902 let row = if direction == Direction::Next {
13903 self.hunk_after_position(snapshot, position)
13904 .map(|hunk| hunk.row_range.start)
13905 } else {
13906 self.hunk_before_position(snapshot, position)
13907 };
13908
13909 if let Some(row) = row {
13910 let destination = Point::new(row.0, 0);
13911 let autoscroll = Autoscroll::center();
13912
13913 self.unfold_ranges(&[destination..destination], false, false, cx);
13914 self.change_selections(Some(autoscroll), window, cx, |s| {
13915 s.select_ranges([destination..destination]);
13916 });
13917 }
13918 }
13919
13920 fn hunk_after_position(
13921 &mut self,
13922 snapshot: &EditorSnapshot,
13923 position: Point,
13924 ) -> Option<MultiBufferDiffHunk> {
13925 snapshot
13926 .buffer_snapshot
13927 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13928 .find(|hunk| hunk.row_range.start.0 > position.row)
13929 .or_else(|| {
13930 snapshot
13931 .buffer_snapshot
13932 .diff_hunks_in_range(Point::zero()..position)
13933 .find(|hunk| hunk.row_range.end.0 < position.row)
13934 })
13935 }
13936
13937 fn go_to_prev_hunk(
13938 &mut self,
13939 _: &GoToPreviousHunk,
13940 window: &mut Window,
13941 cx: &mut Context<Self>,
13942 ) {
13943 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13944 let snapshot = self.snapshot(window, cx);
13945 let selection = self.selections.newest::<Point>(cx);
13946 self.go_to_hunk_before_or_after_position(
13947 &snapshot,
13948 selection.head(),
13949 Direction::Prev,
13950 window,
13951 cx,
13952 );
13953 }
13954
13955 fn hunk_before_position(
13956 &mut self,
13957 snapshot: &EditorSnapshot,
13958 position: Point,
13959 ) -> Option<MultiBufferRow> {
13960 snapshot
13961 .buffer_snapshot
13962 .diff_hunk_before(position)
13963 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13964 }
13965
13966 fn go_to_next_change(
13967 &mut self,
13968 _: &GoToNextChange,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) {
13972 if let Some(selections) = self
13973 .change_list
13974 .next_change(1, Direction::Next)
13975 .map(|s| s.to_vec())
13976 {
13977 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13978 let map = s.display_map();
13979 s.select_display_ranges(selections.iter().map(|a| {
13980 let point = a.to_display_point(&map);
13981 point..point
13982 }))
13983 })
13984 }
13985 }
13986
13987 fn go_to_previous_change(
13988 &mut self,
13989 _: &GoToPreviousChange,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 if let Some(selections) = self
13994 .change_list
13995 .next_change(1, Direction::Prev)
13996 .map(|s| s.to_vec())
13997 {
13998 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13999 let map = s.display_map();
14000 s.select_display_ranges(selections.iter().map(|a| {
14001 let point = a.to_display_point(&map);
14002 point..point
14003 }))
14004 })
14005 }
14006 }
14007
14008 fn go_to_line<T: 'static>(
14009 &mut self,
14010 position: Anchor,
14011 highlight_color: Option<Hsla>,
14012 window: &mut Window,
14013 cx: &mut Context<Self>,
14014 ) {
14015 let snapshot = self.snapshot(window, cx).display_snapshot;
14016 let position = position.to_point(&snapshot.buffer_snapshot);
14017 let start = snapshot
14018 .buffer_snapshot
14019 .clip_point(Point::new(position.row, 0), Bias::Left);
14020 let end = start + Point::new(1, 0);
14021 let start = snapshot.buffer_snapshot.anchor_before(start);
14022 let end = snapshot.buffer_snapshot.anchor_before(end);
14023
14024 self.highlight_rows::<T>(
14025 start..end,
14026 highlight_color
14027 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14028 Default::default(),
14029 cx,
14030 );
14031
14032 if self.buffer.read(cx).is_singleton() {
14033 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14034 }
14035 }
14036
14037 pub fn go_to_definition(
14038 &mut self,
14039 _: &GoToDefinition,
14040 window: &mut Window,
14041 cx: &mut Context<Self>,
14042 ) -> Task<Result<Navigated>> {
14043 let definition =
14044 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14045 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14046 cx.spawn_in(window, async move |editor, cx| {
14047 if definition.await? == Navigated::Yes {
14048 return Ok(Navigated::Yes);
14049 }
14050 match fallback_strategy {
14051 GoToDefinitionFallback::None => Ok(Navigated::No),
14052 GoToDefinitionFallback::FindAllReferences => {
14053 match editor.update_in(cx, |editor, window, cx| {
14054 editor.find_all_references(&FindAllReferences, window, cx)
14055 })? {
14056 Some(references) => references.await,
14057 None => Ok(Navigated::No),
14058 }
14059 }
14060 }
14061 })
14062 }
14063
14064 pub fn go_to_declaration(
14065 &mut self,
14066 _: &GoToDeclaration,
14067 window: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) -> Task<Result<Navigated>> {
14070 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14071 }
14072
14073 pub fn go_to_declaration_split(
14074 &mut self,
14075 _: &GoToDeclaration,
14076 window: &mut Window,
14077 cx: &mut Context<Self>,
14078 ) -> Task<Result<Navigated>> {
14079 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14080 }
14081
14082 pub fn go_to_implementation(
14083 &mut self,
14084 _: &GoToImplementation,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) -> Task<Result<Navigated>> {
14088 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14089 }
14090
14091 pub fn go_to_implementation_split(
14092 &mut self,
14093 _: &GoToImplementationSplit,
14094 window: &mut Window,
14095 cx: &mut Context<Self>,
14096 ) -> Task<Result<Navigated>> {
14097 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14098 }
14099
14100 pub fn go_to_type_definition(
14101 &mut self,
14102 _: &GoToTypeDefinition,
14103 window: &mut Window,
14104 cx: &mut Context<Self>,
14105 ) -> Task<Result<Navigated>> {
14106 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14107 }
14108
14109 pub fn go_to_definition_split(
14110 &mut self,
14111 _: &GoToDefinitionSplit,
14112 window: &mut Window,
14113 cx: &mut Context<Self>,
14114 ) -> Task<Result<Navigated>> {
14115 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14116 }
14117
14118 pub fn go_to_type_definition_split(
14119 &mut self,
14120 _: &GoToTypeDefinitionSplit,
14121 window: &mut Window,
14122 cx: &mut Context<Self>,
14123 ) -> Task<Result<Navigated>> {
14124 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14125 }
14126
14127 fn go_to_definition_of_kind(
14128 &mut self,
14129 kind: GotoDefinitionKind,
14130 split: bool,
14131 window: &mut Window,
14132 cx: &mut Context<Self>,
14133 ) -> Task<Result<Navigated>> {
14134 let Some(provider) = self.semantics_provider.clone() else {
14135 return Task::ready(Ok(Navigated::No));
14136 };
14137 let head = self.selections.newest::<usize>(cx).head();
14138 let buffer = self.buffer.read(cx);
14139 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14140 text_anchor
14141 } else {
14142 return Task::ready(Ok(Navigated::No));
14143 };
14144
14145 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14146 return Task::ready(Ok(Navigated::No));
14147 };
14148
14149 cx.spawn_in(window, async move |editor, cx| {
14150 let definitions = definitions.await?;
14151 let navigated = editor
14152 .update_in(cx, |editor, window, cx| {
14153 editor.navigate_to_hover_links(
14154 Some(kind),
14155 definitions
14156 .into_iter()
14157 .filter(|location| {
14158 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14159 })
14160 .map(HoverLink::Text)
14161 .collect::<Vec<_>>(),
14162 split,
14163 window,
14164 cx,
14165 )
14166 })?
14167 .await?;
14168 anyhow::Ok(navigated)
14169 })
14170 }
14171
14172 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14173 let selection = self.selections.newest_anchor();
14174 let head = selection.head();
14175 let tail = selection.tail();
14176
14177 let Some((buffer, start_position)) =
14178 self.buffer.read(cx).text_anchor_for_position(head, cx)
14179 else {
14180 return;
14181 };
14182
14183 let end_position = if head != tail {
14184 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14185 return;
14186 };
14187 Some(pos)
14188 } else {
14189 None
14190 };
14191
14192 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14193 let url = if let Some(end_pos) = end_position {
14194 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14195 } else {
14196 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14197 };
14198
14199 if let Some(url) = url {
14200 editor.update(cx, |_, cx| {
14201 cx.open_url(&url);
14202 })
14203 } else {
14204 Ok(())
14205 }
14206 });
14207
14208 url_finder.detach();
14209 }
14210
14211 pub fn open_selected_filename(
14212 &mut self,
14213 _: &OpenSelectedFilename,
14214 window: &mut Window,
14215 cx: &mut Context<Self>,
14216 ) {
14217 let Some(workspace) = self.workspace() else {
14218 return;
14219 };
14220
14221 let position = self.selections.newest_anchor().head();
14222
14223 let Some((buffer, buffer_position)) =
14224 self.buffer.read(cx).text_anchor_for_position(position, cx)
14225 else {
14226 return;
14227 };
14228
14229 let project = self.project.clone();
14230
14231 cx.spawn_in(window, async move |_, cx| {
14232 let result = find_file(&buffer, project, buffer_position, cx).await;
14233
14234 if let Some((_, path)) = result {
14235 workspace
14236 .update_in(cx, |workspace, window, cx| {
14237 workspace.open_resolved_path(path, window, cx)
14238 })?
14239 .await?;
14240 }
14241 anyhow::Ok(())
14242 })
14243 .detach();
14244 }
14245
14246 pub(crate) fn navigate_to_hover_links(
14247 &mut self,
14248 kind: Option<GotoDefinitionKind>,
14249 mut definitions: Vec<HoverLink>,
14250 split: bool,
14251 window: &mut Window,
14252 cx: &mut Context<Editor>,
14253 ) -> Task<Result<Navigated>> {
14254 // If there is one definition, just open it directly
14255 if definitions.len() == 1 {
14256 let definition = definitions.pop().unwrap();
14257
14258 enum TargetTaskResult {
14259 Location(Option<Location>),
14260 AlreadyNavigated,
14261 }
14262
14263 let target_task = match definition {
14264 HoverLink::Text(link) => {
14265 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14266 }
14267 HoverLink::InlayHint(lsp_location, server_id) => {
14268 let computation =
14269 self.compute_target_location(lsp_location, server_id, window, cx);
14270 cx.background_spawn(async move {
14271 let location = computation.await?;
14272 Ok(TargetTaskResult::Location(location))
14273 })
14274 }
14275 HoverLink::Url(url) => {
14276 cx.open_url(&url);
14277 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14278 }
14279 HoverLink::File(path) => {
14280 if let Some(workspace) = self.workspace() {
14281 cx.spawn_in(window, async move |_, cx| {
14282 workspace
14283 .update_in(cx, |workspace, window, cx| {
14284 workspace.open_resolved_path(path, window, cx)
14285 })?
14286 .await
14287 .map(|_| TargetTaskResult::AlreadyNavigated)
14288 })
14289 } else {
14290 Task::ready(Ok(TargetTaskResult::Location(None)))
14291 }
14292 }
14293 };
14294 cx.spawn_in(window, async move |editor, cx| {
14295 let target = match target_task.await.context("target resolution task")? {
14296 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14297 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14298 TargetTaskResult::Location(Some(target)) => target,
14299 };
14300
14301 editor.update_in(cx, |editor, window, cx| {
14302 let Some(workspace) = editor.workspace() else {
14303 return Navigated::No;
14304 };
14305 let pane = workspace.read(cx).active_pane().clone();
14306
14307 let range = target.range.to_point(target.buffer.read(cx));
14308 let range = editor.range_for_match(&range);
14309 let range = collapse_multiline_range(range);
14310
14311 if !split
14312 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14313 {
14314 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14315 } else {
14316 window.defer(cx, move |window, cx| {
14317 let target_editor: Entity<Self> =
14318 workspace.update(cx, |workspace, cx| {
14319 let pane = if split {
14320 workspace.adjacent_pane(window, cx)
14321 } else {
14322 workspace.active_pane().clone()
14323 };
14324
14325 workspace.open_project_item(
14326 pane,
14327 target.buffer.clone(),
14328 true,
14329 true,
14330 window,
14331 cx,
14332 )
14333 });
14334 target_editor.update(cx, |target_editor, cx| {
14335 // When selecting a definition in a different buffer, disable the nav history
14336 // to avoid creating a history entry at the previous cursor location.
14337 pane.update(cx, |pane, _| pane.disable_history());
14338 target_editor.go_to_singleton_buffer_range(range, window, cx);
14339 pane.update(cx, |pane, _| pane.enable_history());
14340 });
14341 });
14342 }
14343 Navigated::Yes
14344 })
14345 })
14346 } else if !definitions.is_empty() {
14347 cx.spawn_in(window, async move |editor, cx| {
14348 let (title, location_tasks, workspace) = editor
14349 .update_in(cx, |editor, window, cx| {
14350 let tab_kind = match kind {
14351 Some(GotoDefinitionKind::Implementation) => "Implementations",
14352 _ => "Definitions",
14353 };
14354 let title = definitions
14355 .iter()
14356 .find_map(|definition| match definition {
14357 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14358 let buffer = origin.buffer.read(cx);
14359 format!(
14360 "{} for {}",
14361 tab_kind,
14362 buffer
14363 .text_for_range(origin.range.clone())
14364 .collect::<String>()
14365 )
14366 }),
14367 HoverLink::InlayHint(_, _) => None,
14368 HoverLink::Url(_) => None,
14369 HoverLink::File(_) => None,
14370 })
14371 .unwrap_or(tab_kind.to_string());
14372 let location_tasks = definitions
14373 .into_iter()
14374 .map(|definition| match definition {
14375 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14376 HoverLink::InlayHint(lsp_location, server_id) => editor
14377 .compute_target_location(lsp_location, server_id, window, cx),
14378 HoverLink::Url(_) => Task::ready(Ok(None)),
14379 HoverLink::File(_) => Task::ready(Ok(None)),
14380 })
14381 .collect::<Vec<_>>();
14382 (title, location_tasks, editor.workspace().clone())
14383 })
14384 .context("location tasks preparation")?;
14385
14386 let locations = future::join_all(location_tasks)
14387 .await
14388 .into_iter()
14389 .filter_map(|location| location.transpose())
14390 .collect::<Result<_>>()
14391 .context("location tasks")?;
14392
14393 let Some(workspace) = workspace else {
14394 return Ok(Navigated::No);
14395 };
14396 let opened = workspace
14397 .update_in(cx, |workspace, window, cx| {
14398 Self::open_locations_in_multibuffer(
14399 workspace,
14400 locations,
14401 title,
14402 split,
14403 MultibufferSelectionMode::First,
14404 window,
14405 cx,
14406 )
14407 })
14408 .ok();
14409
14410 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14411 })
14412 } else {
14413 Task::ready(Ok(Navigated::No))
14414 }
14415 }
14416
14417 fn compute_target_location(
14418 &self,
14419 lsp_location: lsp::Location,
14420 server_id: LanguageServerId,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) -> Task<anyhow::Result<Option<Location>>> {
14424 let Some(project) = self.project.clone() else {
14425 return Task::ready(Ok(None));
14426 };
14427
14428 cx.spawn_in(window, async move |editor, cx| {
14429 let location_task = editor.update(cx, |_, cx| {
14430 project.update(cx, |project, cx| {
14431 let language_server_name = project
14432 .language_server_statuses(cx)
14433 .find(|(id, _)| server_id == *id)
14434 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14435 language_server_name.map(|language_server_name| {
14436 project.open_local_buffer_via_lsp(
14437 lsp_location.uri.clone(),
14438 server_id,
14439 language_server_name,
14440 cx,
14441 )
14442 })
14443 })
14444 })?;
14445 let location = match location_task {
14446 Some(task) => Some({
14447 let target_buffer_handle = task.await.context("open local buffer")?;
14448 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14449 let target_start = target_buffer
14450 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14451 let target_end = target_buffer
14452 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14453 target_buffer.anchor_after(target_start)
14454 ..target_buffer.anchor_before(target_end)
14455 })?;
14456 Location {
14457 buffer: target_buffer_handle,
14458 range,
14459 }
14460 }),
14461 None => None,
14462 };
14463 Ok(location)
14464 })
14465 }
14466
14467 pub fn find_all_references(
14468 &mut self,
14469 _: &FindAllReferences,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) -> Option<Task<Result<Navigated>>> {
14473 let selection = self.selections.newest::<usize>(cx);
14474 let multi_buffer = self.buffer.read(cx);
14475 let head = selection.head();
14476
14477 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14478 let head_anchor = multi_buffer_snapshot.anchor_at(
14479 head,
14480 if head < selection.tail() {
14481 Bias::Right
14482 } else {
14483 Bias::Left
14484 },
14485 );
14486
14487 match self
14488 .find_all_references_task_sources
14489 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14490 {
14491 Ok(_) => {
14492 log::info!(
14493 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14494 );
14495 return None;
14496 }
14497 Err(i) => {
14498 self.find_all_references_task_sources.insert(i, head_anchor);
14499 }
14500 }
14501
14502 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14503 let workspace = self.workspace()?;
14504 let project = workspace.read(cx).project().clone();
14505 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14506 Some(cx.spawn_in(window, async move |editor, cx| {
14507 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14508 if let Ok(i) = editor
14509 .find_all_references_task_sources
14510 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14511 {
14512 editor.find_all_references_task_sources.remove(i);
14513 }
14514 });
14515
14516 let locations = references.await?;
14517 if locations.is_empty() {
14518 return anyhow::Ok(Navigated::No);
14519 }
14520
14521 workspace.update_in(cx, |workspace, window, cx| {
14522 let title = locations
14523 .first()
14524 .as_ref()
14525 .map(|location| {
14526 let buffer = location.buffer.read(cx);
14527 format!(
14528 "References to `{}`",
14529 buffer
14530 .text_for_range(location.range.clone())
14531 .collect::<String>()
14532 )
14533 })
14534 .unwrap();
14535 Self::open_locations_in_multibuffer(
14536 workspace,
14537 locations,
14538 title,
14539 false,
14540 MultibufferSelectionMode::First,
14541 window,
14542 cx,
14543 );
14544 Navigated::Yes
14545 })
14546 }))
14547 }
14548
14549 /// Opens a multibuffer with the given project locations in it
14550 pub fn open_locations_in_multibuffer(
14551 workspace: &mut Workspace,
14552 mut locations: Vec<Location>,
14553 title: String,
14554 split: bool,
14555 multibuffer_selection_mode: MultibufferSelectionMode,
14556 window: &mut Window,
14557 cx: &mut Context<Workspace>,
14558 ) {
14559 // If there are multiple definitions, open them in a multibuffer
14560 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14561 let mut locations = locations.into_iter().peekable();
14562 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14563 let capability = workspace.project().read(cx).capability();
14564
14565 let excerpt_buffer = cx.new(|cx| {
14566 let mut multibuffer = MultiBuffer::new(capability);
14567 while let Some(location) = locations.next() {
14568 let buffer = location.buffer.read(cx);
14569 let mut ranges_for_buffer = Vec::new();
14570 let range = location.range.to_point(buffer);
14571 ranges_for_buffer.push(range.clone());
14572
14573 while let Some(next_location) = locations.peek() {
14574 if next_location.buffer == location.buffer {
14575 ranges_for_buffer.push(next_location.range.to_point(buffer));
14576 locations.next();
14577 } else {
14578 break;
14579 }
14580 }
14581
14582 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14583 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14584 PathKey::for_buffer(&location.buffer, cx),
14585 location.buffer.clone(),
14586 ranges_for_buffer,
14587 DEFAULT_MULTIBUFFER_CONTEXT,
14588 cx,
14589 );
14590 ranges.extend(new_ranges)
14591 }
14592
14593 multibuffer.with_title(title)
14594 });
14595
14596 let editor = cx.new(|cx| {
14597 Editor::for_multibuffer(
14598 excerpt_buffer,
14599 Some(workspace.project().clone()),
14600 window,
14601 cx,
14602 )
14603 });
14604 editor.update(cx, |editor, cx| {
14605 match multibuffer_selection_mode {
14606 MultibufferSelectionMode::First => {
14607 if let Some(first_range) = ranges.first() {
14608 editor.change_selections(None, window, cx, |selections| {
14609 selections.clear_disjoint();
14610 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14611 });
14612 }
14613 editor.highlight_background::<Self>(
14614 &ranges,
14615 |theme| theme.editor_highlighted_line_background,
14616 cx,
14617 );
14618 }
14619 MultibufferSelectionMode::All => {
14620 editor.change_selections(None, window, cx, |selections| {
14621 selections.clear_disjoint();
14622 selections.select_anchor_ranges(ranges);
14623 });
14624 }
14625 }
14626 editor.register_buffers_with_language_servers(cx);
14627 });
14628
14629 let item = Box::new(editor);
14630 let item_id = item.item_id();
14631
14632 if split {
14633 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14634 } else {
14635 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14636 let (preview_item_id, preview_item_idx) =
14637 workspace.active_pane().update(cx, |pane, _| {
14638 (pane.preview_item_id(), pane.preview_item_idx())
14639 });
14640
14641 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14642
14643 if let Some(preview_item_id) = preview_item_id {
14644 workspace.active_pane().update(cx, |pane, cx| {
14645 pane.remove_item(preview_item_id, false, false, window, cx);
14646 });
14647 }
14648 } else {
14649 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14650 }
14651 }
14652 workspace.active_pane().update(cx, |pane, cx| {
14653 pane.set_preview_item_id(Some(item_id), cx);
14654 });
14655 }
14656
14657 pub fn rename(
14658 &mut self,
14659 _: &Rename,
14660 window: &mut Window,
14661 cx: &mut Context<Self>,
14662 ) -> Option<Task<Result<()>>> {
14663 use language::ToOffset as _;
14664
14665 let provider = self.semantics_provider.clone()?;
14666 let selection = self.selections.newest_anchor().clone();
14667 let (cursor_buffer, cursor_buffer_position) = self
14668 .buffer
14669 .read(cx)
14670 .text_anchor_for_position(selection.head(), cx)?;
14671 let (tail_buffer, cursor_buffer_position_end) = self
14672 .buffer
14673 .read(cx)
14674 .text_anchor_for_position(selection.tail(), cx)?;
14675 if tail_buffer != cursor_buffer {
14676 return None;
14677 }
14678
14679 let snapshot = cursor_buffer.read(cx).snapshot();
14680 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14681 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14682 let prepare_rename = provider
14683 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14684 .unwrap_or_else(|| Task::ready(Ok(None)));
14685 drop(snapshot);
14686
14687 Some(cx.spawn_in(window, async move |this, cx| {
14688 let rename_range = if let Some(range) = prepare_rename.await? {
14689 Some(range)
14690 } else {
14691 this.update(cx, |this, cx| {
14692 let buffer = this.buffer.read(cx).snapshot(cx);
14693 let mut buffer_highlights = this
14694 .document_highlights_for_position(selection.head(), &buffer)
14695 .filter(|highlight| {
14696 highlight.start.excerpt_id == selection.head().excerpt_id
14697 && highlight.end.excerpt_id == selection.head().excerpt_id
14698 });
14699 buffer_highlights
14700 .next()
14701 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14702 })?
14703 };
14704 if let Some(rename_range) = rename_range {
14705 this.update_in(cx, |this, window, cx| {
14706 let snapshot = cursor_buffer.read(cx).snapshot();
14707 let rename_buffer_range = rename_range.to_offset(&snapshot);
14708 let cursor_offset_in_rename_range =
14709 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14710 let cursor_offset_in_rename_range_end =
14711 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14712
14713 this.take_rename(false, window, cx);
14714 let buffer = this.buffer.read(cx).read(cx);
14715 let cursor_offset = selection.head().to_offset(&buffer);
14716 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14717 let rename_end = rename_start + rename_buffer_range.len();
14718 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14719 let mut old_highlight_id = None;
14720 let old_name: Arc<str> = buffer
14721 .chunks(rename_start..rename_end, true)
14722 .map(|chunk| {
14723 if old_highlight_id.is_none() {
14724 old_highlight_id = chunk.syntax_highlight_id;
14725 }
14726 chunk.text
14727 })
14728 .collect::<String>()
14729 .into();
14730
14731 drop(buffer);
14732
14733 // Position the selection in the rename editor so that it matches the current selection.
14734 this.show_local_selections = false;
14735 let rename_editor = cx.new(|cx| {
14736 let mut editor = Editor::single_line(window, cx);
14737 editor.buffer.update(cx, |buffer, cx| {
14738 buffer.edit([(0..0, old_name.clone())], None, cx)
14739 });
14740 let rename_selection_range = match cursor_offset_in_rename_range
14741 .cmp(&cursor_offset_in_rename_range_end)
14742 {
14743 Ordering::Equal => {
14744 editor.select_all(&SelectAll, window, cx);
14745 return editor;
14746 }
14747 Ordering::Less => {
14748 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14749 }
14750 Ordering::Greater => {
14751 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14752 }
14753 };
14754 if rename_selection_range.end > old_name.len() {
14755 editor.select_all(&SelectAll, window, cx);
14756 } else {
14757 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14758 s.select_ranges([rename_selection_range]);
14759 });
14760 }
14761 editor
14762 });
14763 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14764 if e == &EditorEvent::Focused {
14765 cx.emit(EditorEvent::FocusedIn)
14766 }
14767 })
14768 .detach();
14769
14770 let write_highlights =
14771 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14772 let read_highlights =
14773 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14774 let ranges = write_highlights
14775 .iter()
14776 .flat_map(|(_, ranges)| ranges.iter())
14777 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14778 .cloned()
14779 .collect();
14780
14781 this.highlight_text::<Rename>(
14782 ranges,
14783 HighlightStyle {
14784 fade_out: Some(0.6),
14785 ..Default::default()
14786 },
14787 cx,
14788 );
14789 let rename_focus_handle = rename_editor.focus_handle(cx);
14790 window.focus(&rename_focus_handle);
14791 let block_id = this.insert_blocks(
14792 [BlockProperties {
14793 style: BlockStyle::Flex,
14794 placement: BlockPlacement::Below(range.start),
14795 height: Some(1),
14796 render: Arc::new({
14797 let rename_editor = rename_editor.clone();
14798 move |cx: &mut BlockContext| {
14799 let mut text_style = cx.editor_style.text.clone();
14800 if let Some(highlight_style) = old_highlight_id
14801 .and_then(|h| h.style(&cx.editor_style.syntax))
14802 {
14803 text_style = text_style.highlight(highlight_style);
14804 }
14805 div()
14806 .block_mouse_down()
14807 .pl(cx.anchor_x)
14808 .child(EditorElement::new(
14809 &rename_editor,
14810 EditorStyle {
14811 background: cx.theme().system().transparent,
14812 local_player: cx.editor_style.local_player,
14813 text: text_style,
14814 scrollbar_width: cx.editor_style.scrollbar_width,
14815 syntax: cx.editor_style.syntax.clone(),
14816 status: cx.editor_style.status.clone(),
14817 inlay_hints_style: HighlightStyle {
14818 font_weight: Some(FontWeight::BOLD),
14819 ..make_inlay_hints_style(cx.app)
14820 },
14821 inline_completion_styles: make_suggestion_styles(
14822 cx.app,
14823 ),
14824 ..EditorStyle::default()
14825 },
14826 ))
14827 .into_any_element()
14828 }
14829 }),
14830 priority: 0,
14831 render_in_minimap: true,
14832 }],
14833 Some(Autoscroll::fit()),
14834 cx,
14835 )[0];
14836 this.pending_rename = Some(RenameState {
14837 range,
14838 old_name,
14839 editor: rename_editor,
14840 block_id,
14841 });
14842 })?;
14843 }
14844
14845 Ok(())
14846 }))
14847 }
14848
14849 pub fn confirm_rename(
14850 &mut self,
14851 _: &ConfirmRename,
14852 window: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) -> Option<Task<Result<()>>> {
14855 let rename = self.take_rename(false, window, cx)?;
14856 let workspace = self.workspace()?.downgrade();
14857 let (buffer, start) = self
14858 .buffer
14859 .read(cx)
14860 .text_anchor_for_position(rename.range.start, cx)?;
14861 let (end_buffer, _) = self
14862 .buffer
14863 .read(cx)
14864 .text_anchor_for_position(rename.range.end, cx)?;
14865 if buffer != end_buffer {
14866 return None;
14867 }
14868
14869 let old_name = rename.old_name;
14870 let new_name = rename.editor.read(cx).text(cx);
14871
14872 let rename = self.semantics_provider.as_ref()?.perform_rename(
14873 &buffer,
14874 start,
14875 new_name.clone(),
14876 cx,
14877 )?;
14878
14879 Some(cx.spawn_in(window, async move |editor, cx| {
14880 let project_transaction = rename.await?;
14881 Self::open_project_transaction(
14882 &editor,
14883 workspace,
14884 project_transaction,
14885 format!("Rename: {} → {}", old_name, new_name),
14886 cx,
14887 )
14888 .await?;
14889
14890 editor.update(cx, |editor, cx| {
14891 editor.refresh_document_highlights(cx);
14892 })?;
14893 Ok(())
14894 }))
14895 }
14896
14897 fn take_rename(
14898 &mut self,
14899 moving_cursor: bool,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) -> Option<RenameState> {
14903 let rename = self.pending_rename.take()?;
14904 if rename.editor.focus_handle(cx).is_focused(window) {
14905 window.focus(&self.focus_handle);
14906 }
14907
14908 self.remove_blocks(
14909 [rename.block_id].into_iter().collect(),
14910 Some(Autoscroll::fit()),
14911 cx,
14912 );
14913 self.clear_highlights::<Rename>(cx);
14914 self.show_local_selections = true;
14915
14916 if moving_cursor {
14917 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14918 editor.selections.newest::<usize>(cx).head()
14919 });
14920
14921 // Update the selection to match the position of the selection inside
14922 // the rename editor.
14923 let snapshot = self.buffer.read(cx).read(cx);
14924 let rename_range = rename.range.to_offset(&snapshot);
14925 let cursor_in_editor = snapshot
14926 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14927 .min(rename_range.end);
14928 drop(snapshot);
14929
14930 self.change_selections(None, window, cx, |s| {
14931 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14932 });
14933 } else {
14934 self.refresh_document_highlights(cx);
14935 }
14936
14937 Some(rename)
14938 }
14939
14940 pub fn pending_rename(&self) -> Option<&RenameState> {
14941 self.pending_rename.as_ref()
14942 }
14943
14944 fn format(
14945 &mut self,
14946 _: &Format,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) -> Option<Task<Result<()>>> {
14950 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14951
14952 let project = match &self.project {
14953 Some(project) => project.clone(),
14954 None => return None,
14955 };
14956
14957 Some(self.perform_format(
14958 project,
14959 FormatTrigger::Manual,
14960 FormatTarget::Buffers,
14961 window,
14962 cx,
14963 ))
14964 }
14965
14966 fn format_selections(
14967 &mut self,
14968 _: &FormatSelections,
14969 window: &mut Window,
14970 cx: &mut Context<Self>,
14971 ) -> Option<Task<Result<()>>> {
14972 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14973
14974 let project = match &self.project {
14975 Some(project) => project.clone(),
14976 None => return None,
14977 };
14978
14979 let ranges = self
14980 .selections
14981 .all_adjusted(cx)
14982 .into_iter()
14983 .map(|selection| selection.range())
14984 .collect_vec();
14985
14986 Some(self.perform_format(
14987 project,
14988 FormatTrigger::Manual,
14989 FormatTarget::Ranges(ranges),
14990 window,
14991 cx,
14992 ))
14993 }
14994
14995 fn perform_format(
14996 &mut self,
14997 project: Entity<Project>,
14998 trigger: FormatTrigger,
14999 target: FormatTarget,
15000 window: &mut Window,
15001 cx: &mut Context<Self>,
15002 ) -> Task<Result<()>> {
15003 let buffer = self.buffer.clone();
15004 let (buffers, target) = match target {
15005 FormatTarget::Buffers => {
15006 let mut buffers = buffer.read(cx).all_buffers();
15007 if trigger == FormatTrigger::Save {
15008 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15009 }
15010 (buffers, LspFormatTarget::Buffers)
15011 }
15012 FormatTarget::Ranges(selection_ranges) => {
15013 let multi_buffer = buffer.read(cx);
15014 let snapshot = multi_buffer.read(cx);
15015 let mut buffers = HashSet::default();
15016 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15017 BTreeMap::new();
15018 for selection_range in selection_ranges {
15019 for (buffer, buffer_range, _) in
15020 snapshot.range_to_buffer_ranges(selection_range)
15021 {
15022 let buffer_id = buffer.remote_id();
15023 let start = buffer.anchor_before(buffer_range.start);
15024 let end = buffer.anchor_after(buffer_range.end);
15025 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15026 buffer_id_to_ranges
15027 .entry(buffer_id)
15028 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15029 .or_insert_with(|| vec![start..end]);
15030 }
15031 }
15032 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15033 }
15034 };
15035
15036 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15037 let selections_prev = transaction_id_prev
15038 .and_then(|transaction_id_prev| {
15039 // default to selections as they were after the last edit, if we have them,
15040 // instead of how they are now.
15041 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15042 // will take you back to where you made the last edit, instead of staying where you scrolled
15043 self.selection_history
15044 .transaction(transaction_id_prev)
15045 .map(|t| t.0.clone())
15046 })
15047 .unwrap_or_else(|| {
15048 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15049 self.selections.disjoint_anchors()
15050 });
15051
15052 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15053 let format = project.update(cx, |project, cx| {
15054 project.format(buffers, target, true, trigger, cx)
15055 });
15056
15057 cx.spawn_in(window, async move |editor, cx| {
15058 let transaction = futures::select_biased! {
15059 transaction = format.log_err().fuse() => transaction,
15060 () = timeout => {
15061 log::warn!("timed out waiting for formatting");
15062 None
15063 }
15064 };
15065
15066 buffer
15067 .update(cx, |buffer, cx| {
15068 if let Some(transaction) = transaction {
15069 if !buffer.is_singleton() {
15070 buffer.push_transaction(&transaction.0, cx);
15071 }
15072 }
15073 cx.notify();
15074 })
15075 .ok();
15076
15077 if let Some(transaction_id_now) =
15078 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15079 {
15080 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15081 if has_new_transaction {
15082 _ = editor.update(cx, |editor, _| {
15083 editor
15084 .selection_history
15085 .insert_transaction(transaction_id_now, selections_prev);
15086 });
15087 }
15088 }
15089
15090 Ok(())
15091 })
15092 }
15093
15094 fn organize_imports(
15095 &mut self,
15096 _: &OrganizeImports,
15097 window: &mut Window,
15098 cx: &mut Context<Self>,
15099 ) -> Option<Task<Result<()>>> {
15100 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15101 let project = match &self.project {
15102 Some(project) => project.clone(),
15103 None => return None,
15104 };
15105 Some(self.perform_code_action_kind(
15106 project,
15107 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15108 window,
15109 cx,
15110 ))
15111 }
15112
15113 fn perform_code_action_kind(
15114 &mut self,
15115 project: Entity<Project>,
15116 kind: CodeActionKind,
15117 window: &mut Window,
15118 cx: &mut Context<Self>,
15119 ) -> Task<Result<()>> {
15120 let buffer = self.buffer.clone();
15121 let buffers = buffer.read(cx).all_buffers();
15122 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15123 let apply_action = project.update(cx, |project, cx| {
15124 project.apply_code_action_kind(buffers, kind, true, cx)
15125 });
15126 cx.spawn_in(window, async move |_, cx| {
15127 let transaction = futures::select_biased! {
15128 () = timeout => {
15129 log::warn!("timed out waiting for executing code action");
15130 None
15131 }
15132 transaction = apply_action.log_err().fuse() => transaction,
15133 };
15134 buffer
15135 .update(cx, |buffer, cx| {
15136 // check if we need this
15137 if let Some(transaction) = transaction {
15138 if !buffer.is_singleton() {
15139 buffer.push_transaction(&transaction.0, cx);
15140 }
15141 }
15142 cx.notify();
15143 })
15144 .ok();
15145 Ok(())
15146 })
15147 }
15148
15149 fn restart_language_server(
15150 &mut self,
15151 _: &RestartLanguageServer,
15152 _: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) {
15155 if let Some(project) = self.project.clone() {
15156 self.buffer.update(cx, |multi_buffer, cx| {
15157 project.update(cx, |project, cx| {
15158 project.restart_language_servers_for_buffers(
15159 multi_buffer.all_buffers().into_iter().collect(),
15160 cx,
15161 );
15162 });
15163 })
15164 }
15165 }
15166
15167 fn stop_language_server(
15168 &mut self,
15169 _: &StopLanguageServer,
15170 _: &mut Window,
15171 cx: &mut Context<Self>,
15172 ) {
15173 if let Some(project) = self.project.clone() {
15174 self.buffer.update(cx, |multi_buffer, cx| {
15175 project.update(cx, |project, cx| {
15176 project.stop_language_servers_for_buffers(
15177 multi_buffer.all_buffers().into_iter().collect(),
15178 cx,
15179 );
15180 cx.emit(project::Event::RefreshInlayHints);
15181 });
15182 });
15183 }
15184 }
15185
15186 fn cancel_language_server_work(
15187 workspace: &mut Workspace,
15188 _: &actions::CancelLanguageServerWork,
15189 _: &mut Window,
15190 cx: &mut Context<Workspace>,
15191 ) {
15192 let project = workspace.project();
15193 let buffers = workspace
15194 .active_item(cx)
15195 .and_then(|item| item.act_as::<Editor>(cx))
15196 .map_or(HashSet::default(), |editor| {
15197 editor.read(cx).buffer.read(cx).all_buffers()
15198 });
15199 project.update(cx, |project, cx| {
15200 project.cancel_language_server_work_for_buffers(buffers, cx);
15201 });
15202 }
15203
15204 fn show_character_palette(
15205 &mut self,
15206 _: &ShowCharacterPalette,
15207 window: &mut Window,
15208 _: &mut Context<Self>,
15209 ) {
15210 window.show_character_palette();
15211 }
15212
15213 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15214 if self.mode.is_minimap() {
15215 return;
15216 }
15217
15218 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15219 let buffer = self.buffer.read(cx).snapshot(cx);
15220 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15221 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15222 let is_valid = buffer
15223 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15224 .any(|entry| {
15225 entry.diagnostic.is_primary
15226 && !entry.range.is_empty()
15227 && entry.range.start == primary_range_start
15228 && entry.diagnostic.message == active_diagnostics.active_message
15229 });
15230
15231 if !is_valid {
15232 self.dismiss_diagnostics(cx);
15233 }
15234 }
15235 }
15236
15237 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15238 match &self.active_diagnostics {
15239 ActiveDiagnostic::Group(group) => Some(group),
15240 _ => None,
15241 }
15242 }
15243
15244 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15245 self.dismiss_diagnostics(cx);
15246 self.active_diagnostics = ActiveDiagnostic::All;
15247 }
15248
15249 fn activate_diagnostics(
15250 &mut self,
15251 buffer_id: BufferId,
15252 diagnostic: DiagnosticEntry<usize>,
15253 window: &mut Window,
15254 cx: &mut Context<Self>,
15255 ) {
15256 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15257 return;
15258 }
15259 self.dismiss_diagnostics(cx);
15260 let snapshot = self.snapshot(window, cx);
15261 let buffer = self.buffer.read(cx).snapshot(cx);
15262 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15263 return;
15264 };
15265
15266 let diagnostic_group = buffer
15267 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15268 .collect::<Vec<_>>();
15269
15270 let blocks =
15271 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15272
15273 let blocks = self.display_map.update(cx, |display_map, cx| {
15274 display_map.insert_blocks(blocks, cx).into_iter().collect()
15275 });
15276 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15277 active_range: buffer.anchor_before(diagnostic.range.start)
15278 ..buffer.anchor_after(diagnostic.range.end),
15279 active_message: diagnostic.diagnostic.message.clone(),
15280 group_id: diagnostic.diagnostic.group_id,
15281 blocks,
15282 });
15283 cx.notify();
15284 }
15285
15286 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15287 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15288 return;
15289 };
15290
15291 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15292 if let ActiveDiagnostic::Group(group) = prev {
15293 self.display_map.update(cx, |display_map, cx| {
15294 display_map.remove_blocks(group.blocks, cx);
15295 });
15296 cx.notify();
15297 }
15298 }
15299
15300 /// Disable inline diagnostics rendering for this editor.
15301 pub fn disable_inline_diagnostics(&mut self) {
15302 self.inline_diagnostics_enabled = false;
15303 self.inline_diagnostics_update = Task::ready(());
15304 self.inline_diagnostics.clear();
15305 }
15306
15307 pub fn diagnostics_enabled(&self) -> bool {
15308 self.mode.is_full()
15309 }
15310
15311 pub fn inline_diagnostics_enabled(&self) -> bool {
15312 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15313 }
15314
15315 pub fn show_inline_diagnostics(&self) -> bool {
15316 self.show_inline_diagnostics
15317 }
15318
15319 pub fn toggle_inline_diagnostics(
15320 &mut self,
15321 _: &ToggleInlineDiagnostics,
15322 window: &mut Window,
15323 cx: &mut Context<Editor>,
15324 ) {
15325 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15326 self.refresh_inline_diagnostics(false, window, cx);
15327 }
15328
15329 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15330 self.diagnostics_max_severity = severity;
15331 self.display_map.update(cx, |display_map, _| {
15332 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15333 });
15334 }
15335
15336 pub fn toggle_diagnostics(
15337 &mut self,
15338 _: &ToggleDiagnostics,
15339 window: &mut Window,
15340 cx: &mut Context<Editor>,
15341 ) {
15342 if !self.diagnostics_enabled() {
15343 return;
15344 }
15345
15346 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15347 EditorSettings::get_global(cx)
15348 .diagnostics_max_severity
15349 .filter(|severity| severity != &DiagnosticSeverity::Off)
15350 .unwrap_or(DiagnosticSeverity::Hint)
15351 } else {
15352 DiagnosticSeverity::Off
15353 };
15354 self.set_max_diagnostics_severity(new_severity, cx);
15355 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15356 self.active_diagnostics = ActiveDiagnostic::None;
15357 self.inline_diagnostics_update = Task::ready(());
15358 self.inline_diagnostics.clear();
15359 } else {
15360 self.refresh_inline_diagnostics(false, window, cx);
15361 }
15362
15363 cx.notify();
15364 }
15365
15366 pub fn toggle_minimap(
15367 &mut self,
15368 _: &ToggleMinimap,
15369 window: &mut Window,
15370 cx: &mut Context<Editor>,
15371 ) {
15372 if self.supports_minimap(cx) {
15373 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15374 }
15375 }
15376
15377 fn refresh_inline_diagnostics(
15378 &mut self,
15379 debounce: bool,
15380 window: &mut Window,
15381 cx: &mut Context<Self>,
15382 ) {
15383 let max_severity = ProjectSettings::get_global(cx)
15384 .diagnostics
15385 .inline
15386 .max_severity
15387 .unwrap_or(self.diagnostics_max_severity);
15388
15389 if self.mode.is_minimap()
15390 || !self.inline_diagnostics_enabled()
15391 || !self.show_inline_diagnostics
15392 || max_severity == DiagnosticSeverity::Off
15393 {
15394 self.inline_diagnostics_update = Task::ready(());
15395 self.inline_diagnostics.clear();
15396 return;
15397 }
15398
15399 let debounce_ms = ProjectSettings::get_global(cx)
15400 .diagnostics
15401 .inline
15402 .update_debounce_ms;
15403 let debounce = if debounce && debounce_ms > 0 {
15404 Some(Duration::from_millis(debounce_ms))
15405 } else {
15406 None
15407 };
15408 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15409 let editor = editor.upgrade().unwrap();
15410
15411 if let Some(debounce) = debounce {
15412 cx.background_executor().timer(debounce).await;
15413 }
15414 let Some(snapshot) = editor
15415 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15416 .ok()
15417 else {
15418 return;
15419 };
15420
15421 let new_inline_diagnostics = cx
15422 .background_spawn(async move {
15423 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15424 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15425 let message = diagnostic_entry
15426 .diagnostic
15427 .message
15428 .split_once('\n')
15429 .map(|(line, _)| line)
15430 .map(SharedString::new)
15431 .unwrap_or_else(|| {
15432 SharedString::from(diagnostic_entry.diagnostic.message)
15433 });
15434 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15435 let (Ok(i) | Err(i)) = inline_diagnostics
15436 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15437 inline_diagnostics.insert(
15438 i,
15439 (
15440 start_anchor,
15441 InlineDiagnostic {
15442 message,
15443 group_id: diagnostic_entry.diagnostic.group_id,
15444 start: diagnostic_entry.range.start.to_point(&snapshot),
15445 is_primary: diagnostic_entry.diagnostic.is_primary,
15446 severity: diagnostic_entry.diagnostic.severity,
15447 },
15448 ),
15449 );
15450 }
15451 inline_diagnostics
15452 })
15453 .await;
15454
15455 editor
15456 .update(cx, |editor, cx| {
15457 editor.inline_diagnostics = new_inline_diagnostics;
15458 cx.notify();
15459 })
15460 .ok();
15461 });
15462 }
15463
15464 pub fn set_selections_from_remote(
15465 &mut self,
15466 selections: Vec<Selection<Anchor>>,
15467 pending_selection: Option<Selection<Anchor>>,
15468 window: &mut Window,
15469 cx: &mut Context<Self>,
15470 ) {
15471 let old_cursor_position = self.selections.newest_anchor().head();
15472 self.selections.change_with(cx, |s| {
15473 s.select_anchors(selections);
15474 if let Some(pending_selection) = pending_selection {
15475 s.set_pending(pending_selection, SelectMode::Character);
15476 } else {
15477 s.clear_pending();
15478 }
15479 });
15480 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15481 }
15482
15483 fn push_to_selection_history(&mut self) {
15484 self.selection_history.push(SelectionHistoryEntry {
15485 selections: self.selections.disjoint_anchors(),
15486 select_next_state: self.select_next_state.clone(),
15487 select_prev_state: self.select_prev_state.clone(),
15488 add_selections_state: self.add_selections_state.clone(),
15489 });
15490 }
15491
15492 pub fn transact(
15493 &mut self,
15494 window: &mut Window,
15495 cx: &mut Context<Self>,
15496 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15497 ) -> Option<TransactionId> {
15498 self.start_transaction_at(Instant::now(), window, cx);
15499 update(self, window, cx);
15500 self.end_transaction_at(Instant::now(), cx)
15501 }
15502
15503 pub fn start_transaction_at(
15504 &mut self,
15505 now: Instant,
15506 window: &mut Window,
15507 cx: &mut Context<Self>,
15508 ) {
15509 self.end_selection(window, cx);
15510 if let Some(tx_id) = self
15511 .buffer
15512 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15513 {
15514 self.selection_history
15515 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15516 cx.emit(EditorEvent::TransactionBegun {
15517 transaction_id: tx_id,
15518 })
15519 }
15520 }
15521
15522 pub fn end_transaction_at(
15523 &mut self,
15524 now: Instant,
15525 cx: &mut Context<Self>,
15526 ) -> Option<TransactionId> {
15527 if let Some(transaction_id) = self
15528 .buffer
15529 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15530 {
15531 if let Some((_, end_selections)) =
15532 self.selection_history.transaction_mut(transaction_id)
15533 {
15534 *end_selections = Some(self.selections.disjoint_anchors());
15535 } else {
15536 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15537 }
15538
15539 cx.emit(EditorEvent::Edited { transaction_id });
15540 Some(transaction_id)
15541 } else {
15542 None
15543 }
15544 }
15545
15546 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15547 if self.selection_mark_mode {
15548 self.change_selections(None, window, cx, |s| {
15549 s.move_with(|_, sel| {
15550 sel.collapse_to(sel.head(), SelectionGoal::None);
15551 });
15552 })
15553 }
15554 self.selection_mark_mode = true;
15555 cx.notify();
15556 }
15557
15558 pub fn swap_selection_ends(
15559 &mut self,
15560 _: &actions::SwapSelectionEnds,
15561 window: &mut Window,
15562 cx: &mut Context<Self>,
15563 ) {
15564 self.change_selections(None, window, cx, |s| {
15565 s.move_with(|_, sel| {
15566 if sel.start != sel.end {
15567 sel.reversed = !sel.reversed
15568 }
15569 });
15570 });
15571 self.request_autoscroll(Autoscroll::newest(), cx);
15572 cx.notify();
15573 }
15574
15575 pub fn toggle_fold(
15576 &mut self,
15577 _: &actions::ToggleFold,
15578 window: &mut Window,
15579 cx: &mut Context<Self>,
15580 ) {
15581 if self.is_singleton(cx) {
15582 let selection = self.selections.newest::<Point>(cx);
15583
15584 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15585 let range = if selection.is_empty() {
15586 let point = selection.head().to_display_point(&display_map);
15587 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15588 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15589 .to_point(&display_map);
15590 start..end
15591 } else {
15592 selection.range()
15593 };
15594 if display_map.folds_in_range(range).next().is_some() {
15595 self.unfold_lines(&Default::default(), window, cx)
15596 } else {
15597 self.fold(&Default::default(), window, cx)
15598 }
15599 } else {
15600 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15601 let buffer_ids: HashSet<_> = self
15602 .selections
15603 .disjoint_anchor_ranges()
15604 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15605 .collect();
15606
15607 let should_unfold = buffer_ids
15608 .iter()
15609 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15610
15611 for buffer_id in buffer_ids {
15612 if should_unfold {
15613 self.unfold_buffer(buffer_id, cx);
15614 } else {
15615 self.fold_buffer(buffer_id, cx);
15616 }
15617 }
15618 }
15619 }
15620
15621 pub fn toggle_fold_recursive(
15622 &mut self,
15623 _: &actions::ToggleFoldRecursive,
15624 window: &mut Window,
15625 cx: &mut Context<Self>,
15626 ) {
15627 let selection = self.selections.newest::<Point>(cx);
15628
15629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15630 let range = if selection.is_empty() {
15631 let point = selection.head().to_display_point(&display_map);
15632 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15633 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15634 .to_point(&display_map);
15635 start..end
15636 } else {
15637 selection.range()
15638 };
15639 if display_map.folds_in_range(range).next().is_some() {
15640 self.unfold_recursive(&Default::default(), window, cx)
15641 } else {
15642 self.fold_recursive(&Default::default(), window, cx)
15643 }
15644 }
15645
15646 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15647 if self.is_singleton(cx) {
15648 let mut to_fold = Vec::new();
15649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15650 let selections = self.selections.all_adjusted(cx);
15651
15652 for selection in selections {
15653 let range = selection.range().sorted();
15654 let buffer_start_row = range.start.row;
15655
15656 if range.start.row != range.end.row {
15657 let mut found = false;
15658 let mut row = range.start.row;
15659 while row <= range.end.row {
15660 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15661 {
15662 found = true;
15663 row = crease.range().end.row + 1;
15664 to_fold.push(crease);
15665 } else {
15666 row += 1
15667 }
15668 }
15669 if found {
15670 continue;
15671 }
15672 }
15673
15674 for row in (0..=range.start.row).rev() {
15675 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15676 if crease.range().end.row >= buffer_start_row {
15677 to_fold.push(crease);
15678 if row <= range.start.row {
15679 break;
15680 }
15681 }
15682 }
15683 }
15684 }
15685
15686 self.fold_creases(to_fold, true, window, cx);
15687 } else {
15688 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15689 let buffer_ids = self
15690 .selections
15691 .disjoint_anchor_ranges()
15692 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15693 .collect::<HashSet<_>>();
15694 for buffer_id in buffer_ids {
15695 self.fold_buffer(buffer_id, cx);
15696 }
15697 }
15698 }
15699
15700 fn fold_at_level(
15701 &mut self,
15702 fold_at: &FoldAtLevel,
15703 window: &mut Window,
15704 cx: &mut Context<Self>,
15705 ) {
15706 if !self.buffer.read(cx).is_singleton() {
15707 return;
15708 }
15709
15710 let fold_at_level = fold_at.0;
15711 let snapshot = self.buffer.read(cx).snapshot(cx);
15712 let mut to_fold = Vec::new();
15713 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15714
15715 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15716 while start_row < end_row {
15717 match self
15718 .snapshot(window, cx)
15719 .crease_for_buffer_row(MultiBufferRow(start_row))
15720 {
15721 Some(crease) => {
15722 let nested_start_row = crease.range().start.row + 1;
15723 let nested_end_row = crease.range().end.row;
15724
15725 if current_level < fold_at_level {
15726 stack.push((nested_start_row, nested_end_row, current_level + 1));
15727 } else if current_level == fold_at_level {
15728 to_fold.push(crease);
15729 }
15730
15731 start_row = nested_end_row + 1;
15732 }
15733 None => start_row += 1,
15734 }
15735 }
15736 }
15737
15738 self.fold_creases(to_fold, true, window, cx);
15739 }
15740
15741 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15742 if self.buffer.read(cx).is_singleton() {
15743 let mut fold_ranges = Vec::new();
15744 let snapshot = self.buffer.read(cx).snapshot(cx);
15745
15746 for row in 0..snapshot.max_row().0 {
15747 if let Some(foldable_range) = self
15748 .snapshot(window, cx)
15749 .crease_for_buffer_row(MultiBufferRow(row))
15750 {
15751 fold_ranges.push(foldable_range);
15752 }
15753 }
15754
15755 self.fold_creases(fold_ranges, true, window, cx);
15756 } else {
15757 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15758 editor
15759 .update_in(cx, |editor, _, cx| {
15760 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15761 editor.fold_buffer(buffer_id, cx);
15762 }
15763 })
15764 .ok();
15765 });
15766 }
15767 }
15768
15769 pub fn fold_function_bodies(
15770 &mut self,
15771 _: &actions::FoldFunctionBodies,
15772 window: &mut Window,
15773 cx: &mut Context<Self>,
15774 ) {
15775 let snapshot = self.buffer.read(cx).snapshot(cx);
15776
15777 let ranges = snapshot
15778 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15779 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15780 .collect::<Vec<_>>();
15781
15782 let creases = ranges
15783 .into_iter()
15784 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15785 .collect();
15786
15787 self.fold_creases(creases, true, window, cx);
15788 }
15789
15790 pub fn fold_recursive(
15791 &mut self,
15792 _: &actions::FoldRecursive,
15793 window: &mut Window,
15794 cx: &mut Context<Self>,
15795 ) {
15796 let mut to_fold = Vec::new();
15797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15798 let selections = self.selections.all_adjusted(cx);
15799
15800 for selection in selections {
15801 let range = selection.range().sorted();
15802 let buffer_start_row = range.start.row;
15803
15804 if range.start.row != range.end.row {
15805 let mut found = false;
15806 for row in range.start.row..=range.end.row {
15807 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15808 found = true;
15809 to_fold.push(crease);
15810 }
15811 }
15812 if found {
15813 continue;
15814 }
15815 }
15816
15817 for row in (0..=range.start.row).rev() {
15818 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15819 if crease.range().end.row >= buffer_start_row {
15820 to_fold.push(crease);
15821 } else {
15822 break;
15823 }
15824 }
15825 }
15826 }
15827
15828 self.fold_creases(to_fold, true, window, cx);
15829 }
15830
15831 pub fn fold_at(
15832 &mut self,
15833 buffer_row: MultiBufferRow,
15834 window: &mut Window,
15835 cx: &mut Context<Self>,
15836 ) {
15837 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15838
15839 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15840 let autoscroll = self
15841 .selections
15842 .all::<Point>(cx)
15843 .iter()
15844 .any(|selection| crease.range().overlaps(&selection.range()));
15845
15846 self.fold_creases(vec![crease], autoscroll, window, cx);
15847 }
15848 }
15849
15850 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15851 if self.is_singleton(cx) {
15852 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15853 let buffer = &display_map.buffer_snapshot;
15854 let selections = self.selections.all::<Point>(cx);
15855 let ranges = selections
15856 .iter()
15857 .map(|s| {
15858 let range = s.display_range(&display_map).sorted();
15859 let mut start = range.start.to_point(&display_map);
15860 let mut end = range.end.to_point(&display_map);
15861 start.column = 0;
15862 end.column = buffer.line_len(MultiBufferRow(end.row));
15863 start..end
15864 })
15865 .collect::<Vec<_>>();
15866
15867 self.unfold_ranges(&ranges, true, true, cx);
15868 } else {
15869 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15870 let buffer_ids = self
15871 .selections
15872 .disjoint_anchor_ranges()
15873 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15874 .collect::<HashSet<_>>();
15875 for buffer_id in buffer_ids {
15876 self.unfold_buffer(buffer_id, cx);
15877 }
15878 }
15879 }
15880
15881 pub fn unfold_recursive(
15882 &mut self,
15883 _: &UnfoldRecursive,
15884 _window: &mut Window,
15885 cx: &mut Context<Self>,
15886 ) {
15887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15888 let selections = self.selections.all::<Point>(cx);
15889 let ranges = selections
15890 .iter()
15891 .map(|s| {
15892 let mut range = s.display_range(&display_map).sorted();
15893 *range.start.column_mut() = 0;
15894 *range.end.column_mut() = display_map.line_len(range.end.row());
15895 let start = range.start.to_point(&display_map);
15896 let end = range.end.to_point(&display_map);
15897 start..end
15898 })
15899 .collect::<Vec<_>>();
15900
15901 self.unfold_ranges(&ranges, true, true, cx);
15902 }
15903
15904 pub fn unfold_at(
15905 &mut self,
15906 buffer_row: MultiBufferRow,
15907 _window: &mut Window,
15908 cx: &mut Context<Self>,
15909 ) {
15910 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15911
15912 let intersection_range = Point::new(buffer_row.0, 0)
15913 ..Point::new(
15914 buffer_row.0,
15915 display_map.buffer_snapshot.line_len(buffer_row),
15916 );
15917
15918 let autoscroll = self
15919 .selections
15920 .all::<Point>(cx)
15921 .iter()
15922 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15923
15924 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15925 }
15926
15927 pub fn unfold_all(
15928 &mut self,
15929 _: &actions::UnfoldAll,
15930 _window: &mut Window,
15931 cx: &mut Context<Self>,
15932 ) {
15933 if self.buffer.read(cx).is_singleton() {
15934 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15935 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15936 } else {
15937 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15938 editor
15939 .update(cx, |editor, cx| {
15940 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15941 editor.unfold_buffer(buffer_id, cx);
15942 }
15943 })
15944 .ok();
15945 });
15946 }
15947 }
15948
15949 pub fn fold_selected_ranges(
15950 &mut self,
15951 _: &FoldSelectedRanges,
15952 window: &mut Window,
15953 cx: &mut Context<Self>,
15954 ) {
15955 let selections = self.selections.all_adjusted(cx);
15956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15957 let ranges = selections
15958 .into_iter()
15959 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15960 .collect::<Vec<_>>();
15961 self.fold_creases(ranges, true, window, cx);
15962 }
15963
15964 pub fn fold_ranges<T: ToOffset + Clone>(
15965 &mut self,
15966 ranges: Vec<Range<T>>,
15967 auto_scroll: bool,
15968 window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15972 let ranges = ranges
15973 .into_iter()
15974 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15975 .collect::<Vec<_>>();
15976 self.fold_creases(ranges, auto_scroll, window, cx);
15977 }
15978
15979 pub fn fold_creases<T: ToOffset + Clone>(
15980 &mut self,
15981 creases: Vec<Crease<T>>,
15982 auto_scroll: bool,
15983 _window: &mut Window,
15984 cx: &mut Context<Self>,
15985 ) {
15986 if creases.is_empty() {
15987 return;
15988 }
15989
15990 let mut buffers_affected = HashSet::default();
15991 let multi_buffer = self.buffer().read(cx);
15992 for crease in &creases {
15993 if let Some((_, buffer, _)) =
15994 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15995 {
15996 buffers_affected.insert(buffer.read(cx).remote_id());
15997 };
15998 }
15999
16000 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16001
16002 if auto_scroll {
16003 self.request_autoscroll(Autoscroll::fit(), cx);
16004 }
16005
16006 cx.notify();
16007
16008 self.scrollbar_marker_state.dirty = true;
16009 self.folds_did_change(cx);
16010 }
16011
16012 /// Removes any folds whose ranges intersect any of the given ranges.
16013 pub fn unfold_ranges<T: ToOffset + Clone>(
16014 &mut self,
16015 ranges: &[Range<T>],
16016 inclusive: bool,
16017 auto_scroll: bool,
16018 cx: &mut Context<Self>,
16019 ) {
16020 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16021 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16022 });
16023 self.folds_did_change(cx);
16024 }
16025
16026 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16027 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16028 return;
16029 }
16030 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16031 self.display_map.update(cx, |display_map, cx| {
16032 display_map.fold_buffers([buffer_id], cx)
16033 });
16034 cx.emit(EditorEvent::BufferFoldToggled {
16035 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16036 folded: true,
16037 });
16038 cx.notify();
16039 }
16040
16041 pub fn unfold_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 unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16046 self.display_map.update(cx, |display_map, cx| {
16047 display_map.unfold_buffers([buffer_id], cx);
16048 });
16049 cx.emit(EditorEvent::BufferFoldToggled {
16050 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16051 folded: false,
16052 });
16053 cx.notify();
16054 }
16055
16056 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16057 self.display_map.read(cx).is_buffer_folded(buffer)
16058 }
16059
16060 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16061 self.display_map.read(cx).folded_buffers()
16062 }
16063
16064 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16065 self.display_map.update(cx, |display_map, cx| {
16066 display_map.disable_header_for_buffer(buffer_id, cx);
16067 });
16068 cx.notify();
16069 }
16070
16071 /// Removes any folds with the given ranges.
16072 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16073 &mut self,
16074 ranges: &[Range<T>],
16075 type_id: TypeId,
16076 auto_scroll: bool,
16077 cx: &mut Context<Self>,
16078 ) {
16079 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16080 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16081 });
16082 self.folds_did_change(cx);
16083 }
16084
16085 fn remove_folds_with<T: ToOffset + Clone>(
16086 &mut self,
16087 ranges: &[Range<T>],
16088 auto_scroll: bool,
16089 cx: &mut Context<Self>,
16090 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16091 ) {
16092 if ranges.is_empty() {
16093 return;
16094 }
16095
16096 let mut buffers_affected = HashSet::default();
16097 let multi_buffer = self.buffer().read(cx);
16098 for range in ranges {
16099 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16100 buffers_affected.insert(buffer.read(cx).remote_id());
16101 };
16102 }
16103
16104 self.display_map.update(cx, update);
16105
16106 if auto_scroll {
16107 self.request_autoscroll(Autoscroll::fit(), cx);
16108 }
16109
16110 cx.notify();
16111 self.scrollbar_marker_state.dirty = true;
16112 self.active_indent_guides_state.dirty = true;
16113 }
16114
16115 pub fn update_fold_widths(
16116 &mut self,
16117 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16118 cx: &mut Context<Self>,
16119 ) -> bool {
16120 self.display_map
16121 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16122 }
16123
16124 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16125 self.display_map.read(cx).fold_placeholder.clone()
16126 }
16127
16128 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16129 self.buffer.update(cx, |buffer, cx| {
16130 buffer.set_all_diff_hunks_expanded(cx);
16131 });
16132 }
16133
16134 pub fn expand_all_diff_hunks(
16135 &mut self,
16136 _: &ExpandAllDiffHunks,
16137 _window: &mut Window,
16138 cx: &mut Context<Self>,
16139 ) {
16140 self.buffer.update(cx, |buffer, cx| {
16141 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16142 });
16143 }
16144
16145 pub fn toggle_selected_diff_hunks(
16146 &mut self,
16147 _: &ToggleSelectedDiffHunks,
16148 _window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16152 self.toggle_diff_hunks_in_ranges(ranges, cx);
16153 }
16154
16155 pub fn diff_hunks_in_ranges<'a>(
16156 &'a self,
16157 ranges: &'a [Range<Anchor>],
16158 buffer: &'a MultiBufferSnapshot,
16159 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16160 ranges.iter().flat_map(move |range| {
16161 let end_excerpt_id = range.end.excerpt_id;
16162 let range = range.to_point(buffer);
16163 let mut peek_end = range.end;
16164 if range.end.row < buffer.max_row().0 {
16165 peek_end = Point::new(range.end.row + 1, 0);
16166 }
16167 buffer
16168 .diff_hunks_in_range(range.start..peek_end)
16169 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16170 })
16171 }
16172
16173 pub fn has_stageable_diff_hunks_in_ranges(
16174 &self,
16175 ranges: &[Range<Anchor>],
16176 snapshot: &MultiBufferSnapshot,
16177 ) -> bool {
16178 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16179 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16180 }
16181
16182 pub fn toggle_staged_selected_diff_hunks(
16183 &mut self,
16184 _: &::git::ToggleStaged,
16185 _: &mut Window,
16186 cx: &mut Context<Self>,
16187 ) {
16188 let snapshot = self.buffer.read(cx).snapshot(cx);
16189 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16190 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16191 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16192 }
16193
16194 pub fn set_render_diff_hunk_controls(
16195 &mut self,
16196 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16197 cx: &mut Context<Self>,
16198 ) {
16199 self.render_diff_hunk_controls = render_diff_hunk_controls;
16200 cx.notify();
16201 }
16202
16203 pub fn stage_and_next(
16204 &mut self,
16205 _: &::git::StageAndNext,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) {
16209 self.do_stage_or_unstage_and_next(true, window, cx);
16210 }
16211
16212 pub fn unstage_and_next(
16213 &mut self,
16214 _: &::git::UnstageAndNext,
16215 window: &mut Window,
16216 cx: &mut Context<Self>,
16217 ) {
16218 self.do_stage_or_unstage_and_next(false, window, cx);
16219 }
16220
16221 pub fn stage_or_unstage_diff_hunks(
16222 &mut self,
16223 stage: bool,
16224 ranges: Vec<Range<Anchor>>,
16225 cx: &mut Context<Self>,
16226 ) {
16227 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16228 cx.spawn(async move |this, cx| {
16229 task.await?;
16230 this.update(cx, |this, cx| {
16231 let snapshot = this.buffer.read(cx).snapshot(cx);
16232 let chunk_by = this
16233 .diff_hunks_in_ranges(&ranges, &snapshot)
16234 .chunk_by(|hunk| hunk.buffer_id);
16235 for (buffer_id, hunks) in &chunk_by {
16236 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16237 }
16238 })
16239 })
16240 .detach_and_log_err(cx);
16241 }
16242
16243 fn save_buffers_for_ranges_if_needed(
16244 &mut self,
16245 ranges: &[Range<Anchor>],
16246 cx: &mut Context<Editor>,
16247 ) -> Task<Result<()>> {
16248 let multibuffer = self.buffer.read(cx);
16249 let snapshot = multibuffer.read(cx);
16250 let buffer_ids: HashSet<_> = ranges
16251 .iter()
16252 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16253 .collect();
16254 drop(snapshot);
16255
16256 let mut buffers = HashSet::default();
16257 for buffer_id in buffer_ids {
16258 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16259 let buffer = buffer_entity.read(cx);
16260 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16261 {
16262 buffers.insert(buffer_entity);
16263 }
16264 }
16265 }
16266
16267 if let Some(project) = &self.project {
16268 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16269 } else {
16270 Task::ready(Ok(()))
16271 }
16272 }
16273
16274 fn do_stage_or_unstage_and_next(
16275 &mut self,
16276 stage: bool,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) {
16280 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16281
16282 if ranges.iter().any(|range| range.start != range.end) {
16283 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16284 return;
16285 }
16286
16287 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16288 let snapshot = self.snapshot(window, cx);
16289 let position = self.selections.newest::<Point>(cx).head();
16290 let mut row = snapshot
16291 .buffer_snapshot
16292 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16293 .find(|hunk| hunk.row_range.start.0 > position.row)
16294 .map(|hunk| hunk.row_range.start);
16295
16296 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16297 // Outside of the project diff editor, wrap around to the beginning.
16298 if !all_diff_hunks_expanded {
16299 row = row.or_else(|| {
16300 snapshot
16301 .buffer_snapshot
16302 .diff_hunks_in_range(Point::zero()..position)
16303 .find(|hunk| hunk.row_range.end.0 < position.row)
16304 .map(|hunk| hunk.row_range.start)
16305 });
16306 }
16307
16308 if let Some(row) = row {
16309 let destination = Point::new(row.0, 0);
16310 let autoscroll = Autoscroll::center();
16311
16312 self.unfold_ranges(&[destination..destination], false, false, cx);
16313 self.change_selections(Some(autoscroll), window, cx, |s| {
16314 s.select_ranges([destination..destination]);
16315 });
16316 }
16317 }
16318
16319 fn do_stage_or_unstage(
16320 &self,
16321 stage: bool,
16322 buffer_id: BufferId,
16323 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16324 cx: &mut App,
16325 ) -> Option<()> {
16326 let project = self.project.as_ref()?;
16327 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16328 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16329 let buffer_snapshot = buffer.read(cx).snapshot();
16330 let file_exists = buffer_snapshot
16331 .file()
16332 .is_some_and(|file| file.disk_state().exists());
16333 diff.update(cx, |diff, cx| {
16334 diff.stage_or_unstage_hunks(
16335 stage,
16336 &hunks
16337 .map(|hunk| buffer_diff::DiffHunk {
16338 buffer_range: hunk.buffer_range,
16339 diff_base_byte_range: hunk.diff_base_byte_range,
16340 secondary_status: hunk.secondary_status,
16341 range: Point::zero()..Point::zero(), // unused
16342 })
16343 .collect::<Vec<_>>(),
16344 &buffer_snapshot,
16345 file_exists,
16346 cx,
16347 )
16348 });
16349 None
16350 }
16351
16352 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16353 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16354 self.buffer
16355 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16356 }
16357
16358 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16359 self.buffer.update(cx, |buffer, cx| {
16360 let ranges = vec![Anchor::min()..Anchor::max()];
16361 if !buffer.all_diff_hunks_expanded()
16362 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16363 {
16364 buffer.collapse_diff_hunks(ranges, cx);
16365 true
16366 } else {
16367 false
16368 }
16369 })
16370 }
16371
16372 fn toggle_diff_hunks_in_ranges(
16373 &mut self,
16374 ranges: Vec<Range<Anchor>>,
16375 cx: &mut Context<Editor>,
16376 ) {
16377 self.buffer.update(cx, |buffer, cx| {
16378 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16379 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16380 })
16381 }
16382
16383 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16384 self.buffer.update(cx, |buffer, cx| {
16385 let snapshot = buffer.snapshot(cx);
16386 let excerpt_id = range.end.excerpt_id;
16387 let point_range = range.to_point(&snapshot);
16388 let expand = !buffer.single_hunk_is_expanded(range, cx);
16389 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16390 })
16391 }
16392
16393 pub(crate) fn apply_all_diff_hunks(
16394 &mut self,
16395 _: &ApplyAllDiffHunks,
16396 window: &mut Window,
16397 cx: &mut Context<Self>,
16398 ) {
16399 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16400
16401 let buffers = self.buffer.read(cx).all_buffers();
16402 for branch_buffer in buffers {
16403 branch_buffer.update(cx, |branch_buffer, cx| {
16404 branch_buffer.merge_into_base(Vec::new(), cx);
16405 });
16406 }
16407
16408 if let Some(project) = self.project.clone() {
16409 self.save(true, project, window, cx).detach_and_log_err(cx);
16410 }
16411 }
16412
16413 pub(crate) fn apply_selected_diff_hunks(
16414 &mut self,
16415 _: &ApplyDiffHunk,
16416 window: &mut Window,
16417 cx: &mut Context<Self>,
16418 ) {
16419 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16420 let snapshot = self.snapshot(window, cx);
16421 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16422 let mut ranges_by_buffer = HashMap::default();
16423 self.transact(window, cx, |editor, _window, cx| {
16424 for hunk in hunks {
16425 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16426 ranges_by_buffer
16427 .entry(buffer.clone())
16428 .or_insert_with(Vec::new)
16429 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16430 }
16431 }
16432
16433 for (buffer, ranges) in ranges_by_buffer {
16434 buffer.update(cx, |buffer, cx| {
16435 buffer.merge_into_base(ranges, cx);
16436 });
16437 }
16438 });
16439
16440 if let Some(project) = self.project.clone() {
16441 self.save(true, project, window, cx).detach_and_log_err(cx);
16442 }
16443 }
16444
16445 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16446 if hovered != self.gutter_hovered {
16447 self.gutter_hovered = hovered;
16448 cx.notify();
16449 }
16450 }
16451
16452 pub fn insert_blocks(
16453 &mut self,
16454 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16455 autoscroll: Option<Autoscroll>,
16456 cx: &mut Context<Self>,
16457 ) -> Vec<CustomBlockId> {
16458 let blocks = self
16459 .display_map
16460 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16461 if let Some(autoscroll) = autoscroll {
16462 self.request_autoscroll(autoscroll, cx);
16463 }
16464 cx.notify();
16465 blocks
16466 }
16467
16468 pub fn resize_blocks(
16469 &mut self,
16470 heights: HashMap<CustomBlockId, u32>,
16471 autoscroll: Option<Autoscroll>,
16472 cx: &mut Context<Self>,
16473 ) {
16474 self.display_map
16475 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16476 if let Some(autoscroll) = autoscroll {
16477 self.request_autoscroll(autoscroll, cx);
16478 }
16479 cx.notify();
16480 }
16481
16482 pub fn replace_blocks(
16483 &mut self,
16484 renderers: HashMap<CustomBlockId, RenderBlock>,
16485 autoscroll: Option<Autoscroll>,
16486 cx: &mut Context<Self>,
16487 ) {
16488 self.display_map
16489 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16490 if let Some(autoscroll) = autoscroll {
16491 self.request_autoscroll(autoscroll, cx);
16492 }
16493 cx.notify();
16494 }
16495
16496 pub fn remove_blocks(
16497 &mut self,
16498 block_ids: HashSet<CustomBlockId>,
16499 autoscroll: Option<Autoscroll>,
16500 cx: &mut Context<Self>,
16501 ) {
16502 self.display_map.update(cx, |display_map, cx| {
16503 display_map.remove_blocks(block_ids, cx)
16504 });
16505 if let Some(autoscroll) = autoscroll {
16506 self.request_autoscroll(autoscroll, cx);
16507 }
16508 cx.notify();
16509 }
16510
16511 pub fn row_for_block(
16512 &self,
16513 block_id: CustomBlockId,
16514 cx: &mut Context<Self>,
16515 ) -> Option<DisplayRow> {
16516 self.display_map
16517 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16518 }
16519
16520 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16521 self.focused_block = Some(focused_block);
16522 }
16523
16524 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16525 self.focused_block.take()
16526 }
16527
16528 pub fn insert_creases(
16529 &mut self,
16530 creases: impl IntoIterator<Item = Crease<Anchor>>,
16531 cx: &mut Context<Self>,
16532 ) -> Vec<CreaseId> {
16533 self.display_map
16534 .update(cx, |map, cx| map.insert_creases(creases, cx))
16535 }
16536
16537 pub fn remove_creases(
16538 &mut self,
16539 ids: impl IntoIterator<Item = CreaseId>,
16540 cx: &mut Context<Self>,
16541 ) -> Vec<(CreaseId, Range<Anchor>)> {
16542 self.display_map
16543 .update(cx, |map, cx| map.remove_creases(ids, cx))
16544 }
16545
16546 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16547 self.display_map
16548 .update(cx, |map, cx| map.snapshot(cx))
16549 .longest_row()
16550 }
16551
16552 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16553 self.display_map
16554 .update(cx, |map, cx| map.snapshot(cx))
16555 .max_point()
16556 }
16557
16558 pub fn text(&self, cx: &App) -> String {
16559 self.buffer.read(cx).read(cx).text()
16560 }
16561
16562 pub fn is_empty(&self, cx: &App) -> bool {
16563 self.buffer.read(cx).read(cx).is_empty()
16564 }
16565
16566 pub fn text_option(&self, cx: &App) -> Option<String> {
16567 let text = self.text(cx);
16568 let text = text.trim();
16569
16570 if text.is_empty() {
16571 return None;
16572 }
16573
16574 Some(text.to_string())
16575 }
16576
16577 pub fn set_text(
16578 &mut self,
16579 text: impl Into<Arc<str>>,
16580 window: &mut Window,
16581 cx: &mut Context<Self>,
16582 ) {
16583 self.transact(window, cx, |this, _, cx| {
16584 this.buffer
16585 .read(cx)
16586 .as_singleton()
16587 .expect("you can only call set_text on editors for singleton buffers")
16588 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16589 });
16590 }
16591
16592 pub fn display_text(&self, cx: &mut App) -> String {
16593 self.display_map
16594 .update(cx, |map, cx| map.snapshot(cx))
16595 .text()
16596 }
16597
16598 fn create_minimap(
16599 &self,
16600 minimap_settings: MinimapSettings,
16601 window: &mut Window,
16602 cx: &mut Context<Self>,
16603 ) -> Option<Entity<Self>> {
16604 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16605 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16606 }
16607
16608 fn initialize_new_minimap(
16609 &self,
16610 minimap_settings: MinimapSettings,
16611 window: &mut Window,
16612 cx: &mut Context<Self>,
16613 ) -> Entity<Self> {
16614 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16615
16616 let mut minimap = Editor::new_internal(
16617 EditorMode::Minimap {
16618 parent: cx.weak_entity(),
16619 },
16620 self.buffer.clone(),
16621 self.project.clone(),
16622 Some(self.display_map.clone()),
16623 window,
16624 cx,
16625 );
16626 minimap.scroll_manager.clone_state(&self.scroll_manager);
16627 minimap.set_text_style_refinement(TextStyleRefinement {
16628 font_size: Some(MINIMAP_FONT_SIZE),
16629 font_weight: Some(MINIMAP_FONT_WEIGHT),
16630 ..Default::default()
16631 });
16632 minimap.update_minimap_configuration(minimap_settings, cx);
16633 cx.new(|_| minimap)
16634 }
16635
16636 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16637 let current_line_highlight = minimap_settings
16638 .current_line_highlight
16639 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16640 self.set_current_line_highlight(Some(current_line_highlight));
16641 }
16642
16643 pub fn minimap(&self) -> Option<&Entity<Self>> {
16644 self.minimap
16645 .as_ref()
16646 .filter(|_| self.minimap_visibility.visible())
16647 }
16648
16649 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16650 let mut wrap_guides = smallvec::smallvec![];
16651
16652 if self.show_wrap_guides == Some(false) {
16653 return wrap_guides;
16654 }
16655
16656 let settings = self.buffer.read(cx).language_settings(cx);
16657 if settings.show_wrap_guides {
16658 match self.soft_wrap_mode(cx) {
16659 SoftWrap::Column(soft_wrap) => {
16660 wrap_guides.push((soft_wrap as usize, true));
16661 }
16662 SoftWrap::Bounded(soft_wrap) => {
16663 wrap_guides.push((soft_wrap as usize, true));
16664 }
16665 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16666 }
16667 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16668 }
16669
16670 wrap_guides
16671 }
16672
16673 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16674 let settings = self.buffer.read(cx).language_settings(cx);
16675 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16676 match mode {
16677 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16678 SoftWrap::None
16679 }
16680 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16681 language_settings::SoftWrap::PreferredLineLength => {
16682 SoftWrap::Column(settings.preferred_line_length)
16683 }
16684 language_settings::SoftWrap::Bounded => {
16685 SoftWrap::Bounded(settings.preferred_line_length)
16686 }
16687 }
16688 }
16689
16690 pub fn set_soft_wrap_mode(
16691 &mut self,
16692 mode: language_settings::SoftWrap,
16693
16694 cx: &mut Context<Self>,
16695 ) {
16696 self.soft_wrap_mode_override = Some(mode);
16697 cx.notify();
16698 }
16699
16700 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16701 self.hard_wrap = hard_wrap;
16702 cx.notify();
16703 }
16704
16705 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16706 self.text_style_refinement = Some(style);
16707 }
16708
16709 /// called by the Element so we know what style we were most recently rendered with.
16710 pub(crate) fn set_style(
16711 &mut self,
16712 style: EditorStyle,
16713 window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) {
16716 // We intentionally do not inform the display map about the minimap style
16717 // so that wrapping is not recalculated and stays consistent for the editor
16718 // and its linked minimap.
16719 if !self.mode.is_minimap() {
16720 let rem_size = window.rem_size();
16721 self.display_map.update(cx, |map, cx| {
16722 map.set_font(
16723 style.text.font(),
16724 style.text.font_size.to_pixels(rem_size),
16725 cx,
16726 )
16727 });
16728 }
16729 self.style = Some(style);
16730 }
16731
16732 pub fn style(&self) -> Option<&EditorStyle> {
16733 self.style.as_ref()
16734 }
16735
16736 // Called by the element. This method is not designed to be called outside of the editor
16737 // element's layout code because it does not notify when rewrapping is computed synchronously.
16738 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16739 self.display_map
16740 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16741 }
16742
16743 pub fn set_soft_wrap(&mut self) {
16744 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16745 }
16746
16747 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16748 if self.soft_wrap_mode_override.is_some() {
16749 self.soft_wrap_mode_override.take();
16750 } else {
16751 let soft_wrap = match self.soft_wrap_mode(cx) {
16752 SoftWrap::GitDiff => return,
16753 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16754 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16755 language_settings::SoftWrap::None
16756 }
16757 };
16758 self.soft_wrap_mode_override = Some(soft_wrap);
16759 }
16760 cx.notify();
16761 }
16762
16763 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16764 let Some(workspace) = self.workspace() else {
16765 return;
16766 };
16767 let fs = workspace.read(cx).app_state().fs.clone();
16768 let current_show = TabBarSettings::get_global(cx).show;
16769 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16770 setting.show = Some(!current_show);
16771 });
16772 }
16773
16774 pub fn toggle_indent_guides(
16775 &mut self,
16776 _: &ToggleIndentGuides,
16777 _: &mut Window,
16778 cx: &mut Context<Self>,
16779 ) {
16780 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16781 self.buffer
16782 .read(cx)
16783 .language_settings(cx)
16784 .indent_guides
16785 .enabled
16786 });
16787 self.show_indent_guides = Some(!currently_enabled);
16788 cx.notify();
16789 }
16790
16791 fn should_show_indent_guides(&self) -> Option<bool> {
16792 self.show_indent_guides
16793 }
16794
16795 pub fn toggle_line_numbers(
16796 &mut self,
16797 _: &ToggleLineNumbers,
16798 _: &mut Window,
16799 cx: &mut Context<Self>,
16800 ) {
16801 let mut editor_settings = EditorSettings::get_global(cx).clone();
16802 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16803 EditorSettings::override_global(editor_settings, cx);
16804 }
16805
16806 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16807 if let Some(show_line_numbers) = self.show_line_numbers {
16808 return show_line_numbers;
16809 }
16810 EditorSettings::get_global(cx).gutter.line_numbers
16811 }
16812
16813 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16814 self.use_relative_line_numbers
16815 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16816 }
16817
16818 pub fn toggle_relative_line_numbers(
16819 &mut self,
16820 _: &ToggleRelativeLineNumbers,
16821 _: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) {
16824 let is_relative = self.should_use_relative_line_numbers(cx);
16825 self.set_relative_line_number(Some(!is_relative), cx)
16826 }
16827
16828 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16829 self.use_relative_line_numbers = is_relative;
16830 cx.notify();
16831 }
16832
16833 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16834 self.show_gutter = show_gutter;
16835 cx.notify();
16836 }
16837
16838 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16839 self.show_scrollbars = show_scrollbars;
16840 cx.notify();
16841 }
16842
16843 pub fn set_minimap_visibility(
16844 &mut self,
16845 minimap_visibility: MinimapVisibility,
16846 window: &mut Window,
16847 cx: &mut Context<Self>,
16848 ) {
16849 if self.minimap_visibility != minimap_visibility {
16850 if minimap_visibility.visible() && self.minimap.is_none() {
16851 let minimap_settings = EditorSettings::get_global(cx).minimap;
16852 self.minimap =
16853 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16854 }
16855 self.minimap_visibility = minimap_visibility;
16856 cx.notify();
16857 }
16858 }
16859
16860 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16861 self.set_show_scrollbars(false, cx);
16862 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16863 }
16864
16865 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16866 self.show_line_numbers = Some(show_line_numbers);
16867 cx.notify();
16868 }
16869
16870 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16871 self.disable_expand_excerpt_buttons = true;
16872 cx.notify();
16873 }
16874
16875 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16876 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16877 cx.notify();
16878 }
16879
16880 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16881 self.show_code_actions = Some(show_code_actions);
16882 cx.notify();
16883 }
16884
16885 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16886 self.show_runnables = Some(show_runnables);
16887 cx.notify();
16888 }
16889
16890 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16891 self.show_breakpoints = Some(show_breakpoints);
16892 cx.notify();
16893 }
16894
16895 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16896 if self.display_map.read(cx).masked != masked {
16897 self.display_map.update(cx, |map, _| map.masked = masked);
16898 }
16899 cx.notify()
16900 }
16901
16902 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16903 self.show_wrap_guides = Some(show_wrap_guides);
16904 cx.notify();
16905 }
16906
16907 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16908 self.show_indent_guides = Some(show_indent_guides);
16909 cx.notify();
16910 }
16911
16912 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16913 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16914 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16915 if let Some(dir) = file.abs_path(cx).parent() {
16916 return Some(dir.to_owned());
16917 }
16918 }
16919
16920 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16921 return Some(project_path.path.to_path_buf());
16922 }
16923 }
16924
16925 None
16926 }
16927
16928 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16929 self.active_excerpt(cx)?
16930 .1
16931 .read(cx)
16932 .file()
16933 .and_then(|f| f.as_local())
16934 }
16935
16936 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16937 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16938 let buffer = buffer.read(cx);
16939 if let Some(project_path) = buffer.project_path(cx) {
16940 let project = self.project.as_ref()?.read(cx);
16941 project.absolute_path(&project_path, cx)
16942 } else {
16943 buffer
16944 .file()
16945 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16946 }
16947 })
16948 }
16949
16950 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16951 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16952 let project_path = buffer.read(cx).project_path(cx)?;
16953 let project = self.project.as_ref()?.read(cx);
16954 let entry = project.entry_for_path(&project_path, cx)?;
16955 let path = entry.path.to_path_buf();
16956 Some(path)
16957 })
16958 }
16959
16960 pub fn reveal_in_finder(
16961 &mut self,
16962 _: &RevealInFileManager,
16963 _window: &mut Window,
16964 cx: &mut Context<Self>,
16965 ) {
16966 if let Some(target) = self.target_file(cx) {
16967 cx.reveal_path(&target.abs_path(cx));
16968 }
16969 }
16970
16971 pub fn copy_path(
16972 &mut self,
16973 _: &zed_actions::workspace::CopyPath,
16974 _window: &mut Window,
16975 cx: &mut Context<Self>,
16976 ) {
16977 if let Some(path) = self.target_file_abs_path(cx) {
16978 if let Some(path) = path.to_str() {
16979 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16980 }
16981 }
16982 }
16983
16984 pub fn copy_relative_path(
16985 &mut self,
16986 _: &zed_actions::workspace::CopyRelativePath,
16987 _window: &mut Window,
16988 cx: &mut Context<Self>,
16989 ) {
16990 if let Some(path) = self.target_file_path(cx) {
16991 if let Some(path) = path.to_str() {
16992 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16993 }
16994 }
16995 }
16996
16997 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16998 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16999 buffer.read(cx).project_path(cx)
17000 } else {
17001 None
17002 }
17003 }
17004
17005 // Returns true if the editor handled a go-to-line request
17006 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17007 maybe!({
17008 let breakpoint_store = self.breakpoint_store.as_ref()?;
17009
17010 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17011 else {
17012 self.clear_row_highlights::<ActiveDebugLine>();
17013 return None;
17014 };
17015
17016 let position = active_stack_frame.position;
17017 let buffer_id = position.buffer_id?;
17018 let snapshot = self
17019 .project
17020 .as_ref()?
17021 .read(cx)
17022 .buffer_for_id(buffer_id, cx)?
17023 .read(cx)
17024 .snapshot();
17025
17026 let mut handled = false;
17027 for (id, ExcerptRange { context, .. }) in
17028 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17029 {
17030 if context.start.cmp(&position, &snapshot).is_ge()
17031 || context.end.cmp(&position, &snapshot).is_lt()
17032 {
17033 continue;
17034 }
17035 let snapshot = self.buffer.read(cx).snapshot(cx);
17036 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17037
17038 handled = true;
17039 self.clear_row_highlights::<ActiveDebugLine>();
17040
17041 self.go_to_line::<ActiveDebugLine>(
17042 multibuffer_anchor,
17043 Some(cx.theme().colors().editor_debugger_active_line_background),
17044 window,
17045 cx,
17046 );
17047
17048 cx.notify();
17049 }
17050
17051 handled.then_some(())
17052 })
17053 .is_some()
17054 }
17055
17056 pub fn copy_file_name_without_extension(
17057 &mut self,
17058 _: &CopyFileNameWithoutExtension,
17059 _: &mut Window,
17060 cx: &mut Context<Self>,
17061 ) {
17062 if let Some(file) = self.target_file(cx) {
17063 if let Some(file_stem) = file.path().file_stem() {
17064 if let Some(name) = file_stem.to_str() {
17065 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17066 }
17067 }
17068 }
17069 }
17070
17071 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17072 if let Some(file) = self.target_file(cx) {
17073 if let Some(file_name) = file.path().file_name() {
17074 if let Some(name) = file_name.to_str() {
17075 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17076 }
17077 }
17078 }
17079 }
17080
17081 pub fn toggle_git_blame(
17082 &mut self,
17083 _: &::git::Blame,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17088
17089 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17090 self.start_git_blame(true, window, cx);
17091 }
17092
17093 cx.notify();
17094 }
17095
17096 pub fn toggle_git_blame_inline(
17097 &mut self,
17098 _: &ToggleGitBlameInline,
17099 window: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) {
17102 self.toggle_git_blame_inline_internal(true, window, cx);
17103 cx.notify();
17104 }
17105
17106 pub fn open_git_blame_commit(
17107 &mut self,
17108 _: &OpenGitBlameCommit,
17109 window: &mut Window,
17110 cx: &mut Context<Self>,
17111 ) {
17112 self.open_git_blame_commit_internal(window, cx);
17113 }
17114
17115 fn open_git_blame_commit_internal(
17116 &mut self,
17117 window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) -> Option<()> {
17120 let blame = self.blame.as_ref()?;
17121 let snapshot = self.snapshot(window, cx);
17122 let cursor = self.selections.newest::<Point>(cx).head();
17123 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17124 let blame_entry = blame
17125 .update(cx, |blame, cx| {
17126 blame
17127 .blame_for_rows(
17128 &[RowInfo {
17129 buffer_id: Some(buffer.remote_id()),
17130 buffer_row: Some(point.row),
17131 ..Default::default()
17132 }],
17133 cx,
17134 )
17135 .next()
17136 })
17137 .flatten()?;
17138 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17139 let repo = blame.read(cx).repository(cx)?;
17140 let workspace = self.workspace()?.downgrade();
17141 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17142 None
17143 }
17144
17145 pub fn git_blame_inline_enabled(&self) -> bool {
17146 self.git_blame_inline_enabled
17147 }
17148
17149 pub fn toggle_selection_menu(
17150 &mut self,
17151 _: &ToggleSelectionMenu,
17152 _: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) {
17155 self.show_selection_menu = self
17156 .show_selection_menu
17157 .map(|show_selections_menu| !show_selections_menu)
17158 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17159
17160 cx.notify();
17161 }
17162
17163 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17164 self.show_selection_menu
17165 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17166 }
17167
17168 fn start_git_blame(
17169 &mut self,
17170 user_triggered: bool,
17171 window: &mut Window,
17172 cx: &mut Context<Self>,
17173 ) {
17174 if let Some(project) = self.project.as_ref() {
17175 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17176 return;
17177 };
17178
17179 if buffer.read(cx).file().is_none() {
17180 return;
17181 }
17182
17183 let focused = self.focus_handle(cx).contains_focused(window, cx);
17184
17185 let project = project.clone();
17186 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17187 self.blame_subscription =
17188 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17189 self.blame = Some(blame);
17190 }
17191 }
17192
17193 fn toggle_git_blame_inline_internal(
17194 &mut self,
17195 user_triggered: bool,
17196 window: &mut Window,
17197 cx: &mut Context<Self>,
17198 ) {
17199 if self.git_blame_inline_enabled {
17200 self.git_blame_inline_enabled = false;
17201 self.show_git_blame_inline = false;
17202 self.show_git_blame_inline_delay_task.take();
17203 } else {
17204 self.git_blame_inline_enabled = true;
17205 self.start_git_blame_inline(user_triggered, window, cx);
17206 }
17207
17208 cx.notify();
17209 }
17210
17211 fn start_git_blame_inline(
17212 &mut self,
17213 user_triggered: bool,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) {
17217 self.start_git_blame(user_triggered, window, cx);
17218
17219 if ProjectSettings::get_global(cx)
17220 .git
17221 .inline_blame_delay()
17222 .is_some()
17223 {
17224 self.start_inline_blame_timer(window, cx);
17225 } else {
17226 self.show_git_blame_inline = true
17227 }
17228 }
17229
17230 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17231 self.blame.as_ref()
17232 }
17233
17234 pub fn show_git_blame_gutter(&self) -> bool {
17235 self.show_git_blame_gutter
17236 }
17237
17238 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17239 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17240 }
17241
17242 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17243 self.show_git_blame_inline
17244 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17245 && !self.newest_selection_head_on_empty_line(cx)
17246 && self.has_blame_entries(cx)
17247 }
17248
17249 fn has_blame_entries(&self, cx: &App) -> bool {
17250 self.blame()
17251 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17252 }
17253
17254 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17255 let cursor_anchor = self.selections.newest_anchor().head();
17256
17257 let snapshot = self.buffer.read(cx).snapshot(cx);
17258 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17259
17260 snapshot.line_len(buffer_row) == 0
17261 }
17262
17263 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17264 let buffer_and_selection = maybe!({
17265 let selection = self.selections.newest::<Point>(cx);
17266 let selection_range = selection.range();
17267
17268 let multi_buffer = self.buffer().read(cx);
17269 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17270 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17271
17272 let (buffer, range, _) = if selection.reversed {
17273 buffer_ranges.first()
17274 } else {
17275 buffer_ranges.last()
17276 }?;
17277
17278 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17279 ..text::ToPoint::to_point(&range.end, &buffer).row;
17280 Some((
17281 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17282 selection,
17283 ))
17284 });
17285
17286 let Some((buffer, selection)) = buffer_and_selection else {
17287 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17288 };
17289
17290 let Some(project) = self.project.as_ref() else {
17291 return Task::ready(Err(anyhow!("editor does not have project")));
17292 };
17293
17294 project.update(cx, |project, cx| {
17295 project.get_permalink_to_line(&buffer, selection, cx)
17296 })
17297 }
17298
17299 pub fn copy_permalink_to_line(
17300 &mut self,
17301 _: &CopyPermalinkToLine,
17302 window: &mut Window,
17303 cx: &mut Context<Self>,
17304 ) {
17305 let permalink_task = self.get_permalink_to_line(cx);
17306 let workspace = self.workspace();
17307
17308 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17309 Ok(permalink) => {
17310 cx.update(|_, cx| {
17311 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17312 })
17313 .ok();
17314 }
17315 Err(err) => {
17316 let message = format!("Failed to copy permalink: {err}");
17317
17318 Err::<(), anyhow::Error>(err).log_err();
17319
17320 if let Some(workspace) = workspace {
17321 workspace
17322 .update_in(cx, |workspace, _, cx| {
17323 struct CopyPermalinkToLine;
17324
17325 workspace.show_toast(
17326 Toast::new(
17327 NotificationId::unique::<CopyPermalinkToLine>(),
17328 message,
17329 ),
17330 cx,
17331 )
17332 })
17333 .ok();
17334 }
17335 }
17336 })
17337 .detach();
17338 }
17339
17340 pub fn copy_file_location(
17341 &mut self,
17342 _: &CopyFileLocation,
17343 _: &mut Window,
17344 cx: &mut Context<Self>,
17345 ) {
17346 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17347 if let Some(file) = self.target_file(cx) {
17348 if let Some(path) = file.path().to_str() {
17349 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17350 }
17351 }
17352 }
17353
17354 pub fn open_permalink_to_line(
17355 &mut self,
17356 _: &OpenPermalinkToLine,
17357 window: &mut Window,
17358 cx: &mut Context<Self>,
17359 ) {
17360 let permalink_task = self.get_permalink_to_line(cx);
17361 let workspace = self.workspace();
17362
17363 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17364 Ok(permalink) => {
17365 cx.update(|_, cx| {
17366 cx.open_url(permalink.as_ref());
17367 })
17368 .ok();
17369 }
17370 Err(err) => {
17371 let message = format!("Failed to open permalink: {err}");
17372
17373 Err::<(), anyhow::Error>(err).log_err();
17374
17375 if let Some(workspace) = workspace {
17376 workspace
17377 .update(cx, |workspace, cx| {
17378 struct OpenPermalinkToLine;
17379
17380 workspace.show_toast(
17381 Toast::new(
17382 NotificationId::unique::<OpenPermalinkToLine>(),
17383 message,
17384 ),
17385 cx,
17386 )
17387 })
17388 .ok();
17389 }
17390 }
17391 })
17392 .detach();
17393 }
17394
17395 pub fn insert_uuid_v4(
17396 &mut self,
17397 _: &InsertUuidV4,
17398 window: &mut Window,
17399 cx: &mut Context<Self>,
17400 ) {
17401 self.insert_uuid(UuidVersion::V4, window, cx);
17402 }
17403
17404 pub fn insert_uuid_v7(
17405 &mut self,
17406 _: &InsertUuidV7,
17407 window: &mut Window,
17408 cx: &mut Context<Self>,
17409 ) {
17410 self.insert_uuid(UuidVersion::V7, window, cx);
17411 }
17412
17413 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17414 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17415 self.transact(window, cx, |this, window, cx| {
17416 let edits = this
17417 .selections
17418 .all::<Point>(cx)
17419 .into_iter()
17420 .map(|selection| {
17421 let uuid = match version {
17422 UuidVersion::V4 => uuid::Uuid::new_v4(),
17423 UuidVersion::V7 => uuid::Uuid::now_v7(),
17424 };
17425
17426 (selection.range(), uuid.to_string())
17427 });
17428 this.edit(edits, cx);
17429 this.refresh_inline_completion(true, false, window, cx);
17430 });
17431 }
17432
17433 pub fn open_selections_in_multibuffer(
17434 &mut self,
17435 _: &OpenSelectionsInMultibuffer,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) {
17439 let multibuffer = self.buffer.read(cx);
17440
17441 let Some(buffer) = multibuffer.as_singleton() else {
17442 return;
17443 };
17444
17445 let Some(workspace) = self.workspace() else {
17446 return;
17447 };
17448
17449 let locations = self
17450 .selections
17451 .disjoint_anchors()
17452 .iter()
17453 .map(|range| Location {
17454 buffer: buffer.clone(),
17455 range: range.start.text_anchor..range.end.text_anchor,
17456 })
17457 .collect::<Vec<_>>();
17458
17459 let title = multibuffer.title(cx).to_string();
17460
17461 cx.spawn_in(window, async move |_, cx| {
17462 workspace.update_in(cx, |workspace, window, cx| {
17463 Self::open_locations_in_multibuffer(
17464 workspace,
17465 locations,
17466 format!("Selections for '{title}'"),
17467 false,
17468 MultibufferSelectionMode::All,
17469 window,
17470 cx,
17471 );
17472 })
17473 })
17474 .detach();
17475 }
17476
17477 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17478 /// last highlight added will be used.
17479 ///
17480 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17481 pub fn highlight_rows<T: 'static>(
17482 &mut self,
17483 range: Range<Anchor>,
17484 color: Hsla,
17485 options: RowHighlightOptions,
17486 cx: &mut Context<Self>,
17487 ) {
17488 let snapshot = self.buffer().read(cx).snapshot(cx);
17489 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17490 let ix = row_highlights.binary_search_by(|highlight| {
17491 Ordering::Equal
17492 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17493 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17494 });
17495
17496 if let Err(mut ix) = ix {
17497 let index = post_inc(&mut self.highlight_order);
17498
17499 // If this range intersects with the preceding highlight, then merge it with
17500 // the preceding highlight. Otherwise insert a new highlight.
17501 let mut merged = false;
17502 if ix > 0 {
17503 let prev_highlight = &mut row_highlights[ix - 1];
17504 if prev_highlight
17505 .range
17506 .end
17507 .cmp(&range.start, &snapshot)
17508 .is_ge()
17509 {
17510 ix -= 1;
17511 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17512 prev_highlight.range.end = range.end;
17513 }
17514 merged = true;
17515 prev_highlight.index = index;
17516 prev_highlight.color = color;
17517 prev_highlight.options = options;
17518 }
17519 }
17520
17521 if !merged {
17522 row_highlights.insert(
17523 ix,
17524 RowHighlight {
17525 range: range.clone(),
17526 index,
17527 color,
17528 options,
17529 type_id: TypeId::of::<T>(),
17530 },
17531 );
17532 }
17533
17534 // If any of the following highlights intersect with this one, merge them.
17535 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17536 let highlight = &row_highlights[ix];
17537 if next_highlight
17538 .range
17539 .start
17540 .cmp(&highlight.range.end, &snapshot)
17541 .is_le()
17542 {
17543 if next_highlight
17544 .range
17545 .end
17546 .cmp(&highlight.range.end, &snapshot)
17547 .is_gt()
17548 {
17549 row_highlights[ix].range.end = next_highlight.range.end;
17550 }
17551 row_highlights.remove(ix + 1);
17552 } else {
17553 break;
17554 }
17555 }
17556 }
17557 }
17558
17559 /// Remove any highlighted row ranges of the given type that intersect the
17560 /// given ranges.
17561 pub fn remove_highlighted_rows<T: 'static>(
17562 &mut self,
17563 ranges_to_remove: Vec<Range<Anchor>>,
17564 cx: &mut Context<Self>,
17565 ) {
17566 let snapshot = self.buffer().read(cx).snapshot(cx);
17567 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17568 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17569 row_highlights.retain(|highlight| {
17570 while let Some(range_to_remove) = ranges_to_remove.peek() {
17571 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17572 Ordering::Less | Ordering::Equal => {
17573 ranges_to_remove.next();
17574 }
17575 Ordering::Greater => {
17576 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17577 Ordering::Less | Ordering::Equal => {
17578 return false;
17579 }
17580 Ordering::Greater => break,
17581 }
17582 }
17583 }
17584 }
17585
17586 true
17587 })
17588 }
17589
17590 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17591 pub fn clear_row_highlights<T: 'static>(&mut self) {
17592 self.highlighted_rows.remove(&TypeId::of::<T>());
17593 }
17594
17595 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17596 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17597 self.highlighted_rows
17598 .get(&TypeId::of::<T>())
17599 .map_or(&[] as &[_], |vec| vec.as_slice())
17600 .iter()
17601 .map(|highlight| (highlight.range.clone(), highlight.color))
17602 }
17603
17604 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17605 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17606 /// Allows to ignore certain kinds of highlights.
17607 pub fn highlighted_display_rows(
17608 &self,
17609 window: &mut Window,
17610 cx: &mut App,
17611 ) -> BTreeMap<DisplayRow, LineHighlight> {
17612 let snapshot = self.snapshot(window, cx);
17613 let mut used_highlight_orders = HashMap::default();
17614 self.highlighted_rows
17615 .iter()
17616 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17617 .fold(
17618 BTreeMap::<DisplayRow, LineHighlight>::new(),
17619 |mut unique_rows, highlight| {
17620 let start = highlight.range.start.to_display_point(&snapshot);
17621 let end = highlight.range.end.to_display_point(&snapshot);
17622 let start_row = start.row().0;
17623 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17624 && end.column() == 0
17625 {
17626 end.row().0.saturating_sub(1)
17627 } else {
17628 end.row().0
17629 };
17630 for row in start_row..=end_row {
17631 let used_index =
17632 used_highlight_orders.entry(row).or_insert(highlight.index);
17633 if highlight.index >= *used_index {
17634 *used_index = highlight.index;
17635 unique_rows.insert(
17636 DisplayRow(row),
17637 LineHighlight {
17638 include_gutter: highlight.options.include_gutter,
17639 border: None,
17640 background: highlight.color.into(),
17641 type_id: Some(highlight.type_id),
17642 },
17643 );
17644 }
17645 }
17646 unique_rows
17647 },
17648 )
17649 }
17650
17651 pub fn highlighted_display_row_for_autoscroll(
17652 &self,
17653 snapshot: &DisplaySnapshot,
17654 ) -> Option<DisplayRow> {
17655 self.highlighted_rows
17656 .values()
17657 .flat_map(|highlighted_rows| highlighted_rows.iter())
17658 .filter_map(|highlight| {
17659 if highlight.options.autoscroll {
17660 Some(highlight.range.start.to_display_point(snapshot).row())
17661 } else {
17662 None
17663 }
17664 })
17665 .min()
17666 }
17667
17668 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17669 self.highlight_background::<SearchWithinRange>(
17670 ranges,
17671 |colors| colors.editor_document_highlight_read_background,
17672 cx,
17673 )
17674 }
17675
17676 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17677 self.breadcrumb_header = Some(new_header);
17678 }
17679
17680 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17681 self.clear_background_highlights::<SearchWithinRange>(cx);
17682 }
17683
17684 pub fn highlight_background<T: 'static>(
17685 &mut self,
17686 ranges: &[Range<Anchor>],
17687 color_fetcher: fn(&ThemeColors) -> Hsla,
17688 cx: &mut Context<Self>,
17689 ) {
17690 self.background_highlights
17691 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17692 self.scrollbar_marker_state.dirty = true;
17693 cx.notify();
17694 }
17695
17696 pub fn clear_background_highlights<T: 'static>(
17697 &mut self,
17698 cx: &mut Context<Self>,
17699 ) -> Option<BackgroundHighlight> {
17700 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17701 if !text_highlights.1.is_empty() {
17702 self.scrollbar_marker_state.dirty = true;
17703 cx.notify();
17704 }
17705 Some(text_highlights)
17706 }
17707
17708 pub fn highlight_gutter<T: 'static>(
17709 &mut self,
17710 ranges: &[Range<Anchor>],
17711 color_fetcher: fn(&App) -> Hsla,
17712 cx: &mut Context<Self>,
17713 ) {
17714 self.gutter_highlights
17715 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17716 cx.notify();
17717 }
17718
17719 pub fn clear_gutter_highlights<T: 'static>(
17720 &mut self,
17721 cx: &mut Context<Self>,
17722 ) -> Option<GutterHighlight> {
17723 cx.notify();
17724 self.gutter_highlights.remove(&TypeId::of::<T>())
17725 }
17726
17727 #[cfg(feature = "test-support")]
17728 pub fn all_text_background_highlights(
17729 &self,
17730 window: &mut Window,
17731 cx: &mut Context<Self>,
17732 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17733 let snapshot = self.snapshot(window, cx);
17734 let buffer = &snapshot.buffer_snapshot;
17735 let start = buffer.anchor_before(0);
17736 let end = buffer.anchor_after(buffer.len());
17737 let theme = cx.theme().colors();
17738 self.background_highlights_in_range(start..end, &snapshot, theme)
17739 }
17740
17741 #[cfg(feature = "test-support")]
17742 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17743 let snapshot = self.buffer().read(cx).snapshot(cx);
17744
17745 let highlights = self
17746 .background_highlights
17747 .get(&TypeId::of::<items::BufferSearchHighlights>());
17748
17749 if let Some((_color, ranges)) = highlights {
17750 ranges
17751 .iter()
17752 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17753 .collect_vec()
17754 } else {
17755 vec![]
17756 }
17757 }
17758
17759 fn document_highlights_for_position<'a>(
17760 &'a self,
17761 position: Anchor,
17762 buffer: &'a MultiBufferSnapshot,
17763 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17764 let read_highlights = self
17765 .background_highlights
17766 .get(&TypeId::of::<DocumentHighlightRead>())
17767 .map(|h| &h.1);
17768 let write_highlights = self
17769 .background_highlights
17770 .get(&TypeId::of::<DocumentHighlightWrite>())
17771 .map(|h| &h.1);
17772 let left_position = position.bias_left(buffer);
17773 let right_position = position.bias_right(buffer);
17774 read_highlights
17775 .into_iter()
17776 .chain(write_highlights)
17777 .flat_map(move |ranges| {
17778 let start_ix = match ranges.binary_search_by(|probe| {
17779 let cmp = probe.end.cmp(&left_position, buffer);
17780 if cmp.is_ge() {
17781 Ordering::Greater
17782 } else {
17783 Ordering::Less
17784 }
17785 }) {
17786 Ok(i) | Err(i) => i,
17787 };
17788
17789 ranges[start_ix..]
17790 .iter()
17791 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17792 })
17793 }
17794
17795 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17796 self.background_highlights
17797 .get(&TypeId::of::<T>())
17798 .map_or(false, |(_, highlights)| !highlights.is_empty())
17799 }
17800
17801 pub fn background_highlights_in_range(
17802 &self,
17803 search_range: Range<Anchor>,
17804 display_snapshot: &DisplaySnapshot,
17805 theme: &ThemeColors,
17806 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17807 let mut results = Vec::new();
17808 for (color_fetcher, ranges) in self.background_highlights.values() {
17809 let color = color_fetcher(theme);
17810 let start_ix = match ranges.binary_search_by(|probe| {
17811 let cmp = probe
17812 .end
17813 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17814 if cmp.is_gt() {
17815 Ordering::Greater
17816 } else {
17817 Ordering::Less
17818 }
17819 }) {
17820 Ok(i) | Err(i) => i,
17821 };
17822 for range in &ranges[start_ix..] {
17823 if range
17824 .start
17825 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17826 .is_ge()
17827 {
17828 break;
17829 }
17830
17831 let start = range.start.to_display_point(display_snapshot);
17832 let end = range.end.to_display_point(display_snapshot);
17833 results.push((start..end, color))
17834 }
17835 }
17836 results
17837 }
17838
17839 pub fn background_highlight_row_ranges<T: 'static>(
17840 &self,
17841 search_range: Range<Anchor>,
17842 display_snapshot: &DisplaySnapshot,
17843 count: usize,
17844 ) -> Vec<RangeInclusive<DisplayPoint>> {
17845 let mut results = Vec::new();
17846 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17847 return vec![];
17848 };
17849
17850 let start_ix = match ranges.binary_search_by(|probe| {
17851 let cmp = probe
17852 .end
17853 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17854 if cmp.is_gt() {
17855 Ordering::Greater
17856 } else {
17857 Ordering::Less
17858 }
17859 }) {
17860 Ok(i) | Err(i) => i,
17861 };
17862 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17863 if let (Some(start_display), Some(end_display)) = (start, end) {
17864 results.push(
17865 start_display.to_display_point(display_snapshot)
17866 ..=end_display.to_display_point(display_snapshot),
17867 );
17868 }
17869 };
17870 let mut start_row: Option<Point> = None;
17871 let mut end_row: Option<Point> = None;
17872 if ranges.len() > count {
17873 return Vec::new();
17874 }
17875 for range in &ranges[start_ix..] {
17876 if range
17877 .start
17878 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17879 .is_ge()
17880 {
17881 break;
17882 }
17883 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17884 if let Some(current_row) = &end_row {
17885 if end.row == current_row.row {
17886 continue;
17887 }
17888 }
17889 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17890 if start_row.is_none() {
17891 assert_eq!(end_row, None);
17892 start_row = Some(start);
17893 end_row = Some(end);
17894 continue;
17895 }
17896 if let Some(current_end) = end_row.as_mut() {
17897 if start.row > current_end.row + 1 {
17898 push_region(start_row, end_row);
17899 start_row = Some(start);
17900 end_row = Some(end);
17901 } else {
17902 // Merge two hunks.
17903 *current_end = end;
17904 }
17905 } else {
17906 unreachable!();
17907 }
17908 }
17909 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17910 push_region(start_row, end_row);
17911 results
17912 }
17913
17914 pub fn gutter_highlights_in_range(
17915 &self,
17916 search_range: Range<Anchor>,
17917 display_snapshot: &DisplaySnapshot,
17918 cx: &App,
17919 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17920 let mut results = Vec::new();
17921 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17922 let color = color_fetcher(cx);
17923 let start_ix = match ranges.binary_search_by(|probe| {
17924 let cmp = probe
17925 .end
17926 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17927 if cmp.is_gt() {
17928 Ordering::Greater
17929 } else {
17930 Ordering::Less
17931 }
17932 }) {
17933 Ok(i) | Err(i) => i,
17934 };
17935 for range in &ranges[start_ix..] {
17936 if range
17937 .start
17938 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17939 .is_ge()
17940 {
17941 break;
17942 }
17943
17944 let start = range.start.to_display_point(display_snapshot);
17945 let end = range.end.to_display_point(display_snapshot);
17946 results.push((start..end, color))
17947 }
17948 }
17949 results
17950 }
17951
17952 /// Get the text ranges corresponding to the redaction query
17953 pub fn redacted_ranges(
17954 &self,
17955 search_range: Range<Anchor>,
17956 display_snapshot: &DisplaySnapshot,
17957 cx: &App,
17958 ) -> Vec<Range<DisplayPoint>> {
17959 display_snapshot
17960 .buffer_snapshot
17961 .redacted_ranges(search_range, |file| {
17962 if let Some(file) = file {
17963 file.is_private()
17964 && EditorSettings::get(
17965 Some(SettingsLocation {
17966 worktree_id: file.worktree_id(cx),
17967 path: file.path().as_ref(),
17968 }),
17969 cx,
17970 )
17971 .redact_private_values
17972 } else {
17973 false
17974 }
17975 })
17976 .map(|range| {
17977 range.start.to_display_point(display_snapshot)
17978 ..range.end.to_display_point(display_snapshot)
17979 })
17980 .collect()
17981 }
17982
17983 pub fn highlight_text<T: 'static>(
17984 &mut self,
17985 ranges: Vec<Range<Anchor>>,
17986 style: HighlightStyle,
17987 cx: &mut Context<Self>,
17988 ) {
17989 self.display_map.update(cx, |map, _| {
17990 map.highlight_text(TypeId::of::<T>(), ranges, style)
17991 });
17992 cx.notify();
17993 }
17994
17995 pub(crate) fn highlight_inlays<T: 'static>(
17996 &mut self,
17997 highlights: Vec<InlayHighlight>,
17998 style: HighlightStyle,
17999 cx: &mut Context<Self>,
18000 ) {
18001 self.display_map.update(cx, |map, _| {
18002 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18003 });
18004 cx.notify();
18005 }
18006
18007 pub fn text_highlights<'a, T: 'static>(
18008 &'a self,
18009 cx: &'a App,
18010 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18011 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18012 }
18013
18014 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18015 let cleared = self
18016 .display_map
18017 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18018 if cleared {
18019 cx.notify();
18020 }
18021 }
18022
18023 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18024 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18025 && self.focus_handle.is_focused(window)
18026 }
18027
18028 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18029 self.show_cursor_when_unfocused = is_enabled;
18030 cx.notify();
18031 }
18032
18033 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18034 cx.notify();
18035 }
18036
18037 fn on_debug_session_event(
18038 &mut self,
18039 _session: Entity<Session>,
18040 event: &SessionEvent,
18041 cx: &mut Context<Self>,
18042 ) {
18043 match event {
18044 SessionEvent::InvalidateInlineValue => {
18045 self.refresh_inline_values(cx);
18046 }
18047 _ => {}
18048 }
18049 }
18050
18051 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18052 let Some(project) = self.project.clone() else {
18053 return;
18054 };
18055
18056 if !self.inline_value_cache.enabled {
18057 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18058 self.splice_inlays(&inlays, Vec::new(), cx);
18059 return;
18060 }
18061
18062 let current_execution_position = self
18063 .highlighted_rows
18064 .get(&TypeId::of::<ActiveDebugLine>())
18065 .and_then(|lines| lines.last().map(|line| line.range.start));
18066
18067 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18068 let snapshot = editor
18069 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18070 .ok()?;
18071
18072 let inline_values = editor
18073 .update(cx, |editor, cx| {
18074 let Some(current_execution_position) = current_execution_position else {
18075 return Some(Task::ready(Ok(Vec::new())));
18076 };
18077
18078 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18079 let snapshot = buffer.snapshot(cx);
18080
18081 let excerpt = snapshot.excerpt_containing(
18082 current_execution_position..current_execution_position,
18083 )?;
18084
18085 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18086 })?;
18087
18088 let range =
18089 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18090
18091 project.inline_values(buffer, range, cx)
18092 })
18093 .ok()
18094 .flatten()?
18095 .await
18096 .context("refreshing debugger inlays")
18097 .log_err()?;
18098
18099 let (excerpt_id, buffer_id) = snapshot
18100 .excerpts()
18101 .next()
18102 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
18103 editor
18104 .update(cx, |editor, cx| {
18105 let new_inlays = inline_values
18106 .into_iter()
18107 .map(|debugger_value| {
18108 Inlay::debugger_hint(
18109 post_inc(&mut editor.next_inlay_id),
18110 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
18111 debugger_value.text(),
18112 )
18113 })
18114 .collect::<Vec<_>>();
18115 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18116 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18117
18118 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18119 })
18120 .ok()?;
18121 Some(())
18122 });
18123 }
18124
18125 fn on_buffer_event(
18126 &mut self,
18127 multibuffer: &Entity<MultiBuffer>,
18128 event: &multi_buffer::Event,
18129 window: &mut Window,
18130 cx: &mut Context<Self>,
18131 ) {
18132 match event {
18133 multi_buffer::Event::Edited {
18134 singleton_buffer_edited,
18135 edited_buffer: buffer_edited,
18136 } => {
18137 self.scrollbar_marker_state.dirty = true;
18138 self.active_indent_guides_state.dirty = true;
18139 self.refresh_active_diagnostics(cx);
18140 self.refresh_code_actions(window, cx);
18141 self.refresh_selected_text_highlights(true, window, cx);
18142 refresh_matching_bracket_highlights(self, window, cx);
18143 if self.has_active_inline_completion() {
18144 self.update_visible_inline_completion(window, cx);
18145 }
18146 if let Some(buffer) = buffer_edited {
18147 let buffer_id = buffer.read(cx).remote_id();
18148 if !self.registered_buffers.contains_key(&buffer_id) {
18149 if let Some(project) = self.project.as_ref() {
18150 project.update(cx, |project, cx| {
18151 self.registered_buffers.insert(
18152 buffer_id,
18153 project.register_buffer_with_language_servers(&buffer, cx),
18154 );
18155 })
18156 }
18157 }
18158 }
18159 cx.emit(EditorEvent::BufferEdited);
18160 cx.emit(SearchEvent::MatchesInvalidated);
18161 if *singleton_buffer_edited {
18162 if let Some(project) = &self.project {
18163 #[allow(clippy::mutable_key_type)]
18164 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18165 multibuffer
18166 .all_buffers()
18167 .into_iter()
18168 .filter_map(|buffer| {
18169 buffer.update(cx, |buffer, cx| {
18170 let language = buffer.language()?;
18171 let should_discard = project.update(cx, |project, cx| {
18172 project.is_local()
18173 && !project.has_language_servers_for(buffer, cx)
18174 });
18175 should_discard.not().then_some(language.clone())
18176 })
18177 })
18178 .collect::<HashSet<_>>()
18179 });
18180 if !languages_affected.is_empty() {
18181 self.refresh_inlay_hints(
18182 InlayHintRefreshReason::BufferEdited(languages_affected),
18183 cx,
18184 );
18185 }
18186 }
18187 }
18188
18189 let Some(project) = &self.project else { return };
18190 let (telemetry, is_via_ssh) = {
18191 let project = project.read(cx);
18192 let telemetry = project.client().telemetry().clone();
18193 let is_via_ssh = project.is_via_ssh();
18194 (telemetry, is_via_ssh)
18195 };
18196 refresh_linked_ranges(self, window, cx);
18197 telemetry.log_edit_event("editor", is_via_ssh);
18198 }
18199 multi_buffer::Event::ExcerptsAdded {
18200 buffer,
18201 predecessor,
18202 excerpts,
18203 } => {
18204 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18205 let buffer_id = buffer.read(cx).remote_id();
18206 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18207 if let Some(project) = &self.project {
18208 update_uncommitted_diff_for_buffer(
18209 cx.entity(),
18210 project,
18211 [buffer.clone()],
18212 self.buffer.clone(),
18213 cx,
18214 )
18215 .detach();
18216 }
18217 }
18218 cx.emit(EditorEvent::ExcerptsAdded {
18219 buffer: buffer.clone(),
18220 predecessor: *predecessor,
18221 excerpts: excerpts.clone(),
18222 });
18223 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18224 }
18225 multi_buffer::Event::ExcerptsRemoved {
18226 ids,
18227 removed_buffer_ids,
18228 } => {
18229 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18230 let buffer = self.buffer.read(cx);
18231 self.registered_buffers
18232 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18233 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18234 cx.emit(EditorEvent::ExcerptsRemoved {
18235 ids: ids.clone(),
18236 removed_buffer_ids: removed_buffer_ids.clone(),
18237 })
18238 }
18239 multi_buffer::Event::ExcerptsEdited {
18240 excerpt_ids,
18241 buffer_ids,
18242 } => {
18243 self.display_map.update(cx, |map, cx| {
18244 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18245 });
18246 cx.emit(EditorEvent::ExcerptsEdited {
18247 ids: excerpt_ids.clone(),
18248 })
18249 }
18250 multi_buffer::Event::ExcerptsExpanded { ids } => {
18251 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18252 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18253 }
18254 multi_buffer::Event::Reparsed(buffer_id) => {
18255 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18256 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18257
18258 cx.emit(EditorEvent::Reparsed(*buffer_id));
18259 }
18260 multi_buffer::Event::DiffHunksToggled => {
18261 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18262 }
18263 multi_buffer::Event::LanguageChanged(buffer_id) => {
18264 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18265 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18266 cx.emit(EditorEvent::Reparsed(*buffer_id));
18267 cx.notify();
18268 }
18269 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18270 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18271 multi_buffer::Event::FileHandleChanged
18272 | multi_buffer::Event::Reloaded
18273 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18274 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18275 multi_buffer::Event::DiagnosticsUpdated => {
18276 self.refresh_active_diagnostics(cx);
18277 self.refresh_inline_diagnostics(true, window, cx);
18278 self.scrollbar_marker_state.dirty = true;
18279 cx.notify();
18280 }
18281 _ => {}
18282 };
18283 }
18284
18285 pub fn start_temporary_diff_override(&mut self) {
18286 self.load_diff_task.take();
18287 self.temporary_diff_override = true;
18288 }
18289
18290 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18291 self.temporary_diff_override = false;
18292 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18293 self.buffer.update(cx, |buffer, cx| {
18294 buffer.set_all_diff_hunks_collapsed(cx);
18295 });
18296
18297 if let Some(project) = self.project.clone() {
18298 self.load_diff_task = Some(
18299 update_uncommitted_diff_for_buffer(
18300 cx.entity(),
18301 &project,
18302 self.buffer.read(cx).all_buffers(),
18303 self.buffer.clone(),
18304 cx,
18305 )
18306 .shared(),
18307 );
18308 }
18309 }
18310
18311 fn on_display_map_changed(
18312 &mut self,
18313 _: Entity<DisplayMap>,
18314 _: &mut Window,
18315 cx: &mut Context<Self>,
18316 ) {
18317 cx.notify();
18318 }
18319
18320 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18321 let new_severity = if self.diagnostics_enabled() {
18322 EditorSettings::get_global(cx)
18323 .diagnostics_max_severity
18324 .unwrap_or(DiagnosticSeverity::Hint)
18325 } else {
18326 DiagnosticSeverity::Off
18327 };
18328 self.set_max_diagnostics_severity(new_severity, cx);
18329 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18330 self.update_edit_prediction_settings(cx);
18331 self.refresh_inline_completion(true, false, window, cx);
18332 self.refresh_inlay_hints(
18333 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18334 self.selections.newest_anchor().head(),
18335 &self.buffer.read(cx).snapshot(cx),
18336 cx,
18337 )),
18338 cx,
18339 );
18340
18341 let old_cursor_shape = self.cursor_shape;
18342
18343 {
18344 let editor_settings = EditorSettings::get_global(cx);
18345 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18346 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18347 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18348 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18349 }
18350
18351 if old_cursor_shape != self.cursor_shape {
18352 cx.emit(EditorEvent::CursorShapeChanged);
18353 }
18354
18355 let project_settings = ProjectSettings::get_global(cx);
18356 self.serialize_dirty_buffers =
18357 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18358
18359 if self.mode.is_full() {
18360 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18361 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18362 if self.show_inline_diagnostics != show_inline_diagnostics {
18363 self.show_inline_diagnostics = show_inline_diagnostics;
18364 self.refresh_inline_diagnostics(false, window, cx);
18365 }
18366
18367 if self.git_blame_inline_enabled != inline_blame_enabled {
18368 self.toggle_git_blame_inline_internal(false, window, cx);
18369 }
18370
18371 let minimap_settings = EditorSettings::get_global(cx).minimap;
18372 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18373 self.set_minimap_visibility(
18374 self.minimap_visibility.toggle_visibility(),
18375 window,
18376 cx,
18377 );
18378 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18379 minimap_entity.update(cx, |minimap_editor, cx| {
18380 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18381 })
18382 }
18383 }
18384
18385 cx.notify();
18386 }
18387
18388 pub fn set_searchable(&mut self, searchable: bool) {
18389 self.searchable = searchable;
18390 }
18391
18392 pub fn searchable(&self) -> bool {
18393 self.searchable
18394 }
18395
18396 fn open_proposed_changes_editor(
18397 &mut self,
18398 _: &OpenProposedChangesEditor,
18399 window: &mut Window,
18400 cx: &mut Context<Self>,
18401 ) {
18402 let Some(workspace) = self.workspace() else {
18403 cx.propagate();
18404 return;
18405 };
18406
18407 let selections = self.selections.all::<usize>(cx);
18408 let multi_buffer = self.buffer.read(cx);
18409 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18410 let mut new_selections_by_buffer = HashMap::default();
18411 for selection in selections {
18412 for (buffer, range, _) in
18413 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18414 {
18415 let mut range = range.to_point(buffer);
18416 range.start.column = 0;
18417 range.end.column = buffer.line_len(range.end.row);
18418 new_selections_by_buffer
18419 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18420 .or_insert(Vec::new())
18421 .push(range)
18422 }
18423 }
18424
18425 let proposed_changes_buffers = new_selections_by_buffer
18426 .into_iter()
18427 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18428 .collect::<Vec<_>>();
18429 let proposed_changes_editor = cx.new(|cx| {
18430 ProposedChangesEditor::new(
18431 "Proposed changes",
18432 proposed_changes_buffers,
18433 self.project.clone(),
18434 window,
18435 cx,
18436 )
18437 });
18438
18439 window.defer(cx, move |window, cx| {
18440 workspace.update(cx, |workspace, cx| {
18441 workspace.active_pane().update(cx, |pane, cx| {
18442 pane.add_item(
18443 Box::new(proposed_changes_editor),
18444 true,
18445 true,
18446 None,
18447 window,
18448 cx,
18449 );
18450 });
18451 });
18452 });
18453 }
18454
18455 pub fn open_excerpts_in_split(
18456 &mut self,
18457 _: &OpenExcerptsSplit,
18458 window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 self.open_excerpts_common(None, true, window, cx)
18462 }
18463
18464 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18465 self.open_excerpts_common(None, false, window, cx)
18466 }
18467
18468 fn open_excerpts_common(
18469 &mut self,
18470 jump_data: Option<JumpData>,
18471 split: bool,
18472 window: &mut Window,
18473 cx: &mut Context<Self>,
18474 ) {
18475 let Some(workspace) = self.workspace() else {
18476 cx.propagate();
18477 return;
18478 };
18479
18480 if self.buffer.read(cx).is_singleton() {
18481 cx.propagate();
18482 return;
18483 }
18484
18485 let mut new_selections_by_buffer = HashMap::default();
18486 match &jump_data {
18487 Some(JumpData::MultiBufferPoint {
18488 excerpt_id,
18489 position,
18490 anchor,
18491 line_offset_from_top,
18492 }) => {
18493 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18494 if let Some(buffer) = multi_buffer_snapshot
18495 .buffer_id_for_excerpt(*excerpt_id)
18496 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18497 {
18498 let buffer_snapshot = buffer.read(cx).snapshot();
18499 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18500 language::ToPoint::to_point(anchor, &buffer_snapshot)
18501 } else {
18502 buffer_snapshot.clip_point(*position, Bias::Left)
18503 };
18504 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18505 new_selections_by_buffer.insert(
18506 buffer,
18507 (
18508 vec![jump_to_offset..jump_to_offset],
18509 Some(*line_offset_from_top),
18510 ),
18511 );
18512 }
18513 }
18514 Some(JumpData::MultiBufferRow {
18515 row,
18516 line_offset_from_top,
18517 }) => {
18518 let point = MultiBufferPoint::new(row.0, 0);
18519 if let Some((buffer, buffer_point, _)) =
18520 self.buffer.read(cx).point_to_buffer_point(point, cx)
18521 {
18522 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18523 new_selections_by_buffer
18524 .entry(buffer)
18525 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18526 .0
18527 .push(buffer_offset..buffer_offset)
18528 }
18529 }
18530 None => {
18531 let selections = self.selections.all::<usize>(cx);
18532 let multi_buffer = self.buffer.read(cx);
18533 for selection in selections {
18534 for (snapshot, range, _, anchor) in multi_buffer
18535 .snapshot(cx)
18536 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18537 {
18538 if let Some(anchor) = anchor {
18539 // selection is in a deleted hunk
18540 let Some(buffer_id) = anchor.buffer_id else {
18541 continue;
18542 };
18543 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18544 continue;
18545 };
18546 let offset = text::ToOffset::to_offset(
18547 &anchor.text_anchor,
18548 &buffer_handle.read(cx).snapshot(),
18549 );
18550 let range = offset..offset;
18551 new_selections_by_buffer
18552 .entry(buffer_handle)
18553 .or_insert((Vec::new(), None))
18554 .0
18555 .push(range)
18556 } else {
18557 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18558 else {
18559 continue;
18560 };
18561 new_selections_by_buffer
18562 .entry(buffer_handle)
18563 .or_insert((Vec::new(), None))
18564 .0
18565 .push(range)
18566 }
18567 }
18568 }
18569 }
18570 }
18571
18572 new_selections_by_buffer
18573 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18574
18575 if new_selections_by_buffer.is_empty() {
18576 return;
18577 }
18578
18579 // We defer the pane interaction because we ourselves are a workspace item
18580 // and activating a new item causes the pane to call a method on us reentrantly,
18581 // which panics if we're on the stack.
18582 window.defer(cx, move |window, cx| {
18583 workspace.update(cx, |workspace, cx| {
18584 let pane = if split {
18585 workspace.adjacent_pane(window, cx)
18586 } else {
18587 workspace.active_pane().clone()
18588 };
18589
18590 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18591 let editor = buffer
18592 .read(cx)
18593 .file()
18594 .is_none()
18595 .then(|| {
18596 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18597 // so `workspace.open_project_item` will never find them, always opening a new editor.
18598 // Instead, we try to activate the existing editor in the pane first.
18599 let (editor, pane_item_index) =
18600 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18601 let editor = item.downcast::<Editor>()?;
18602 let singleton_buffer =
18603 editor.read(cx).buffer().read(cx).as_singleton()?;
18604 if singleton_buffer == buffer {
18605 Some((editor, i))
18606 } else {
18607 None
18608 }
18609 })?;
18610 pane.update(cx, |pane, cx| {
18611 pane.activate_item(pane_item_index, true, true, window, cx)
18612 });
18613 Some(editor)
18614 })
18615 .flatten()
18616 .unwrap_or_else(|| {
18617 workspace.open_project_item::<Self>(
18618 pane.clone(),
18619 buffer,
18620 true,
18621 true,
18622 window,
18623 cx,
18624 )
18625 });
18626
18627 editor.update(cx, |editor, cx| {
18628 let autoscroll = match scroll_offset {
18629 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18630 None => Autoscroll::newest(),
18631 };
18632 let nav_history = editor.nav_history.take();
18633 editor.change_selections(Some(autoscroll), window, cx, |s| {
18634 s.select_ranges(ranges);
18635 });
18636 editor.nav_history = nav_history;
18637 });
18638 }
18639 })
18640 });
18641 }
18642
18643 // For now, don't allow opening excerpts in buffers that aren't backed by
18644 // regular project files.
18645 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18646 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18647 }
18648
18649 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18650 let snapshot = self.buffer.read(cx).read(cx);
18651 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18652 Some(
18653 ranges
18654 .iter()
18655 .map(move |range| {
18656 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18657 })
18658 .collect(),
18659 )
18660 }
18661
18662 fn selection_replacement_ranges(
18663 &self,
18664 range: Range<OffsetUtf16>,
18665 cx: &mut App,
18666 ) -> Vec<Range<OffsetUtf16>> {
18667 let selections = self.selections.all::<OffsetUtf16>(cx);
18668 let newest_selection = selections
18669 .iter()
18670 .max_by_key(|selection| selection.id)
18671 .unwrap();
18672 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18673 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18674 let snapshot = self.buffer.read(cx).read(cx);
18675 selections
18676 .into_iter()
18677 .map(|mut selection| {
18678 selection.start.0 =
18679 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18680 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18681 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18682 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18683 })
18684 .collect()
18685 }
18686
18687 fn report_editor_event(
18688 &self,
18689 event_type: &'static str,
18690 file_extension: Option<String>,
18691 cx: &App,
18692 ) {
18693 if cfg!(any(test, feature = "test-support")) {
18694 return;
18695 }
18696
18697 let Some(project) = &self.project else { return };
18698
18699 // If None, we are in a file without an extension
18700 let file = self
18701 .buffer
18702 .read(cx)
18703 .as_singleton()
18704 .and_then(|b| b.read(cx).file());
18705 let file_extension = file_extension.or(file
18706 .as_ref()
18707 .and_then(|file| Path::new(file.file_name(cx)).extension())
18708 .and_then(|e| e.to_str())
18709 .map(|a| a.to_string()));
18710
18711 let vim_mode = vim_enabled(cx);
18712
18713 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18714 let copilot_enabled = edit_predictions_provider
18715 == language::language_settings::EditPredictionProvider::Copilot;
18716 let copilot_enabled_for_language = self
18717 .buffer
18718 .read(cx)
18719 .language_settings(cx)
18720 .show_edit_predictions;
18721
18722 let project = project.read(cx);
18723 telemetry::event!(
18724 event_type,
18725 file_extension,
18726 vim_mode,
18727 copilot_enabled,
18728 copilot_enabled_for_language,
18729 edit_predictions_provider,
18730 is_via_ssh = project.is_via_ssh(),
18731 );
18732 }
18733
18734 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18735 /// with each line being an array of {text, highlight} objects.
18736 fn copy_highlight_json(
18737 &mut self,
18738 _: &CopyHighlightJson,
18739 window: &mut Window,
18740 cx: &mut Context<Self>,
18741 ) {
18742 #[derive(Serialize)]
18743 struct Chunk<'a> {
18744 text: String,
18745 highlight: Option<&'a str>,
18746 }
18747
18748 let snapshot = self.buffer.read(cx).snapshot(cx);
18749 let range = self
18750 .selected_text_range(false, window, cx)
18751 .and_then(|selection| {
18752 if selection.range.is_empty() {
18753 None
18754 } else {
18755 Some(selection.range)
18756 }
18757 })
18758 .unwrap_or_else(|| 0..snapshot.len());
18759
18760 let chunks = snapshot.chunks(range, true);
18761 let mut lines = Vec::new();
18762 let mut line: VecDeque<Chunk> = VecDeque::new();
18763
18764 let Some(style) = self.style.as_ref() else {
18765 return;
18766 };
18767
18768 for chunk in chunks {
18769 let highlight = chunk
18770 .syntax_highlight_id
18771 .and_then(|id| id.name(&style.syntax));
18772 let mut chunk_lines = chunk.text.split('\n').peekable();
18773 while let Some(text) = chunk_lines.next() {
18774 let mut merged_with_last_token = false;
18775 if let Some(last_token) = line.back_mut() {
18776 if last_token.highlight == highlight {
18777 last_token.text.push_str(text);
18778 merged_with_last_token = true;
18779 }
18780 }
18781
18782 if !merged_with_last_token {
18783 line.push_back(Chunk {
18784 text: text.into(),
18785 highlight,
18786 });
18787 }
18788
18789 if chunk_lines.peek().is_some() {
18790 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18791 line.pop_front();
18792 }
18793 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18794 line.pop_back();
18795 }
18796
18797 lines.push(mem::take(&mut line));
18798 }
18799 }
18800 }
18801
18802 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18803 return;
18804 };
18805 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18806 }
18807
18808 pub fn open_context_menu(
18809 &mut self,
18810 _: &OpenContextMenu,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 self.request_autoscroll(Autoscroll::newest(), cx);
18815 let position = self.selections.newest_display(cx).start;
18816 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18817 }
18818
18819 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18820 &self.inlay_hint_cache
18821 }
18822
18823 pub fn replay_insert_event(
18824 &mut self,
18825 text: &str,
18826 relative_utf16_range: Option<Range<isize>>,
18827 window: &mut Window,
18828 cx: &mut Context<Self>,
18829 ) {
18830 if !self.input_enabled {
18831 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18832 return;
18833 }
18834 if let Some(relative_utf16_range) = relative_utf16_range {
18835 let selections = self.selections.all::<OffsetUtf16>(cx);
18836 self.change_selections(None, window, cx, |s| {
18837 let new_ranges = selections.into_iter().map(|range| {
18838 let start = OffsetUtf16(
18839 range
18840 .head()
18841 .0
18842 .saturating_add_signed(relative_utf16_range.start),
18843 );
18844 let end = OffsetUtf16(
18845 range
18846 .head()
18847 .0
18848 .saturating_add_signed(relative_utf16_range.end),
18849 );
18850 start..end
18851 });
18852 s.select_ranges(new_ranges);
18853 });
18854 }
18855
18856 self.handle_input(text, window, cx);
18857 }
18858
18859 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18860 let Some(provider) = self.semantics_provider.as_ref() else {
18861 return false;
18862 };
18863
18864 let mut supports = false;
18865 self.buffer().update(cx, |this, cx| {
18866 this.for_each_buffer(|buffer| {
18867 supports |= provider.supports_inlay_hints(buffer, cx);
18868 });
18869 });
18870
18871 supports
18872 }
18873
18874 pub fn is_focused(&self, window: &Window) -> bool {
18875 self.focus_handle.is_focused(window)
18876 }
18877
18878 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18879 cx.emit(EditorEvent::Focused);
18880
18881 if let Some(descendant) = self
18882 .last_focused_descendant
18883 .take()
18884 .and_then(|descendant| descendant.upgrade())
18885 {
18886 window.focus(&descendant);
18887 } else {
18888 if let Some(blame) = self.blame.as_ref() {
18889 blame.update(cx, GitBlame::focus)
18890 }
18891
18892 self.blink_manager.update(cx, BlinkManager::enable);
18893 self.show_cursor_names(window, cx);
18894 self.buffer.update(cx, |buffer, cx| {
18895 buffer.finalize_last_transaction(cx);
18896 if self.leader_id.is_none() {
18897 buffer.set_active_selections(
18898 &self.selections.disjoint_anchors(),
18899 self.selections.line_mode,
18900 self.cursor_shape,
18901 cx,
18902 );
18903 }
18904 });
18905 }
18906 }
18907
18908 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18909 cx.emit(EditorEvent::FocusedIn)
18910 }
18911
18912 fn handle_focus_out(
18913 &mut self,
18914 event: FocusOutEvent,
18915 _window: &mut Window,
18916 cx: &mut Context<Self>,
18917 ) {
18918 if event.blurred != self.focus_handle {
18919 self.last_focused_descendant = Some(event.blurred);
18920 }
18921 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18922 }
18923
18924 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18925 self.blink_manager.update(cx, BlinkManager::disable);
18926 self.buffer
18927 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18928
18929 if let Some(blame) = self.blame.as_ref() {
18930 blame.update(cx, GitBlame::blur)
18931 }
18932 if !self.hover_state.focused(window, cx) {
18933 hide_hover(self, cx);
18934 }
18935 if !self
18936 .context_menu
18937 .borrow()
18938 .as_ref()
18939 .is_some_and(|context_menu| context_menu.focused(window, cx))
18940 {
18941 self.hide_context_menu(window, cx);
18942 }
18943 self.discard_inline_completion(false, cx);
18944 cx.emit(EditorEvent::Blurred);
18945 cx.notify();
18946 }
18947
18948 pub fn register_action<A: Action>(
18949 &mut self,
18950 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18951 ) -> Subscription {
18952 let id = self.next_editor_action_id.post_inc();
18953 let listener = Arc::new(listener);
18954 self.editor_actions.borrow_mut().insert(
18955 id,
18956 Box::new(move |window, _| {
18957 let listener = listener.clone();
18958 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18959 let action = action.downcast_ref().unwrap();
18960 if phase == DispatchPhase::Bubble {
18961 listener(action, window, cx)
18962 }
18963 })
18964 }),
18965 );
18966
18967 let editor_actions = self.editor_actions.clone();
18968 Subscription::new(move || {
18969 editor_actions.borrow_mut().remove(&id);
18970 })
18971 }
18972
18973 pub fn file_header_size(&self) -> u32 {
18974 FILE_HEADER_HEIGHT
18975 }
18976
18977 pub fn restore(
18978 &mut self,
18979 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18980 window: &mut Window,
18981 cx: &mut Context<Self>,
18982 ) {
18983 let workspace = self.workspace();
18984 let project = self.project.as_ref();
18985 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18986 let mut tasks = Vec::new();
18987 for (buffer_id, changes) in revert_changes {
18988 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18989 buffer.update(cx, |buffer, cx| {
18990 buffer.edit(
18991 changes
18992 .into_iter()
18993 .map(|(range, text)| (range, text.to_string())),
18994 None,
18995 cx,
18996 );
18997 });
18998
18999 if let Some(project) =
19000 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19001 {
19002 project.update(cx, |project, cx| {
19003 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19004 })
19005 }
19006 }
19007 }
19008 tasks
19009 });
19010 cx.spawn_in(window, async move |_, cx| {
19011 for (buffer, task) in save_tasks {
19012 let result = task.await;
19013 if result.is_err() {
19014 let Some(path) = buffer
19015 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19016 .ok()
19017 else {
19018 continue;
19019 };
19020 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19021 let Some(task) = cx
19022 .update_window_entity(&workspace, |workspace, window, cx| {
19023 workspace
19024 .open_path_preview(path, None, false, false, false, window, cx)
19025 })
19026 .ok()
19027 else {
19028 continue;
19029 };
19030 task.await.log_err();
19031 }
19032 }
19033 }
19034 })
19035 .detach();
19036 self.change_selections(None, window, cx, |selections| selections.refresh());
19037 }
19038
19039 pub fn to_pixel_point(
19040 &self,
19041 source: multi_buffer::Anchor,
19042 editor_snapshot: &EditorSnapshot,
19043 window: &mut Window,
19044 ) -> Option<gpui::Point<Pixels>> {
19045 let source_point = source.to_display_point(editor_snapshot);
19046 self.display_to_pixel_point(source_point, editor_snapshot, window)
19047 }
19048
19049 pub fn display_to_pixel_point(
19050 &self,
19051 source: DisplayPoint,
19052 editor_snapshot: &EditorSnapshot,
19053 window: &mut Window,
19054 ) -> Option<gpui::Point<Pixels>> {
19055 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19056 let text_layout_details = self.text_layout_details(window);
19057 let scroll_top = text_layout_details
19058 .scroll_anchor
19059 .scroll_position(editor_snapshot)
19060 .y;
19061
19062 if source.row().as_f32() < scroll_top.floor() {
19063 return None;
19064 }
19065 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19066 let source_y = line_height * (source.row().as_f32() - scroll_top);
19067 Some(gpui::Point::new(source_x, source_y))
19068 }
19069
19070 pub fn has_visible_completions_menu(&self) -> bool {
19071 !self.edit_prediction_preview_is_active()
19072 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19073 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19074 })
19075 }
19076
19077 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19078 if self.mode.is_minimap() {
19079 return;
19080 }
19081 self.addons
19082 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19083 }
19084
19085 pub fn unregister_addon<T: Addon>(&mut self) {
19086 self.addons.remove(&std::any::TypeId::of::<T>());
19087 }
19088
19089 pub fn addon<T: Addon>(&self) -> Option<&T> {
19090 let type_id = std::any::TypeId::of::<T>();
19091 self.addons
19092 .get(&type_id)
19093 .and_then(|item| item.to_any().downcast_ref::<T>())
19094 }
19095
19096 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19097 let type_id = std::any::TypeId::of::<T>();
19098 self.addons
19099 .get_mut(&type_id)
19100 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19101 }
19102
19103 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19104 let text_layout_details = self.text_layout_details(window);
19105 let style = &text_layout_details.editor_style;
19106 let font_id = window.text_system().resolve_font(&style.text.font());
19107 let font_size = style.text.font_size.to_pixels(window.rem_size());
19108 let line_height = style.text.line_height_in_pixels(window.rem_size());
19109 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19110
19111 gpui::Size::new(em_width, line_height)
19112 }
19113
19114 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19115 self.load_diff_task.clone()
19116 }
19117
19118 fn read_metadata_from_db(
19119 &mut self,
19120 item_id: u64,
19121 workspace_id: WorkspaceId,
19122 window: &mut Window,
19123 cx: &mut Context<Editor>,
19124 ) {
19125 if self.is_singleton(cx)
19126 && !self.mode.is_minimap()
19127 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19128 {
19129 let buffer_snapshot = OnceCell::new();
19130
19131 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19132 if !folds.is_empty() {
19133 let snapshot =
19134 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19135 self.fold_ranges(
19136 folds
19137 .into_iter()
19138 .map(|(start, end)| {
19139 snapshot.clip_offset(start, Bias::Left)
19140 ..snapshot.clip_offset(end, Bias::Right)
19141 })
19142 .collect(),
19143 false,
19144 window,
19145 cx,
19146 );
19147 }
19148 }
19149
19150 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19151 if !selections.is_empty() {
19152 let snapshot =
19153 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19154 self.change_selections(None, window, cx, |s| {
19155 s.select_ranges(selections.into_iter().map(|(start, end)| {
19156 snapshot.clip_offset(start, Bias::Left)
19157 ..snapshot.clip_offset(end, Bias::Right)
19158 }));
19159 });
19160 }
19161 };
19162 }
19163
19164 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19165 }
19166}
19167
19168fn vim_enabled(cx: &App) -> bool {
19169 cx.global::<SettingsStore>()
19170 .raw_user_settings()
19171 .get("vim_mode")
19172 == Some(&serde_json::Value::Bool(true))
19173}
19174
19175// Consider user intent and default settings
19176fn choose_completion_range(
19177 completion: &Completion,
19178 intent: CompletionIntent,
19179 buffer: &Entity<Buffer>,
19180 cx: &mut Context<Editor>,
19181) -> Range<usize> {
19182 fn should_replace(
19183 completion: &Completion,
19184 insert_range: &Range<text::Anchor>,
19185 intent: CompletionIntent,
19186 completion_mode_setting: LspInsertMode,
19187 buffer: &Buffer,
19188 ) -> bool {
19189 // specific actions take precedence over settings
19190 match intent {
19191 CompletionIntent::CompleteWithInsert => return false,
19192 CompletionIntent::CompleteWithReplace => return true,
19193 CompletionIntent::Complete | CompletionIntent::Compose => {}
19194 }
19195
19196 match completion_mode_setting {
19197 LspInsertMode::Insert => false,
19198 LspInsertMode::Replace => true,
19199 LspInsertMode::ReplaceSubsequence => {
19200 let mut text_to_replace = buffer.chars_for_range(
19201 buffer.anchor_before(completion.replace_range.start)
19202 ..buffer.anchor_after(completion.replace_range.end),
19203 );
19204 let mut completion_text = completion.new_text.chars();
19205
19206 // is `text_to_replace` a subsequence of `completion_text`
19207 text_to_replace
19208 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19209 }
19210 LspInsertMode::ReplaceSuffix => {
19211 let range_after_cursor = insert_range.end..completion.replace_range.end;
19212
19213 let text_after_cursor = buffer
19214 .text_for_range(
19215 buffer.anchor_before(range_after_cursor.start)
19216 ..buffer.anchor_after(range_after_cursor.end),
19217 )
19218 .collect::<String>();
19219 completion.new_text.ends_with(&text_after_cursor)
19220 }
19221 }
19222 }
19223
19224 let buffer = buffer.read(cx);
19225
19226 if let CompletionSource::Lsp {
19227 insert_range: Some(insert_range),
19228 ..
19229 } = &completion.source
19230 {
19231 let completion_mode_setting =
19232 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19233 .completions
19234 .lsp_insert_mode;
19235
19236 if !should_replace(
19237 completion,
19238 &insert_range,
19239 intent,
19240 completion_mode_setting,
19241 buffer,
19242 ) {
19243 return insert_range.to_offset(buffer);
19244 }
19245 }
19246
19247 completion.replace_range.to_offset(buffer)
19248}
19249
19250fn insert_extra_newline_brackets(
19251 buffer: &MultiBufferSnapshot,
19252 range: Range<usize>,
19253 language: &language::LanguageScope,
19254) -> bool {
19255 let leading_whitespace_len = buffer
19256 .reversed_chars_at(range.start)
19257 .take_while(|c| c.is_whitespace() && *c != '\n')
19258 .map(|c| c.len_utf8())
19259 .sum::<usize>();
19260 let trailing_whitespace_len = buffer
19261 .chars_at(range.end)
19262 .take_while(|c| c.is_whitespace() && *c != '\n')
19263 .map(|c| c.len_utf8())
19264 .sum::<usize>();
19265 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19266
19267 language.brackets().any(|(pair, enabled)| {
19268 let pair_start = pair.start.trim_end();
19269 let pair_end = pair.end.trim_start();
19270
19271 enabled
19272 && pair.newline
19273 && buffer.contains_str_at(range.end, pair_end)
19274 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19275 })
19276}
19277
19278fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19279 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19280 [(buffer, range, _)] => (*buffer, range.clone()),
19281 _ => return false,
19282 };
19283 let pair = {
19284 let mut result: Option<BracketMatch> = None;
19285
19286 for pair in buffer
19287 .all_bracket_ranges(range.clone())
19288 .filter(move |pair| {
19289 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19290 })
19291 {
19292 let len = pair.close_range.end - pair.open_range.start;
19293
19294 if let Some(existing) = &result {
19295 let existing_len = existing.close_range.end - existing.open_range.start;
19296 if len > existing_len {
19297 continue;
19298 }
19299 }
19300
19301 result = Some(pair);
19302 }
19303
19304 result
19305 };
19306 let Some(pair) = pair else {
19307 return false;
19308 };
19309 pair.newline_only
19310 && buffer
19311 .chars_for_range(pair.open_range.end..range.start)
19312 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19313 .all(|c| c.is_whitespace() && c != '\n')
19314}
19315
19316fn update_uncommitted_diff_for_buffer(
19317 editor: Entity<Editor>,
19318 project: &Entity<Project>,
19319 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19320 buffer: Entity<MultiBuffer>,
19321 cx: &mut App,
19322) -> Task<()> {
19323 let mut tasks = Vec::new();
19324 project.update(cx, |project, cx| {
19325 for buffer in buffers {
19326 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19327 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19328 }
19329 }
19330 });
19331 cx.spawn(async move |cx| {
19332 let diffs = future::join_all(tasks).await;
19333 if editor
19334 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19335 .unwrap_or(false)
19336 {
19337 return;
19338 }
19339
19340 buffer
19341 .update(cx, |buffer, cx| {
19342 for diff in diffs.into_iter().flatten() {
19343 buffer.add_diff(diff, cx);
19344 }
19345 })
19346 .ok();
19347 })
19348}
19349
19350fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19351 let tab_size = tab_size.get() as usize;
19352 let mut width = offset;
19353
19354 for ch in text.chars() {
19355 width += if ch == '\t' {
19356 tab_size - (width % tab_size)
19357 } else {
19358 1
19359 };
19360 }
19361
19362 width - offset
19363}
19364
19365#[cfg(test)]
19366mod tests {
19367 use super::*;
19368
19369 #[test]
19370 fn test_string_size_with_expanded_tabs() {
19371 let nz = |val| NonZeroU32::new(val).unwrap();
19372 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19373 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19374 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19375 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19376 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19377 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19378 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19379 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19380 }
19381}
19382
19383/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19384struct WordBreakingTokenizer<'a> {
19385 input: &'a str,
19386}
19387
19388impl<'a> WordBreakingTokenizer<'a> {
19389 fn new(input: &'a str) -> Self {
19390 Self { input }
19391 }
19392}
19393
19394fn is_char_ideographic(ch: char) -> bool {
19395 use unicode_script::Script::*;
19396 use unicode_script::UnicodeScript;
19397 matches!(ch.script(), Han | Tangut | Yi)
19398}
19399
19400fn is_grapheme_ideographic(text: &str) -> bool {
19401 text.chars().any(is_char_ideographic)
19402}
19403
19404fn is_grapheme_whitespace(text: &str) -> bool {
19405 text.chars().any(|x| x.is_whitespace())
19406}
19407
19408fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19409 text.chars().next().map_or(false, |ch| {
19410 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19411 })
19412}
19413
19414#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19415enum WordBreakToken<'a> {
19416 Word { token: &'a str, grapheme_len: usize },
19417 InlineWhitespace { token: &'a str, grapheme_len: usize },
19418 Newline,
19419}
19420
19421impl<'a> Iterator for WordBreakingTokenizer<'a> {
19422 /// Yields a span, the count of graphemes in the token, and whether it was
19423 /// whitespace. Note that it also breaks at word boundaries.
19424 type Item = WordBreakToken<'a>;
19425
19426 fn next(&mut self) -> Option<Self::Item> {
19427 use unicode_segmentation::UnicodeSegmentation;
19428 if self.input.is_empty() {
19429 return None;
19430 }
19431
19432 let mut iter = self.input.graphemes(true).peekable();
19433 let mut offset = 0;
19434 let mut grapheme_len = 0;
19435 if let Some(first_grapheme) = iter.next() {
19436 let is_newline = first_grapheme == "\n";
19437 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19438 offset += first_grapheme.len();
19439 grapheme_len += 1;
19440 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19441 if let Some(grapheme) = iter.peek().copied() {
19442 if should_stay_with_preceding_ideograph(grapheme) {
19443 offset += grapheme.len();
19444 grapheme_len += 1;
19445 }
19446 }
19447 } else {
19448 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19449 let mut next_word_bound = words.peek().copied();
19450 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19451 next_word_bound = words.next();
19452 }
19453 while let Some(grapheme) = iter.peek().copied() {
19454 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19455 break;
19456 };
19457 if is_grapheme_whitespace(grapheme) != is_whitespace
19458 || (grapheme == "\n") != is_newline
19459 {
19460 break;
19461 };
19462 offset += grapheme.len();
19463 grapheme_len += 1;
19464 iter.next();
19465 }
19466 }
19467 let token = &self.input[..offset];
19468 self.input = &self.input[offset..];
19469 if token == "\n" {
19470 Some(WordBreakToken::Newline)
19471 } else if is_whitespace {
19472 Some(WordBreakToken::InlineWhitespace {
19473 token,
19474 grapheme_len,
19475 })
19476 } else {
19477 Some(WordBreakToken::Word {
19478 token,
19479 grapheme_len,
19480 })
19481 }
19482 } else {
19483 None
19484 }
19485 }
19486}
19487
19488#[test]
19489fn test_word_breaking_tokenizer() {
19490 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19491 ("", &[]),
19492 (" ", &[whitespace(" ", 2)]),
19493 ("Ʒ", &[word("Ʒ", 1)]),
19494 ("Ǽ", &[word("Ǽ", 1)]),
19495 ("⋑", &[word("⋑", 1)]),
19496 ("⋑⋑", &[word("⋑⋑", 2)]),
19497 (
19498 "原理,进而",
19499 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19500 ),
19501 (
19502 "hello world",
19503 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19504 ),
19505 (
19506 "hello, world",
19507 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19508 ),
19509 (
19510 " hello world",
19511 &[
19512 whitespace(" ", 2),
19513 word("hello", 5),
19514 whitespace(" ", 1),
19515 word("world", 5),
19516 ],
19517 ),
19518 (
19519 "这是什么 \n 钢笔",
19520 &[
19521 word("这", 1),
19522 word("是", 1),
19523 word("什", 1),
19524 word("么", 1),
19525 whitespace(" ", 1),
19526 newline(),
19527 whitespace(" ", 1),
19528 word("钢", 1),
19529 word("笔", 1),
19530 ],
19531 ),
19532 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19533 ];
19534
19535 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19536 WordBreakToken::Word {
19537 token,
19538 grapheme_len,
19539 }
19540 }
19541
19542 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19543 WordBreakToken::InlineWhitespace {
19544 token,
19545 grapheme_len,
19546 }
19547 }
19548
19549 fn newline() -> WordBreakToken<'static> {
19550 WordBreakToken::Newline
19551 }
19552
19553 for (input, result) in tests {
19554 assert_eq!(
19555 WordBreakingTokenizer::new(input)
19556 .collect::<Vec<_>>()
19557 .as_slice(),
19558 *result,
19559 );
19560 }
19561}
19562
19563fn wrap_with_prefix(
19564 line_prefix: String,
19565 unwrapped_text: String,
19566 wrap_column: usize,
19567 tab_size: NonZeroU32,
19568 preserve_existing_whitespace: bool,
19569) -> String {
19570 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19571 let mut wrapped_text = String::new();
19572 let mut current_line = line_prefix.clone();
19573
19574 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19575 let mut current_line_len = line_prefix_len;
19576 let mut in_whitespace = false;
19577 for token in tokenizer {
19578 let have_preceding_whitespace = in_whitespace;
19579 match token {
19580 WordBreakToken::Word {
19581 token,
19582 grapheme_len,
19583 } => {
19584 in_whitespace = false;
19585 if current_line_len + grapheme_len > wrap_column
19586 && current_line_len != line_prefix_len
19587 {
19588 wrapped_text.push_str(current_line.trim_end());
19589 wrapped_text.push('\n');
19590 current_line.truncate(line_prefix.len());
19591 current_line_len = line_prefix_len;
19592 }
19593 current_line.push_str(token);
19594 current_line_len += grapheme_len;
19595 }
19596 WordBreakToken::InlineWhitespace {
19597 mut token,
19598 mut grapheme_len,
19599 } => {
19600 in_whitespace = true;
19601 if have_preceding_whitespace && !preserve_existing_whitespace {
19602 continue;
19603 }
19604 if !preserve_existing_whitespace {
19605 token = " ";
19606 grapheme_len = 1;
19607 }
19608 if current_line_len + grapheme_len > wrap_column {
19609 wrapped_text.push_str(current_line.trim_end());
19610 wrapped_text.push('\n');
19611 current_line.truncate(line_prefix.len());
19612 current_line_len = line_prefix_len;
19613 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19614 current_line.push_str(token);
19615 current_line_len += grapheme_len;
19616 }
19617 }
19618 WordBreakToken::Newline => {
19619 in_whitespace = true;
19620 if preserve_existing_whitespace {
19621 wrapped_text.push_str(current_line.trim_end());
19622 wrapped_text.push('\n');
19623 current_line.truncate(line_prefix.len());
19624 current_line_len = line_prefix_len;
19625 } else if have_preceding_whitespace {
19626 continue;
19627 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19628 {
19629 wrapped_text.push_str(current_line.trim_end());
19630 wrapped_text.push('\n');
19631 current_line.truncate(line_prefix.len());
19632 current_line_len = line_prefix_len;
19633 } else if current_line_len != line_prefix_len {
19634 current_line.push(' ');
19635 current_line_len += 1;
19636 }
19637 }
19638 }
19639 }
19640
19641 if !current_line.is_empty() {
19642 wrapped_text.push_str(¤t_line);
19643 }
19644 wrapped_text
19645}
19646
19647#[test]
19648fn test_wrap_with_prefix() {
19649 assert_eq!(
19650 wrap_with_prefix(
19651 "# ".to_string(),
19652 "abcdefg".to_string(),
19653 4,
19654 NonZeroU32::new(4).unwrap(),
19655 false,
19656 ),
19657 "# abcdefg"
19658 );
19659 assert_eq!(
19660 wrap_with_prefix(
19661 "".to_string(),
19662 "\thello world".to_string(),
19663 8,
19664 NonZeroU32::new(4).unwrap(),
19665 false,
19666 ),
19667 "hello\nworld"
19668 );
19669 assert_eq!(
19670 wrap_with_prefix(
19671 "// ".to_string(),
19672 "xx \nyy zz aa bb cc".to_string(),
19673 12,
19674 NonZeroU32::new(4).unwrap(),
19675 false,
19676 ),
19677 "// xx yy zz\n// aa bb cc"
19678 );
19679 assert_eq!(
19680 wrap_with_prefix(
19681 String::new(),
19682 "这是什么 \n 钢笔".to_string(),
19683 3,
19684 NonZeroU32::new(4).unwrap(),
19685 false,
19686 ),
19687 "这是什\n么 钢\n笔"
19688 );
19689}
19690
19691pub trait CollaborationHub {
19692 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19693 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19694 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19695}
19696
19697impl CollaborationHub for Entity<Project> {
19698 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19699 self.read(cx).collaborators()
19700 }
19701
19702 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19703 self.read(cx).user_store().read(cx).participant_indices()
19704 }
19705
19706 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19707 let this = self.read(cx);
19708 let user_ids = this.collaborators().values().map(|c| c.user_id);
19709 this.user_store().read_with(cx, |user_store, cx| {
19710 user_store.participant_names(user_ids, cx)
19711 })
19712 }
19713}
19714
19715pub trait SemanticsProvider {
19716 fn hover(
19717 &self,
19718 buffer: &Entity<Buffer>,
19719 position: text::Anchor,
19720 cx: &mut App,
19721 ) -> Option<Task<Vec<project::Hover>>>;
19722
19723 fn inline_values(
19724 &self,
19725 buffer_handle: Entity<Buffer>,
19726 range: Range<text::Anchor>,
19727 cx: &mut App,
19728 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19729
19730 fn inlay_hints(
19731 &self,
19732 buffer_handle: Entity<Buffer>,
19733 range: Range<text::Anchor>,
19734 cx: &mut App,
19735 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19736
19737 fn resolve_inlay_hint(
19738 &self,
19739 hint: InlayHint,
19740 buffer_handle: Entity<Buffer>,
19741 server_id: LanguageServerId,
19742 cx: &mut App,
19743 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19744
19745 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19746
19747 fn document_highlights(
19748 &self,
19749 buffer: &Entity<Buffer>,
19750 position: text::Anchor,
19751 cx: &mut App,
19752 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19753
19754 fn definitions(
19755 &self,
19756 buffer: &Entity<Buffer>,
19757 position: text::Anchor,
19758 kind: GotoDefinitionKind,
19759 cx: &mut App,
19760 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19761
19762 fn range_for_rename(
19763 &self,
19764 buffer: &Entity<Buffer>,
19765 position: text::Anchor,
19766 cx: &mut App,
19767 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19768
19769 fn perform_rename(
19770 &self,
19771 buffer: &Entity<Buffer>,
19772 position: text::Anchor,
19773 new_name: String,
19774 cx: &mut App,
19775 ) -> Option<Task<Result<ProjectTransaction>>>;
19776}
19777
19778pub trait CompletionProvider {
19779 fn completions(
19780 &self,
19781 excerpt_id: ExcerptId,
19782 buffer: &Entity<Buffer>,
19783 buffer_position: text::Anchor,
19784 trigger: CompletionContext,
19785 window: &mut Window,
19786 cx: &mut Context<Editor>,
19787 ) -> Task<Result<Option<Vec<Completion>>>>;
19788
19789 fn resolve_completions(
19790 &self,
19791 buffer: Entity<Buffer>,
19792 completion_indices: Vec<usize>,
19793 completions: Rc<RefCell<Box<[Completion]>>>,
19794 cx: &mut Context<Editor>,
19795 ) -> Task<Result<bool>>;
19796
19797 fn apply_additional_edits_for_completion(
19798 &self,
19799 _buffer: Entity<Buffer>,
19800 _completions: Rc<RefCell<Box<[Completion]>>>,
19801 _completion_index: usize,
19802 _push_to_history: bool,
19803 _cx: &mut Context<Editor>,
19804 ) -> Task<Result<Option<language::Transaction>>> {
19805 Task::ready(Ok(None))
19806 }
19807
19808 fn is_completion_trigger(
19809 &self,
19810 buffer: &Entity<Buffer>,
19811 position: language::Anchor,
19812 text: &str,
19813 trigger_in_words: bool,
19814 cx: &mut Context<Editor>,
19815 ) -> bool;
19816
19817 fn sort_completions(&self) -> bool {
19818 true
19819 }
19820
19821 fn filter_completions(&self) -> bool {
19822 true
19823 }
19824}
19825
19826pub trait CodeActionProvider {
19827 fn id(&self) -> Arc<str>;
19828
19829 fn code_actions(
19830 &self,
19831 buffer: &Entity<Buffer>,
19832 range: Range<text::Anchor>,
19833 window: &mut Window,
19834 cx: &mut App,
19835 ) -> Task<Result<Vec<CodeAction>>>;
19836
19837 fn apply_code_action(
19838 &self,
19839 buffer_handle: Entity<Buffer>,
19840 action: CodeAction,
19841 excerpt_id: ExcerptId,
19842 push_to_history: bool,
19843 window: &mut Window,
19844 cx: &mut App,
19845 ) -> Task<Result<ProjectTransaction>>;
19846}
19847
19848impl CodeActionProvider for Entity<Project> {
19849 fn id(&self) -> Arc<str> {
19850 "project".into()
19851 }
19852
19853 fn code_actions(
19854 &self,
19855 buffer: &Entity<Buffer>,
19856 range: Range<text::Anchor>,
19857 _window: &mut Window,
19858 cx: &mut App,
19859 ) -> Task<Result<Vec<CodeAction>>> {
19860 self.update(cx, |project, cx| {
19861 let code_lens = project.code_lens(buffer, range.clone(), cx);
19862 let code_actions = project.code_actions(buffer, range, None, cx);
19863 cx.background_spawn(async move {
19864 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19865 Ok(code_lens
19866 .context("code lens fetch")?
19867 .into_iter()
19868 .chain(code_actions.context("code action fetch")?)
19869 .collect())
19870 })
19871 })
19872 }
19873
19874 fn apply_code_action(
19875 &self,
19876 buffer_handle: Entity<Buffer>,
19877 action: CodeAction,
19878 _excerpt_id: ExcerptId,
19879 push_to_history: bool,
19880 _window: &mut Window,
19881 cx: &mut App,
19882 ) -> Task<Result<ProjectTransaction>> {
19883 self.update(cx, |project, cx| {
19884 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19885 })
19886 }
19887}
19888
19889fn snippet_completions(
19890 project: &Project,
19891 buffer: &Entity<Buffer>,
19892 buffer_position: text::Anchor,
19893 cx: &mut App,
19894) -> Task<Result<Vec<Completion>>> {
19895 let languages = buffer.read(cx).languages_at(buffer_position);
19896 let snippet_store = project.snippets().read(cx);
19897
19898 let scopes: Vec<_> = languages
19899 .iter()
19900 .filter_map(|language| {
19901 let language_name = language.lsp_id();
19902 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19903
19904 if snippets.is_empty() {
19905 None
19906 } else {
19907 Some((language.default_scope(), snippets))
19908 }
19909 })
19910 .collect();
19911
19912 if scopes.is_empty() {
19913 return Task::ready(Ok(vec![]));
19914 }
19915
19916 let snapshot = buffer.read(cx).text_snapshot();
19917 let chars: String = snapshot
19918 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19919 .collect();
19920 let executor = cx.background_executor().clone();
19921
19922 cx.background_spawn(async move {
19923 let mut all_results: Vec<Completion> = Vec::new();
19924 for (scope, snippets) in scopes.into_iter() {
19925 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19926 let mut last_word = chars
19927 .chars()
19928 .take_while(|c| classifier.is_word(*c))
19929 .collect::<String>();
19930 last_word = last_word.chars().rev().collect();
19931
19932 if last_word.is_empty() {
19933 return Ok(vec![]);
19934 }
19935
19936 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19937 let to_lsp = |point: &text::Anchor| {
19938 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19939 point_to_lsp(end)
19940 };
19941 let lsp_end = to_lsp(&buffer_position);
19942
19943 let candidates = snippets
19944 .iter()
19945 .enumerate()
19946 .flat_map(|(ix, snippet)| {
19947 snippet
19948 .prefix
19949 .iter()
19950 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19951 })
19952 .collect::<Vec<StringMatchCandidate>>();
19953
19954 let mut matches = fuzzy::match_strings(
19955 &candidates,
19956 &last_word,
19957 last_word.chars().any(|c| c.is_uppercase()),
19958 100,
19959 &Default::default(),
19960 executor.clone(),
19961 )
19962 .await;
19963
19964 // Remove all candidates where the query's start does not match the start of any word in the candidate
19965 if let Some(query_start) = last_word.chars().next() {
19966 matches.retain(|string_match| {
19967 split_words(&string_match.string).any(|word| {
19968 // Check that the first codepoint of the word as lowercase matches the first
19969 // codepoint of the query as lowercase
19970 word.chars()
19971 .flat_map(|codepoint| codepoint.to_lowercase())
19972 .zip(query_start.to_lowercase())
19973 .all(|(word_cp, query_cp)| word_cp == query_cp)
19974 })
19975 });
19976 }
19977
19978 let matched_strings = matches
19979 .into_iter()
19980 .map(|m| m.string)
19981 .collect::<HashSet<_>>();
19982
19983 let mut result: Vec<Completion> = snippets
19984 .iter()
19985 .filter_map(|snippet| {
19986 let matching_prefix = snippet
19987 .prefix
19988 .iter()
19989 .find(|prefix| matched_strings.contains(*prefix))?;
19990 let start = as_offset - last_word.len();
19991 let start = snapshot.anchor_before(start);
19992 let range = start..buffer_position;
19993 let lsp_start = to_lsp(&start);
19994 let lsp_range = lsp::Range {
19995 start: lsp_start,
19996 end: lsp_end,
19997 };
19998 Some(Completion {
19999 replace_range: range,
20000 new_text: snippet.body.clone(),
20001 source: CompletionSource::Lsp {
20002 insert_range: None,
20003 server_id: LanguageServerId(usize::MAX),
20004 resolved: true,
20005 lsp_completion: Box::new(lsp::CompletionItem {
20006 label: snippet.prefix.first().unwrap().clone(),
20007 kind: Some(CompletionItemKind::SNIPPET),
20008 label_details: snippet.description.as_ref().map(|description| {
20009 lsp::CompletionItemLabelDetails {
20010 detail: Some(description.clone()),
20011 description: None,
20012 }
20013 }),
20014 insert_text_format: Some(InsertTextFormat::SNIPPET),
20015 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20016 lsp::InsertReplaceEdit {
20017 new_text: snippet.body.clone(),
20018 insert: lsp_range,
20019 replace: lsp_range,
20020 },
20021 )),
20022 filter_text: Some(snippet.body.clone()),
20023 sort_text: Some(char::MAX.to_string()),
20024 ..lsp::CompletionItem::default()
20025 }),
20026 lsp_defaults: None,
20027 },
20028 label: CodeLabel {
20029 text: matching_prefix.clone(),
20030 runs: Vec::new(),
20031 filter_range: 0..matching_prefix.len(),
20032 },
20033 icon_path: None,
20034 documentation: Some(
20035 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20036 single_line: snippet.name.clone().into(),
20037 plain_text: snippet
20038 .description
20039 .clone()
20040 .map(|description| description.into()),
20041 },
20042 ),
20043 insert_text_mode: None,
20044 confirm: None,
20045 })
20046 })
20047 .collect();
20048
20049 all_results.append(&mut result);
20050 }
20051
20052 Ok(all_results)
20053 })
20054}
20055
20056impl CompletionProvider for Entity<Project> {
20057 fn completions(
20058 &self,
20059 _excerpt_id: ExcerptId,
20060 buffer: &Entity<Buffer>,
20061 buffer_position: text::Anchor,
20062 options: CompletionContext,
20063 _window: &mut Window,
20064 cx: &mut Context<Editor>,
20065 ) -> Task<Result<Option<Vec<Completion>>>> {
20066 self.update(cx, |project, cx| {
20067 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20068 let project_completions = project.completions(buffer, buffer_position, options, cx);
20069 cx.background_spawn(async move {
20070 let snippets_completions = snippets.await?;
20071 match project_completions.await? {
20072 Some(mut completions) => {
20073 completions.extend(snippets_completions);
20074 Ok(Some(completions))
20075 }
20076 None => {
20077 if snippets_completions.is_empty() {
20078 Ok(None)
20079 } else {
20080 Ok(Some(snippets_completions))
20081 }
20082 }
20083 }
20084 })
20085 })
20086 }
20087
20088 fn resolve_completions(
20089 &self,
20090 buffer: Entity<Buffer>,
20091 completion_indices: Vec<usize>,
20092 completions: Rc<RefCell<Box<[Completion]>>>,
20093 cx: &mut Context<Editor>,
20094 ) -> Task<Result<bool>> {
20095 self.update(cx, |project, cx| {
20096 project.lsp_store().update(cx, |lsp_store, cx| {
20097 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20098 })
20099 })
20100 }
20101
20102 fn apply_additional_edits_for_completion(
20103 &self,
20104 buffer: Entity<Buffer>,
20105 completions: Rc<RefCell<Box<[Completion]>>>,
20106 completion_index: usize,
20107 push_to_history: bool,
20108 cx: &mut Context<Editor>,
20109 ) -> Task<Result<Option<language::Transaction>>> {
20110 self.update(cx, |project, cx| {
20111 project.lsp_store().update(cx, |lsp_store, cx| {
20112 lsp_store.apply_additional_edits_for_completion(
20113 buffer,
20114 completions,
20115 completion_index,
20116 push_to_history,
20117 cx,
20118 )
20119 })
20120 })
20121 }
20122
20123 fn is_completion_trigger(
20124 &self,
20125 buffer: &Entity<Buffer>,
20126 position: language::Anchor,
20127 text: &str,
20128 trigger_in_words: bool,
20129 cx: &mut Context<Editor>,
20130 ) -> bool {
20131 let mut chars = text.chars();
20132 let char = if let Some(char) = chars.next() {
20133 char
20134 } else {
20135 return false;
20136 };
20137 if chars.next().is_some() {
20138 return false;
20139 }
20140
20141 let buffer = buffer.read(cx);
20142 let snapshot = buffer.snapshot();
20143 if !snapshot.settings_at(position, cx).show_completions_on_input {
20144 return false;
20145 }
20146 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20147 if trigger_in_words && classifier.is_word(char) {
20148 return true;
20149 }
20150
20151 buffer.completion_triggers().contains(text)
20152 }
20153}
20154
20155impl SemanticsProvider for Entity<Project> {
20156 fn hover(
20157 &self,
20158 buffer: &Entity<Buffer>,
20159 position: text::Anchor,
20160 cx: &mut App,
20161 ) -> Option<Task<Vec<project::Hover>>> {
20162 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20163 }
20164
20165 fn document_highlights(
20166 &self,
20167 buffer: &Entity<Buffer>,
20168 position: text::Anchor,
20169 cx: &mut App,
20170 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20171 Some(self.update(cx, |project, cx| {
20172 project.document_highlights(buffer, position, cx)
20173 }))
20174 }
20175
20176 fn definitions(
20177 &self,
20178 buffer: &Entity<Buffer>,
20179 position: text::Anchor,
20180 kind: GotoDefinitionKind,
20181 cx: &mut App,
20182 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20183 Some(self.update(cx, |project, cx| match kind {
20184 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20185 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20186 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20187 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20188 }))
20189 }
20190
20191 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20192 // TODO: make this work for remote projects
20193 self.update(cx, |project, cx| {
20194 if project
20195 .active_debug_session(cx)
20196 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20197 {
20198 return true;
20199 }
20200
20201 buffer.update(cx, |buffer, cx| {
20202 project.any_language_server_supports_inlay_hints(buffer, cx)
20203 })
20204 })
20205 }
20206
20207 fn inline_values(
20208 &self,
20209 buffer_handle: Entity<Buffer>,
20210 range: Range<text::Anchor>,
20211 cx: &mut App,
20212 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20213 self.update(cx, |project, cx| {
20214 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20215
20216 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20217 })
20218 }
20219
20220 fn inlay_hints(
20221 &self,
20222 buffer_handle: Entity<Buffer>,
20223 range: Range<text::Anchor>,
20224 cx: &mut App,
20225 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20226 Some(self.update(cx, |project, cx| {
20227 project.inlay_hints(buffer_handle, range, cx)
20228 }))
20229 }
20230
20231 fn resolve_inlay_hint(
20232 &self,
20233 hint: InlayHint,
20234 buffer_handle: Entity<Buffer>,
20235 server_id: LanguageServerId,
20236 cx: &mut App,
20237 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20238 Some(self.update(cx, |project, cx| {
20239 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20240 }))
20241 }
20242
20243 fn range_for_rename(
20244 &self,
20245 buffer: &Entity<Buffer>,
20246 position: text::Anchor,
20247 cx: &mut App,
20248 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20249 Some(self.update(cx, |project, cx| {
20250 let buffer = buffer.clone();
20251 let task = project.prepare_rename(buffer.clone(), position, cx);
20252 cx.spawn(async move |_, cx| {
20253 Ok(match task.await? {
20254 PrepareRenameResponse::Success(range) => Some(range),
20255 PrepareRenameResponse::InvalidPosition => None,
20256 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20257 // Fallback on using TreeSitter info to determine identifier range
20258 buffer.update(cx, |buffer, _| {
20259 let snapshot = buffer.snapshot();
20260 let (range, kind) = snapshot.surrounding_word(position);
20261 if kind != Some(CharKind::Word) {
20262 return None;
20263 }
20264 Some(
20265 snapshot.anchor_before(range.start)
20266 ..snapshot.anchor_after(range.end),
20267 )
20268 })?
20269 }
20270 })
20271 })
20272 }))
20273 }
20274
20275 fn perform_rename(
20276 &self,
20277 buffer: &Entity<Buffer>,
20278 position: text::Anchor,
20279 new_name: String,
20280 cx: &mut App,
20281 ) -> Option<Task<Result<ProjectTransaction>>> {
20282 Some(self.update(cx, |project, cx| {
20283 project.perform_rename(buffer.clone(), position, new_name, cx)
20284 }))
20285 }
20286}
20287
20288fn inlay_hint_settings(
20289 location: Anchor,
20290 snapshot: &MultiBufferSnapshot,
20291 cx: &mut Context<Editor>,
20292) -> InlayHintSettings {
20293 let file = snapshot.file_at(location);
20294 let language = snapshot.language_at(location).map(|l| l.name());
20295 language_settings(language, file, cx).inlay_hints
20296}
20297
20298fn consume_contiguous_rows(
20299 contiguous_row_selections: &mut Vec<Selection<Point>>,
20300 selection: &Selection<Point>,
20301 display_map: &DisplaySnapshot,
20302 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20303) -> (MultiBufferRow, MultiBufferRow) {
20304 contiguous_row_selections.push(selection.clone());
20305 let start_row = MultiBufferRow(selection.start.row);
20306 let mut end_row = ending_row(selection, display_map);
20307
20308 while let Some(next_selection) = selections.peek() {
20309 if next_selection.start.row <= end_row.0 {
20310 end_row = ending_row(next_selection, display_map);
20311 contiguous_row_selections.push(selections.next().unwrap().clone());
20312 } else {
20313 break;
20314 }
20315 }
20316 (start_row, end_row)
20317}
20318
20319fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20320 if next_selection.end.column > 0 || next_selection.is_empty() {
20321 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20322 } else {
20323 MultiBufferRow(next_selection.end.row)
20324 }
20325}
20326
20327impl EditorSnapshot {
20328 pub fn remote_selections_in_range<'a>(
20329 &'a self,
20330 range: &'a Range<Anchor>,
20331 collaboration_hub: &dyn CollaborationHub,
20332 cx: &'a App,
20333 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20334 let participant_names = collaboration_hub.user_names(cx);
20335 let participant_indices = collaboration_hub.user_participant_indices(cx);
20336 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20337 let collaborators_by_replica_id = collaborators_by_peer_id
20338 .values()
20339 .map(|collaborator| (collaborator.replica_id, collaborator))
20340 .collect::<HashMap<_, _>>();
20341 self.buffer_snapshot
20342 .selections_in_range(range, false)
20343 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20344 if replica_id == AGENT_REPLICA_ID {
20345 Some(RemoteSelection {
20346 replica_id,
20347 selection,
20348 cursor_shape,
20349 line_mode,
20350 collaborator_id: CollaboratorId::Agent,
20351 user_name: Some("Agent".into()),
20352 color: cx.theme().players().agent(),
20353 })
20354 } else {
20355 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20356 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20357 let user_name = participant_names.get(&collaborator.user_id).cloned();
20358 Some(RemoteSelection {
20359 replica_id,
20360 selection,
20361 cursor_shape,
20362 line_mode,
20363 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20364 user_name,
20365 color: if let Some(index) = participant_index {
20366 cx.theme().players().color_for_participant(index.0)
20367 } else {
20368 cx.theme().players().absent()
20369 },
20370 })
20371 }
20372 })
20373 }
20374
20375 pub fn hunks_for_ranges(
20376 &self,
20377 ranges: impl IntoIterator<Item = Range<Point>>,
20378 ) -> Vec<MultiBufferDiffHunk> {
20379 let mut hunks = Vec::new();
20380 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20381 HashMap::default();
20382 for query_range in ranges {
20383 let query_rows =
20384 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20385 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20386 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20387 ) {
20388 // Include deleted hunks that are adjacent to the query range, because
20389 // otherwise they would be missed.
20390 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20391 if hunk.status().is_deleted() {
20392 intersects_range |= hunk.row_range.start == query_rows.end;
20393 intersects_range |= hunk.row_range.end == query_rows.start;
20394 }
20395 if intersects_range {
20396 if !processed_buffer_rows
20397 .entry(hunk.buffer_id)
20398 .or_default()
20399 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20400 {
20401 continue;
20402 }
20403 hunks.push(hunk);
20404 }
20405 }
20406 }
20407
20408 hunks
20409 }
20410
20411 fn display_diff_hunks_for_rows<'a>(
20412 &'a self,
20413 display_rows: Range<DisplayRow>,
20414 folded_buffers: &'a HashSet<BufferId>,
20415 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20416 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20417 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20418
20419 self.buffer_snapshot
20420 .diff_hunks_in_range(buffer_start..buffer_end)
20421 .filter_map(|hunk| {
20422 if folded_buffers.contains(&hunk.buffer_id) {
20423 return None;
20424 }
20425
20426 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20427 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20428
20429 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20430 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20431
20432 let display_hunk = if hunk_display_start.column() != 0 {
20433 DisplayDiffHunk::Folded {
20434 display_row: hunk_display_start.row(),
20435 }
20436 } else {
20437 let mut end_row = hunk_display_end.row();
20438 if hunk_display_end.column() > 0 {
20439 end_row.0 += 1;
20440 }
20441 let is_created_file = hunk.is_created_file();
20442 DisplayDiffHunk::Unfolded {
20443 status: hunk.status(),
20444 diff_base_byte_range: hunk.diff_base_byte_range,
20445 display_row_range: hunk_display_start.row()..end_row,
20446 multi_buffer_range: Anchor::range_in_buffer(
20447 hunk.excerpt_id,
20448 hunk.buffer_id,
20449 hunk.buffer_range,
20450 ),
20451 is_created_file,
20452 }
20453 };
20454
20455 Some(display_hunk)
20456 })
20457 }
20458
20459 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20460 self.display_snapshot.buffer_snapshot.language_at(position)
20461 }
20462
20463 pub fn is_focused(&self) -> bool {
20464 self.is_focused
20465 }
20466
20467 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20468 self.placeholder_text.as_ref()
20469 }
20470
20471 pub fn scroll_position(&self) -> gpui::Point<f32> {
20472 self.scroll_anchor.scroll_position(&self.display_snapshot)
20473 }
20474
20475 fn gutter_dimensions(
20476 &self,
20477 font_id: FontId,
20478 font_size: Pixels,
20479 max_line_number_width: Pixels,
20480 cx: &App,
20481 ) -> Option<GutterDimensions> {
20482 if !self.show_gutter {
20483 return None;
20484 }
20485
20486 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20487 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20488
20489 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20490 matches!(
20491 ProjectSettings::get_global(cx).git.git_gutter,
20492 Some(GitGutterSetting::TrackedFiles)
20493 )
20494 });
20495 let gutter_settings = EditorSettings::get_global(cx).gutter;
20496 let show_line_numbers = self
20497 .show_line_numbers
20498 .unwrap_or(gutter_settings.line_numbers);
20499 let line_gutter_width = if show_line_numbers {
20500 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20501 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20502 max_line_number_width.max(min_width_for_number_on_gutter)
20503 } else {
20504 0.0.into()
20505 };
20506
20507 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20508 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20509
20510 let git_blame_entries_width =
20511 self.git_blame_gutter_max_author_length
20512 .map(|max_author_length| {
20513 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20514 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20515
20516 /// The number of characters to dedicate to gaps and margins.
20517 const SPACING_WIDTH: usize = 4;
20518
20519 let max_char_count = max_author_length.min(renderer.max_author_length())
20520 + ::git::SHORT_SHA_LENGTH
20521 + MAX_RELATIVE_TIMESTAMP.len()
20522 + SPACING_WIDTH;
20523
20524 em_advance * max_char_count
20525 });
20526
20527 let is_singleton = self.buffer_snapshot.is_singleton();
20528
20529 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20530 left_padding += if !is_singleton {
20531 em_width * 4.0
20532 } else if show_runnables || show_breakpoints {
20533 em_width * 3.0
20534 } else if show_git_gutter && show_line_numbers {
20535 em_width * 2.0
20536 } else if show_git_gutter || show_line_numbers {
20537 em_width
20538 } else {
20539 px(0.)
20540 };
20541
20542 let shows_folds = is_singleton && gutter_settings.folds;
20543
20544 let right_padding = if shows_folds && show_line_numbers {
20545 em_width * 4.0
20546 } else if shows_folds || (!is_singleton && show_line_numbers) {
20547 em_width * 3.0
20548 } else if show_line_numbers {
20549 em_width
20550 } else {
20551 px(0.)
20552 };
20553
20554 Some(GutterDimensions {
20555 left_padding,
20556 right_padding,
20557 width: line_gutter_width + left_padding + right_padding,
20558 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20559 git_blame_entries_width,
20560 })
20561 }
20562
20563 pub fn render_crease_toggle(
20564 &self,
20565 buffer_row: MultiBufferRow,
20566 row_contains_cursor: bool,
20567 editor: Entity<Editor>,
20568 window: &mut Window,
20569 cx: &mut App,
20570 ) -> Option<AnyElement> {
20571 let folded = self.is_line_folded(buffer_row);
20572 let mut is_foldable = false;
20573
20574 if let Some(crease) = self
20575 .crease_snapshot
20576 .query_row(buffer_row, &self.buffer_snapshot)
20577 {
20578 is_foldable = true;
20579 match crease {
20580 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20581 if let Some(render_toggle) = render_toggle {
20582 let toggle_callback =
20583 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20584 if folded {
20585 editor.update(cx, |editor, cx| {
20586 editor.fold_at(buffer_row, window, cx)
20587 });
20588 } else {
20589 editor.update(cx, |editor, cx| {
20590 editor.unfold_at(buffer_row, window, cx)
20591 });
20592 }
20593 });
20594 return Some((render_toggle)(
20595 buffer_row,
20596 folded,
20597 toggle_callback,
20598 window,
20599 cx,
20600 ));
20601 }
20602 }
20603 }
20604 }
20605
20606 is_foldable |= self.starts_indent(buffer_row);
20607
20608 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20609 Some(
20610 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20611 .toggle_state(folded)
20612 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20613 if folded {
20614 this.unfold_at(buffer_row, window, cx);
20615 } else {
20616 this.fold_at(buffer_row, window, cx);
20617 }
20618 }))
20619 .into_any_element(),
20620 )
20621 } else {
20622 None
20623 }
20624 }
20625
20626 pub fn render_crease_trailer(
20627 &self,
20628 buffer_row: MultiBufferRow,
20629 window: &mut Window,
20630 cx: &mut App,
20631 ) -> Option<AnyElement> {
20632 let folded = self.is_line_folded(buffer_row);
20633 if let Crease::Inline { render_trailer, .. } = self
20634 .crease_snapshot
20635 .query_row(buffer_row, &self.buffer_snapshot)?
20636 {
20637 let render_trailer = render_trailer.as_ref()?;
20638 Some(render_trailer(buffer_row, folded, window, cx))
20639 } else {
20640 None
20641 }
20642 }
20643}
20644
20645impl Deref for EditorSnapshot {
20646 type Target = DisplaySnapshot;
20647
20648 fn deref(&self) -> &Self::Target {
20649 &self.display_snapshot
20650 }
20651}
20652
20653#[derive(Clone, Debug, PartialEq, Eq)]
20654pub enum EditorEvent {
20655 InputIgnored {
20656 text: Arc<str>,
20657 },
20658 InputHandled {
20659 utf16_range_to_replace: Option<Range<isize>>,
20660 text: Arc<str>,
20661 },
20662 ExcerptsAdded {
20663 buffer: Entity<Buffer>,
20664 predecessor: ExcerptId,
20665 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20666 },
20667 ExcerptsRemoved {
20668 ids: Vec<ExcerptId>,
20669 removed_buffer_ids: Vec<BufferId>,
20670 },
20671 BufferFoldToggled {
20672 ids: Vec<ExcerptId>,
20673 folded: bool,
20674 },
20675 ExcerptsEdited {
20676 ids: Vec<ExcerptId>,
20677 },
20678 ExcerptsExpanded {
20679 ids: Vec<ExcerptId>,
20680 },
20681 BufferEdited,
20682 Edited {
20683 transaction_id: clock::Lamport,
20684 },
20685 Reparsed(BufferId),
20686 Focused,
20687 FocusedIn,
20688 Blurred,
20689 DirtyChanged,
20690 Saved,
20691 TitleChanged,
20692 DiffBaseChanged,
20693 SelectionsChanged {
20694 local: bool,
20695 },
20696 ScrollPositionChanged {
20697 local: bool,
20698 autoscroll: bool,
20699 },
20700 Closed,
20701 TransactionUndone {
20702 transaction_id: clock::Lamport,
20703 },
20704 TransactionBegun {
20705 transaction_id: clock::Lamport,
20706 },
20707 Reloaded,
20708 CursorShapeChanged,
20709 PushedToNavHistory {
20710 anchor: Anchor,
20711 is_deactivate: bool,
20712 },
20713}
20714
20715impl EventEmitter<EditorEvent> for Editor {}
20716
20717impl Focusable for Editor {
20718 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20719 self.focus_handle.clone()
20720 }
20721}
20722
20723impl Render for Editor {
20724 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20725 let settings = ThemeSettings::get_global(cx);
20726
20727 let mut text_style = match self.mode {
20728 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20729 color: cx.theme().colors().editor_foreground,
20730 font_family: settings.ui_font.family.clone(),
20731 font_features: settings.ui_font.features.clone(),
20732 font_fallbacks: settings.ui_font.fallbacks.clone(),
20733 font_size: rems(0.875).into(),
20734 font_weight: settings.ui_font.weight,
20735 line_height: relative(settings.buffer_line_height.value()),
20736 ..Default::default()
20737 },
20738 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20739 color: cx.theme().colors().editor_foreground,
20740 font_family: settings.buffer_font.family.clone(),
20741 font_features: settings.buffer_font.features.clone(),
20742 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20743 font_size: settings.buffer_font_size(cx).into(),
20744 font_weight: settings.buffer_font.weight,
20745 line_height: relative(settings.buffer_line_height.value()),
20746 ..Default::default()
20747 },
20748 };
20749 if let Some(text_style_refinement) = &self.text_style_refinement {
20750 text_style.refine(text_style_refinement)
20751 }
20752
20753 let background = match self.mode {
20754 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20755 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20756 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20757 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20758 };
20759
20760 EditorElement::new(
20761 &cx.entity(),
20762 EditorStyle {
20763 background,
20764 local_player: cx.theme().players().local(),
20765 text: text_style,
20766 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20767 syntax: cx.theme().syntax().clone(),
20768 status: cx.theme().status().clone(),
20769 inlay_hints_style: make_inlay_hints_style(cx),
20770 inline_completion_styles: make_suggestion_styles(cx),
20771 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20772 show_underlines: !self.mode.is_minimap(),
20773 },
20774 )
20775 }
20776}
20777
20778impl EntityInputHandler for Editor {
20779 fn text_for_range(
20780 &mut self,
20781 range_utf16: Range<usize>,
20782 adjusted_range: &mut Option<Range<usize>>,
20783 _: &mut Window,
20784 cx: &mut Context<Self>,
20785 ) -> Option<String> {
20786 let snapshot = self.buffer.read(cx).read(cx);
20787 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20788 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20789 if (start.0..end.0) != range_utf16 {
20790 adjusted_range.replace(start.0..end.0);
20791 }
20792 Some(snapshot.text_for_range(start..end).collect())
20793 }
20794
20795 fn selected_text_range(
20796 &mut self,
20797 ignore_disabled_input: bool,
20798 _: &mut Window,
20799 cx: &mut Context<Self>,
20800 ) -> Option<UTF16Selection> {
20801 // Prevent the IME menu from appearing when holding down an alphabetic key
20802 // while input is disabled.
20803 if !ignore_disabled_input && !self.input_enabled {
20804 return None;
20805 }
20806
20807 let selection = self.selections.newest::<OffsetUtf16>(cx);
20808 let range = selection.range();
20809
20810 Some(UTF16Selection {
20811 range: range.start.0..range.end.0,
20812 reversed: selection.reversed,
20813 })
20814 }
20815
20816 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20817 let snapshot = self.buffer.read(cx).read(cx);
20818 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20819 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20820 }
20821
20822 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20823 self.clear_highlights::<InputComposition>(cx);
20824 self.ime_transaction.take();
20825 }
20826
20827 fn replace_text_in_range(
20828 &mut self,
20829 range_utf16: Option<Range<usize>>,
20830 text: &str,
20831 window: &mut Window,
20832 cx: &mut Context<Self>,
20833 ) {
20834 if !self.input_enabled {
20835 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20836 return;
20837 }
20838
20839 self.transact(window, cx, |this, window, cx| {
20840 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20841 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20842 Some(this.selection_replacement_ranges(range_utf16, cx))
20843 } else {
20844 this.marked_text_ranges(cx)
20845 };
20846
20847 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20848 let newest_selection_id = this.selections.newest_anchor().id;
20849 this.selections
20850 .all::<OffsetUtf16>(cx)
20851 .iter()
20852 .zip(ranges_to_replace.iter())
20853 .find_map(|(selection, range)| {
20854 if selection.id == newest_selection_id {
20855 Some(
20856 (range.start.0 as isize - selection.head().0 as isize)
20857 ..(range.end.0 as isize - selection.head().0 as isize),
20858 )
20859 } else {
20860 None
20861 }
20862 })
20863 });
20864
20865 cx.emit(EditorEvent::InputHandled {
20866 utf16_range_to_replace: range_to_replace,
20867 text: text.into(),
20868 });
20869
20870 if let Some(new_selected_ranges) = new_selected_ranges {
20871 this.change_selections(None, window, cx, |selections| {
20872 selections.select_ranges(new_selected_ranges)
20873 });
20874 this.backspace(&Default::default(), window, cx);
20875 }
20876
20877 this.handle_input(text, window, cx);
20878 });
20879
20880 if let Some(transaction) = self.ime_transaction {
20881 self.buffer.update(cx, |buffer, cx| {
20882 buffer.group_until_transaction(transaction, cx);
20883 });
20884 }
20885
20886 self.unmark_text(window, cx);
20887 }
20888
20889 fn replace_and_mark_text_in_range(
20890 &mut self,
20891 range_utf16: Option<Range<usize>>,
20892 text: &str,
20893 new_selected_range_utf16: Option<Range<usize>>,
20894 window: &mut Window,
20895 cx: &mut Context<Self>,
20896 ) {
20897 if !self.input_enabled {
20898 return;
20899 }
20900
20901 let transaction = self.transact(window, cx, |this, window, cx| {
20902 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20903 let snapshot = this.buffer.read(cx).read(cx);
20904 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20905 for marked_range in &mut marked_ranges {
20906 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20907 marked_range.start.0 += relative_range_utf16.start;
20908 marked_range.start =
20909 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20910 marked_range.end =
20911 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20912 }
20913 }
20914 Some(marked_ranges)
20915 } else if let Some(range_utf16) = range_utf16 {
20916 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20917 Some(this.selection_replacement_ranges(range_utf16, cx))
20918 } else {
20919 None
20920 };
20921
20922 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20923 let newest_selection_id = this.selections.newest_anchor().id;
20924 this.selections
20925 .all::<OffsetUtf16>(cx)
20926 .iter()
20927 .zip(ranges_to_replace.iter())
20928 .find_map(|(selection, range)| {
20929 if selection.id == newest_selection_id {
20930 Some(
20931 (range.start.0 as isize - selection.head().0 as isize)
20932 ..(range.end.0 as isize - selection.head().0 as isize),
20933 )
20934 } else {
20935 None
20936 }
20937 })
20938 });
20939
20940 cx.emit(EditorEvent::InputHandled {
20941 utf16_range_to_replace: range_to_replace,
20942 text: text.into(),
20943 });
20944
20945 if let Some(ranges) = ranges_to_replace {
20946 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20947 }
20948
20949 let marked_ranges = {
20950 let snapshot = this.buffer.read(cx).read(cx);
20951 this.selections
20952 .disjoint_anchors()
20953 .iter()
20954 .map(|selection| {
20955 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20956 })
20957 .collect::<Vec<_>>()
20958 };
20959
20960 if text.is_empty() {
20961 this.unmark_text(window, cx);
20962 } else {
20963 this.highlight_text::<InputComposition>(
20964 marked_ranges.clone(),
20965 HighlightStyle {
20966 underline: Some(UnderlineStyle {
20967 thickness: px(1.),
20968 color: None,
20969 wavy: false,
20970 }),
20971 ..Default::default()
20972 },
20973 cx,
20974 );
20975 }
20976
20977 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20978 let use_autoclose = this.use_autoclose;
20979 let use_auto_surround = this.use_auto_surround;
20980 this.set_use_autoclose(false);
20981 this.set_use_auto_surround(false);
20982 this.handle_input(text, window, cx);
20983 this.set_use_autoclose(use_autoclose);
20984 this.set_use_auto_surround(use_auto_surround);
20985
20986 if let Some(new_selected_range) = new_selected_range_utf16 {
20987 let snapshot = this.buffer.read(cx).read(cx);
20988 let new_selected_ranges = marked_ranges
20989 .into_iter()
20990 .map(|marked_range| {
20991 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20992 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20993 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20994 snapshot.clip_offset_utf16(new_start, Bias::Left)
20995 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20996 })
20997 .collect::<Vec<_>>();
20998
20999 drop(snapshot);
21000 this.change_selections(None, window, cx, |selections| {
21001 selections.select_ranges(new_selected_ranges)
21002 });
21003 }
21004 });
21005
21006 self.ime_transaction = self.ime_transaction.or(transaction);
21007 if let Some(transaction) = self.ime_transaction {
21008 self.buffer.update(cx, |buffer, cx| {
21009 buffer.group_until_transaction(transaction, cx);
21010 });
21011 }
21012
21013 if self.text_highlights::<InputComposition>(cx).is_none() {
21014 self.ime_transaction.take();
21015 }
21016 }
21017
21018 fn bounds_for_range(
21019 &mut self,
21020 range_utf16: Range<usize>,
21021 element_bounds: gpui::Bounds<Pixels>,
21022 window: &mut Window,
21023 cx: &mut Context<Self>,
21024 ) -> Option<gpui::Bounds<Pixels>> {
21025 let text_layout_details = self.text_layout_details(window);
21026 let gpui::Size {
21027 width: em_width,
21028 height: line_height,
21029 } = self.character_size(window);
21030
21031 let snapshot = self.snapshot(window, cx);
21032 let scroll_position = snapshot.scroll_position();
21033 let scroll_left = scroll_position.x * em_width;
21034
21035 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21036 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21037 + self.gutter_dimensions.width
21038 + self.gutter_dimensions.margin;
21039 let y = line_height * (start.row().as_f32() - scroll_position.y);
21040
21041 Some(Bounds {
21042 origin: element_bounds.origin + point(x, y),
21043 size: size(em_width, line_height),
21044 })
21045 }
21046
21047 fn character_index_for_point(
21048 &mut self,
21049 point: gpui::Point<Pixels>,
21050 _window: &mut Window,
21051 _cx: &mut Context<Self>,
21052 ) -> Option<usize> {
21053 let position_map = self.last_position_map.as_ref()?;
21054 if !position_map.text_hitbox.contains(&point) {
21055 return None;
21056 }
21057 let display_point = position_map.point_for_position(point).previous_valid;
21058 let anchor = position_map
21059 .snapshot
21060 .display_point_to_anchor(display_point, Bias::Left);
21061 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21062 Some(utf16_offset.0)
21063 }
21064}
21065
21066trait SelectionExt {
21067 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21068 fn spanned_rows(
21069 &self,
21070 include_end_if_at_line_start: bool,
21071 map: &DisplaySnapshot,
21072 ) -> Range<MultiBufferRow>;
21073}
21074
21075impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21076 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21077 let start = self
21078 .start
21079 .to_point(&map.buffer_snapshot)
21080 .to_display_point(map);
21081 let end = self
21082 .end
21083 .to_point(&map.buffer_snapshot)
21084 .to_display_point(map);
21085 if self.reversed {
21086 end..start
21087 } else {
21088 start..end
21089 }
21090 }
21091
21092 fn spanned_rows(
21093 &self,
21094 include_end_if_at_line_start: bool,
21095 map: &DisplaySnapshot,
21096 ) -> Range<MultiBufferRow> {
21097 let start = self.start.to_point(&map.buffer_snapshot);
21098 let mut end = self.end.to_point(&map.buffer_snapshot);
21099 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21100 end.row -= 1;
21101 }
21102
21103 let buffer_start = map.prev_line_boundary(start).0;
21104 let buffer_end = map.next_line_boundary(end).0;
21105 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21106 }
21107}
21108
21109impl<T: InvalidationRegion> InvalidationStack<T> {
21110 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21111 where
21112 S: Clone + ToOffset,
21113 {
21114 while let Some(region) = self.last() {
21115 let all_selections_inside_invalidation_ranges =
21116 if selections.len() == region.ranges().len() {
21117 selections
21118 .iter()
21119 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21120 .all(|(selection, invalidation_range)| {
21121 let head = selection.head().to_offset(buffer);
21122 invalidation_range.start <= head && invalidation_range.end >= head
21123 })
21124 } else {
21125 false
21126 };
21127
21128 if all_selections_inside_invalidation_ranges {
21129 break;
21130 } else {
21131 self.pop();
21132 }
21133 }
21134 }
21135}
21136
21137impl<T> Default for InvalidationStack<T> {
21138 fn default() -> Self {
21139 Self(Default::default())
21140 }
21141}
21142
21143impl<T> Deref for InvalidationStack<T> {
21144 type Target = Vec<T>;
21145
21146 fn deref(&self) -> &Self::Target {
21147 &self.0
21148 }
21149}
21150
21151impl<T> DerefMut for InvalidationStack<T> {
21152 fn deref_mut(&mut self) -> &mut Self::Target {
21153 &mut self.0
21154 }
21155}
21156
21157impl InvalidationRegion for SnippetState {
21158 fn ranges(&self) -> &[Range<Anchor>] {
21159 &self.ranges[self.active_index]
21160 }
21161}
21162
21163fn inline_completion_edit_text(
21164 current_snapshot: &BufferSnapshot,
21165 edits: &[(Range<Anchor>, String)],
21166 edit_preview: &EditPreview,
21167 include_deletions: bool,
21168 cx: &App,
21169) -> HighlightedText {
21170 let edits = edits
21171 .iter()
21172 .map(|(anchor, text)| {
21173 (
21174 anchor.start.text_anchor..anchor.end.text_anchor,
21175 text.clone(),
21176 )
21177 })
21178 .collect::<Vec<_>>();
21179
21180 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21181}
21182
21183pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21184 match severity {
21185 lsp::DiagnosticSeverity::ERROR => colors.error,
21186 lsp::DiagnosticSeverity::WARNING => colors.warning,
21187 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21188 lsp::DiagnosticSeverity::HINT => colors.info,
21189 _ => colors.ignored,
21190 }
21191}
21192
21193pub fn styled_runs_for_code_label<'a>(
21194 label: &'a CodeLabel,
21195 syntax_theme: &'a theme::SyntaxTheme,
21196) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21197 let fade_out = HighlightStyle {
21198 fade_out: Some(0.35),
21199 ..Default::default()
21200 };
21201
21202 let mut prev_end = label.filter_range.end;
21203 label
21204 .runs
21205 .iter()
21206 .enumerate()
21207 .flat_map(move |(ix, (range, highlight_id))| {
21208 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21209 style
21210 } else {
21211 return Default::default();
21212 };
21213 let mut muted_style = style;
21214 muted_style.highlight(fade_out);
21215
21216 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21217 if range.start >= label.filter_range.end {
21218 if range.start > prev_end {
21219 runs.push((prev_end..range.start, fade_out));
21220 }
21221 runs.push((range.clone(), muted_style));
21222 } else if range.end <= label.filter_range.end {
21223 runs.push((range.clone(), style));
21224 } else {
21225 runs.push((range.start..label.filter_range.end, style));
21226 runs.push((label.filter_range.end..range.end, muted_style));
21227 }
21228 prev_end = cmp::max(prev_end, range.end);
21229
21230 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21231 runs.push((prev_end..label.text.len(), fade_out));
21232 }
21233
21234 runs
21235 })
21236}
21237
21238pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21239 let mut prev_index = 0;
21240 let mut prev_codepoint: Option<char> = None;
21241 text.char_indices()
21242 .chain([(text.len(), '\0')])
21243 .filter_map(move |(index, codepoint)| {
21244 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21245 let is_boundary = index == text.len()
21246 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21247 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21248 if is_boundary {
21249 let chunk = &text[prev_index..index];
21250 prev_index = index;
21251 Some(chunk)
21252 } else {
21253 None
21254 }
21255 })
21256}
21257
21258pub trait RangeToAnchorExt: Sized {
21259 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21260
21261 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21262 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21263 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21264 }
21265}
21266
21267impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21268 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21269 let start_offset = self.start.to_offset(snapshot);
21270 let end_offset = self.end.to_offset(snapshot);
21271 if start_offset == end_offset {
21272 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21273 } else {
21274 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21275 }
21276 }
21277}
21278
21279pub trait RowExt {
21280 fn as_f32(&self) -> f32;
21281
21282 fn next_row(&self) -> Self;
21283
21284 fn previous_row(&self) -> Self;
21285
21286 fn minus(&self, other: Self) -> u32;
21287}
21288
21289impl RowExt for DisplayRow {
21290 fn as_f32(&self) -> f32 {
21291 self.0 as f32
21292 }
21293
21294 fn next_row(&self) -> Self {
21295 Self(self.0 + 1)
21296 }
21297
21298 fn previous_row(&self) -> Self {
21299 Self(self.0.saturating_sub(1))
21300 }
21301
21302 fn minus(&self, other: Self) -> u32 {
21303 self.0 - other.0
21304 }
21305}
21306
21307impl RowExt for MultiBufferRow {
21308 fn as_f32(&self) -> f32 {
21309 self.0 as f32
21310 }
21311
21312 fn next_row(&self) -> Self {
21313 Self(self.0 + 1)
21314 }
21315
21316 fn previous_row(&self) -> Self {
21317 Self(self.0.saturating_sub(1))
21318 }
21319
21320 fn minus(&self, other: Self) -> u32 {
21321 self.0 - other.0
21322 }
21323}
21324
21325trait RowRangeExt {
21326 type Row;
21327
21328 fn len(&self) -> usize;
21329
21330 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21331}
21332
21333impl RowRangeExt for Range<MultiBufferRow> {
21334 type Row = MultiBufferRow;
21335
21336 fn len(&self) -> usize {
21337 (self.end.0 - self.start.0) as usize
21338 }
21339
21340 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21341 (self.start.0..self.end.0).map(MultiBufferRow)
21342 }
21343}
21344
21345impl RowRangeExt for Range<DisplayRow> {
21346 type Row = DisplayRow;
21347
21348 fn len(&self) -> usize {
21349 (self.end.0 - self.start.0) as usize
21350 }
21351
21352 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21353 (self.start.0..self.end.0).map(DisplayRow)
21354 }
21355}
21356
21357/// If select range has more than one line, we
21358/// just point the cursor to range.start.
21359fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21360 if range.start.row == range.end.row {
21361 range
21362 } else {
21363 range.start..range.start
21364 }
21365}
21366pub struct KillRing(ClipboardItem);
21367impl Global for KillRing {}
21368
21369const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21370
21371enum BreakpointPromptEditAction {
21372 Log,
21373 Condition,
21374 HitCondition,
21375}
21376
21377struct BreakpointPromptEditor {
21378 pub(crate) prompt: Entity<Editor>,
21379 editor: WeakEntity<Editor>,
21380 breakpoint_anchor: Anchor,
21381 breakpoint: Breakpoint,
21382 edit_action: BreakpointPromptEditAction,
21383 block_ids: HashSet<CustomBlockId>,
21384 editor_margins: Arc<Mutex<EditorMargins>>,
21385 _subscriptions: Vec<Subscription>,
21386}
21387
21388impl BreakpointPromptEditor {
21389 const MAX_LINES: u8 = 4;
21390
21391 fn new(
21392 editor: WeakEntity<Editor>,
21393 breakpoint_anchor: Anchor,
21394 breakpoint: Breakpoint,
21395 edit_action: BreakpointPromptEditAction,
21396 window: &mut Window,
21397 cx: &mut Context<Self>,
21398 ) -> Self {
21399 let base_text = match edit_action {
21400 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21401 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21402 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21403 }
21404 .map(|msg| msg.to_string())
21405 .unwrap_or_default();
21406
21407 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21408 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21409
21410 let prompt = cx.new(|cx| {
21411 let mut prompt = Editor::new(
21412 EditorMode::AutoHeight {
21413 max_lines: Self::MAX_LINES as usize,
21414 },
21415 buffer,
21416 None,
21417 window,
21418 cx,
21419 );
21420 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21421 prompt.set_show_cursor_when_unfocused(false, cx);
21422 prompt.set_placeholder_text(
21423 match edit_action {
21424 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21425 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21426 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21427 },
21428 cx,
21429 );
21430
21431 prompt
21432 });
21433
21434 Self {
21435 prompt,
21436 editor,
21437 breakpoint_anchor,
21438 breakpoint,
21439 edit_action,
21440 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21441 block_ids: Default::default(),
21442 _subscriptions: vec![],
21443 }
21444 }
21445
21446 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21447 self.block_ids.extend(block_ids)
21448 }
21449
21450 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21451 if let Some(editor) = self.editor.upgrade() {
21452 let message = self
21453 .prompt
21454 .read(cx)
21455 .buffer
21456 .read(cx)
21457 .as_singleton()
21458 .expect("A multi buffer in breakpoint prompt isn't possible")
21459 .read(cx)
21460 .as_rope()
21461 .to_string();
21462
21463 editor.update(cx, |editor, cx| {
21464 editor.edit_breakpoint_at_anchor(
21465 self.breakpoint_anchor,
21466 self.breakpoint.clone(),
21467 match self.edit_action {
21468 BreakpointPromptEditAction::Log => {
21469 BreakpointEditAction::EditLogMessage(message.into())
21470 }
21471 BreakpointPromptEditAction::Condition => {
21472 BreakpointEditAction::EditCondition(message.into())
21473 }
21474 BreakpointPromptEditAction::HitCondition => {
21475 BreakpointEditAction::EditHitCondition(message.into())
21476 }
21477 },
21478 cx,
21479 );
21480
21481 editor.remove_blocks(self.block_ids.clone(), None, cx);
21482 cx.focus_self(window);
21483 });
21484 }
21485 }
21486
21487 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21488 self.editor
21489 .update(cx, |editor, cx| {
21490 editor.remove_blocks(self.block_ids.clone(), None, cx);
21491 window.focus(&editor.focus_handle);
21492 })
21493 .log_err();
21494 }
21495
21496 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21497 let settings = ThemeSettings::get_global(cx);
21498 let text_style = TextStyle {
21499 color: if self.prompt.read(cx).read_only(cx) {
21500 cx.theme().colors().text_disabled
21501 } else {
21502 cx.theme().colors().text
21503 },
21504 font_family: settings.buffer_font.family.clone(),
21505 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21506 font_size: settings.buffer_font_size(cx).into(),
21507 font_weight: settings.buffer_font.weight,
21508 line_height: relative(settings.buffer_line_height.value()),
21509 ..Default::default()
21510 };
21511 EditorElement::new(
21512 &self.prompt,
21513 EditorStyle {
21514 background: cx.theme().colors().editor_background,
21515 local_player: cx.theme().players().local(),
21516 text: text_style,
21517 ..Default::default()
21518 },
21519 )
21520 }
21521}
21522
21523impl Render for BreakpointPromptEditor {
21524 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21525 let editor_margins = *self.editor_margins.lock();
21526 let gutter_dimensions = editor_margins.gutter;
21527 h_flex()
21528 .key_context("Editor")
21529 .bg(cx.theme().colors().editor_background)
21530 .border_y_1()
21531 .border_color(cx.theme().status().info_border)
21532 .size_full()
21533 .py(window.line_height() / 2.5)
21534 .on_action(cx.listener(Self::confirm))
21535 .on_action(cx.listener(Self::cancel))
21536 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21537 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21538 }
21539}
21540
21541impl Focusable for BreakpointPromptEditor {
21542 fn focus_handle(&self, cx: &App) -> FocusHandle {
21543 self.prompt.focus_handle(cx)
21544 }
21545}
21546
21547fn all_edits_insertions_or_deletions(
21548 edits: &Vec<(Range<Anchor>, String)>,
21549 snapshot: &MultiBufferSnapshot,
21550) -> bool {
21551 let mut all_insertions = true;
21552 let mut all_deletions = true;
21553
21554 for (range, new_text) in edits.iter() {
21555 let range_is_empty = range.to_offset(&snapshot).is_empty();
21556 let text_is_empty = new_text.is_empty();
21557
21558 if range_is_empty != text_is_empty {
21559 if range_is_empty {
21560 all_deletions = false;
21561 } else {
21562 all_insertions = false;
21563 }
21564 } else {
21565 return false;
21566 }
21567
21568 if !all_insertions && !all_deletions {
21569 return false;
21570 }
21571 }
21572 all_insertions || all_deletions
21573}
21574
21575struct MissingEditPredictionKeybindingTooltip;
21576
21577impl Render for MissingEditPredictionKeybindingTooltip {
21578 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21579 ui::tooltip_container(window, cx, |container, _, cx| {
21580 container
21581 .flex_shrink_0()
21582 .max_w_80()
21583 .min_h(rems_from_px(124.))
21584 .justify_between()
21585 .child(
21586 v_flex()
21587 .flex_1()
21588 .text_ui_sm(cx)
21589 .child(Label::new("Conflict with Accept Keybinding"))
21590 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21591 )
21592 .child(
21593 h_flex()
21594 .pb_1()
21595 .gap_1()
21596 .items_end()
21597 .w_full()
21598 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21599 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21600 }))
21601 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21602 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21603 })),
21604 )
21605 })
21606 }
21607}
21608
21609#[derive(Debug, Clone, Copy, PartialEq)]
21610pub struct LineHighlight {
21611 pub background: Background,
21612 pub border: Option<gpui::Hsla>,
21613 pub include_gutter: bool,
21614 pub type_id: Option<TypeId>,
21615}
21616
21617fn render_diff_hunk_controls(
21618 row: u32,
21619 status: &DiffHunkStatus,
21620 hunk_range: Range<Anchor>,
21621 is_created_file: bool,
21622 line_height: Pixels,
21623 editor: &Entity<Editor>,
21624 _window: &mut Window,
21625 cx: &mut App,
21626) -> AnyElement {
21627 h_flex()
21628 .h(line_height)
21629 .mr_1()
21630 .gap_1()
21631 .px_0p5()
21632 .pb_1()
21633 .border_x_1()
21634 .border_b_1()
21635 .border_color(cx.theme().colors().border_variant)
21636 .rounded_b_lg()
21637 .bg(cx.theme().colors().editor_background)
21638 .gap_1()
21639 .occlude()
21640 .shadow_md()
21641 .child(if status.has_secondary_hunk() {
21642 Button::new(("stage", row as u64), "Stage")
21643 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21644 .tooltip({
21645 let focus_handle = editor.focus_handle(cx);
21646 move |window, cx| {
21647 Tooltip::for_action_in(
21648 "Stage Hunk",
21649 &::git::ToggleStaged,
21650 &focus_handle,
21651 window,
21652 cx,
21653 )
21654 }
21655 })
21656 .on_click({
21657 let editor = editor.clone();
21658 move |_event, _window, cx| {
21659 editor.update(cx, |editor, cx| {
21660 editor.stage_or_unstage_diff_hunks(
21661 true,
21662 vec![hunk_range.start..hunk_range.start],
21663 cx,
21664 );
21665 });
21666 }
21667 })
21668 } else {
21669 Button::new(("unstage", row as u64), "Unstage")
21670 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21671 .tooltip({
21672 let focus_handle = editor.focus_handle(cx);
21673 move |window, cx| {
21674 Tooltip::for_action_in(
21675 "Unstage Hunk",
21676 &::git::ToggleStaged,
21677 &focus_handle,
21678 window,
21679 cx,
21680 )
21681 }
21682 })
21683 .on_click({
21684 let editor = editor.clone();
21685 move |_event, _window, cx| {
21686 editor.update(cx, |editor, cx| {
21687 editor.stage_or_unstage_diff_hunks(
21688 false,
21689 vec![hunk_range.start..hunk_range.start],
21690 cx,
21691 );
21692 });
21693 }
21694 })
21695 })
21696 .child(
21697 Button::new(("restore", row as u64), "Restore")
21698 .tooltip({
21699 let focus_handle = editor.focus_handle(cx);
21700 move |window, cx| {
21701 Tooltip::for_action_in(
21702 "Restore Hunk",
21703 &::git::Restore,
21704 &focus_handle,
21705 window,
21706 cx,
21707 )
21708 }
21709 })
21710 .on_click({
21711 let editor = editor.clone();
21712 move |_event, window, cx| {
21713 editor.update(cx, |editor, cx| {
21714 let snapshot = editor.snapshot(window, cx);
21715 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21716 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21717 });
21718 }
21719 })
21720 .disabled(is_created_file),
21721 )
21722 .when(
21723 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21724 |el| {
21725 el.child(
21726 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21727 .shape(IconButtonShape::Square)
21728 .icon_size(IconSize::Small)
21729 // .disabled(!has_multiple_hunks)
21730 .tooltip({
21731 let focus_handle = editor.focus_handle(cx);
21732 move |window, cx| {
21733 Tooltip::for_action_in(
21734 "Next Hunk",
21735 &GoToHunk,
21736 &focus_handle,
21737 window,
21738 cx,
21739 )
21740 }
21741 })
21742 .on_click({
21743 let editor = editor.clone();
21744 move |_event, window, cx| {
21745 editor.update(cx, |editor, cx| {
21746 let snapshot = editor.snapshot(window, cx);
21747 let position =
21748 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21749 editor.go_to_hunk_before_or_after_position(
21750 &snapshot,
21751 position,
21752 Direction::Next,
21753 window,
21754 cx,
21755 );
21756 editor.expand_selected_diff_hunks(cx);
21757 });
21758 }
21759 }),
21760 )
21761 .child(
21762 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21763 .shape(IconButtonShape::Square)
21764 .icon_size(IconSize::Small)
21765 // .disabled(!has_multiple_hunks)
21766 .tooltip({
21767 let focus_handle = editor.focus_handle(cx);
21768 move |window, cx| {
21769 Tooltip::for_action_in(
21770 "Previous Hunk",
21771 &GoToPreviousHunk,
21772 &focus_handle,
21773 window,
21774 cx,
21775 )
21776 }
21777 })
21778 .on_click({
21779 let editor = editor.clone();
21780 move |_event, window, cx| {
21781 editor.update(cx, |editor, cx| {
21782 let snapshot = editor.snapshot(window, cx);
21783 let point =
21784 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21785 editor.go_to_hunk_before_or_after_position(
21786 &snapshot,
21787 point,
21788 Direction::Prev,
21789 window,
21790 cx,
21791 );
21792 editor.expand_selected_diff_hunks(cx);
21793 });
21794 }
21795 }),
21796 )
21797 },
21798 )
21799 .into_any_element()
21800}