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 num_of_whitespaces = snapshot
3958 .chars_for_range(range.clone())
3959 .take_while(|c| c.is_whitespace())
3960 .count();
3961 let comment_candidate = snapshot
3962 .chars_for_range(range)
3963 .skip(num_of_whitespaces)
3964 .take(max_len_of_delimiter)
3965 .collect::<String>();
3966 let (delimiter, trimmed_len) =
3967 delimiters.iter().find_map(|delimiter| {
3968 let trimmed = delimiter.trim_end();
3969 if comment_candidate.starts_with(trimmed) {
3970 Some((delimiter, trimmed.len()))
3971 } else {
3972 None
3973 }
3974 })?;
3975 let cursor_is_placed_after_comment_marker =
3976 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3977 if cursor_is_placed_after_comment_marker {
3978 Some(delimiter.clone())
3979 } else {
3980 None
3981 }
3982 });
3983
3984 let mut indent_on_newline = IndentSize::spaces(0);
3985 let mut indent_on_extra_newline = IndentSize::spaces(0);
3986
3987 let doc_delimiter = maybe!({
3988 if !selection_is_empty {
3989 return None;
3990 }
3991
3992 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3993 return None;
3994 }
3995
3996 let DocumentationConfig {
3997 start: start_tag,
3998 end: end_tag,
3999 prefix: delimiter,
4000 tab_size: len,
4001 } = language.documentation()?;
4002
4003 let (snapshot, range) =
4004 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4005
4006 let num_of_whitespaces = snapshot
4007 .chars_for_range(range.clone())
4008 .take_while(|c| c.is_whitespace())
4009 .count();
4010
4011 let cursor_is_after_start_tag = {
4012 let start_tag_len = start_tag.len();
4013 let start_tag_line = snapshot
4014 .chars_for_range(range.clone())
4015 .skip(num_of_whitespaces)
4016 .take(start_tag_len)
4017 .collect::<String>();
4018 if start_tag_line.starts_with(start_tag.as_ref()) {
4019 num_of_whitespaces + start_tag_len
4020 <= start_point.column as usize
4021 } else {
4022 false
4023 }
4024 };
4025
4026 let cursor_is_after_delimiter = {
4027 let delimiter_trim = delimiter.trim_end();
4028 let delimiter_line = snapshot
4029 .chars_for_range(range.clone())
4030 .skip(num_of_whitespaces)
4031 .take(delimiter_trim.len())
4032 .collect::<String>();
4033 if delimiter_line.starts_with(delimiter_trim) {
4034 num_of_whitespaces + delimiter_trim.len()
4035 <= start_point.column as usize
4036 } else {
4037 false
4038 }
4039 };
4040
4041 let cursor_is_before_end_tag_if_exists = {
4042 let num_of_whitespaces_rev = snapshot
4043 .reversed_chars_for_range(range.clone())
4044 .take_while(|c| c.is_whitespace())
4045 .count();
4046 let mut line_iter = snapshot
4047 .reversed_chars_for_range(range)
4048 .skip(num_of_whitespaces_rev);
4049 let end_tag_exists = end_tag
4050 .chars()
4051 .rev()
4052 .all(|char| line_iter.next() == Some(char));
4053 if end_tag_exists {
4054 let max_point = snapshot.line_len(start_point.row) as usize;
4055 let ordering = (num_of_whitespaces_rev
4056 + end_tag.len()
4057 + start_point.column as usize)
4058 .cmp(&max_point);
4059 let cursor_is_before_end_tag =
4060 ordering != Ordering::Greater;
4061 if cursor_is_after_start_tag {
4062 if cursor_is_before_end_tag {
4063 insert_extra_newline = true;
4064 }
4065 let cursor_is_at_start_of_end_tag =
4066 ordering == Ordering::Equal;
4067 if cursor_is_at_start_of_end_tag {
4068 indent_on_extra_newline.len = (*len).into();
4069 }
4070 }
4071 cursor_is_before_end_tag
4072 } else {
4073 true
4074 }
4075 };
4076
4077 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4078 && cursor_is_before_end_tag_if_exists
4079 {
4080 if cursor_is_after_start_tag {
4081 indent_on_newline.len = (*len).into();
4082 }
4083 Some(delimiter.clone())
4084 } else {
4085 None
4086 }
4087 });
4088
4089 (
4090 comment_delimiter,
4091 doc_delimiter,
4092 insert_extra_newline,
4093 indent_on_newline,
4094 indent_on_extra_newline,
4095 )
4096 } else {
4097 (
4098 None,
4099 None,
4100 false,
4101 IndentSize::default(),
4102 IndentSize::default(),
4103 )
4104 };
4105
4106 let prevent_auto_indent = doc_delimiter.is_some();
4107 let delimiter = comment_delimiter.or(doc_delimiter);
4108
4109 let capacity_for_delimiter =
4110 delimiter.as_deref().map(str::len).unwrap_or_default();
4111 let mut new_text = String::with_capacity(
4112 1 + capacity_for_delimiter
4113 + existing_indent.len as usize
4114 + indent_on_newline.len as usize
4115 + indent_on_extra_newline.len as usize,
4116 );
4117 new_text.push('\n');
4118 new_text.extend(existing_indent.chars());
4119 new_text.extend(indent_on_newline.chars());
4120
4121 if let Some(delimiter) = &delimiter {
4122 new_text.push_str(delimiter);
4123 }
4124
4125 if insert_extra_newline {
4126 new_text.push('\n');
4127 new_text.extend(existing_indent.chars());
4128 new_text.extend(indent_on_extra_newline.chars());
4129 }
4130
4131 let anchor = buffer.anchor_after(end);
4132 let new_selection = selection.map(|_| anchor);
4133 (
4134 ((start..end, new_text), prevent_auto_indent),
4135 (insert_extra_newline, new_selection),
4136 )
4137 })
4138 .unzip()
4139 };
4140
4141 let mut auto_indent_edits = Vec::new();
4142 let mut edits = Vec::new();
4143 for (edit, prevent_auto_indent) in edits_with_flags {
4144 if prevent_auto_indent {
4145 edits.push(edit);
4146 } else {
4147 auto_indent_edits.push(edit);
4148 }
4149 }
4150 if !edits.is_empty() {
4151 this.edit(edits, cx);
4152 }
4153 if !auto_indent_edits.is_empty() {
4154 this.edit_with_autoindent(auto_indent_edits, cx);
4155 }
4156
4157 let buffer = this.buffer.read(cx).snapshot(cx);
4158 let new_selections = selection_info
4159 .into_iter()
4160 .map(|(extra_newline_inserted, new_selection)| {
4161 let mut cursor = new_selection.end.to_point(&buffer);
4162 if extra_newline_inserted {
4163 cursor.row -= 1;
4164 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4165 }
4166 new_selection.map(|_| cursor)
4167 })
4168 .collect();
4169
4170 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4171 s.select(new_selections)
4172 });
4173 this.refresh_inline_completion(true, false, window, cx);
4174 });
4175 }
4176
4177 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4178 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4179
4180 let buffer = self.buffer.read(cx);
4181 let snapshot = buffer.snapshot(cx);
4182
4183 let mut edits = Vec::new();
4184 let mut rows = Vec::new();
4185
4186 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4187 let cursor = selection.head();
4188 let row = cursor.row;
4189
4190 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4191
4192 let newline = "\n".to_string();
4193 edits.push((start_of_line..start_of_line, newline));
4194
4195 rows.push(row + rows_inserted as u32);
4196 }
4197
4198 self.transact(window, cx, |editor, window, cx| {
4199 editor.edit(edits, cx);
4200
4201 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4202 let mut index = 0;
4203 s.move_cursors_with(|map, _, _| {
4204 let row = rows[index];
4205 index += 1;
4206
4207 let point = Point::new(row, 0);
4208 let boundary = map.next_line_boundary(point).1;
4209 let clipped = map.clip_point(boundary, Bias::Left);
4210
4211 (clipped, SelectionGoal::None)
4212 });
4213 });
4214
4215 let mut indent_edits = Vec::new();
4216 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4217 for row in rows {
4218 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4219 for (row, indent) in indents {
4220 if indent.len == 0 {
4221 continue;
4222 }
4223
4224 let text = match indent.kind {
4225 IndentKind::Space => " ".repeat(indent.len as usize),
4226 IndentKind::Tab => "\t".repeat(indent.len as usize),
4227 };
4228 let point = Point::new(row.0, 0);
4229 indent_edits.push((point..point, text));
4230 }
4231 }
4232 editor.edit(indent_edits, cx);
4233 });
4234 }
4235
4236 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4237 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4238
4239 let buffer = self.buffer.read(cx);
4240 let snapshot = buffer.snapshot(cx);
4241
4242 let mut edits = Vec::new();
4243 let mut rows = Vec::new();
4244 let mut rows_inserted = 0;
4245
4246 for selection in self.selections.all_adjusted(cx) {
4247 let cursor = selection.head();
4248 let row = cursor.row;
4249
4250 let point = Point::new(row + 1, 0);
4251 let start_of_line = snapshot.clip_point(point, Bias::Left);
4252
4253 let newline = "\n".to_string();
4254 edits.push((start_of_line..start_of_line, newline));
4255
4256 rows_inserted += 1;
4257 rows.push(row + rows_inserted);
4258 }
4259
4260 self.transact(window, cx, |editor, window, cx| {
4261 editor.edit(edits, cx);
4262
4263 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4264 let mut index = 0;
4265 s.move_cursors_with(|map, _, _| {
4266 let row = rows[index];
4267 index += 1;
4268
4269 let point = Point::new(row, 0);
4270 let boundary = map.next_line_boundary(point).1;
4271 let clipped = map.clip_point(boundary, Bias::Left);
4272
4273 (clipped, SelectionGoal::None)
4274 });
4275 });
4276
4277 let mut indent_edits = Vec::new();
4278 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4279 for row in rows {
4280 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4281 for (row, indent) in indents {
4282 if indent.len == 0 {
4283 continue;
4284 }
4285
4286 let text = match indent.kind {
4287 IndentKind::Space => " ".repeat(indent.len as usize),
4288 IndentKind::Tab => "\t".repeat(indent.len as usize),
4289 };
4290 let point = Point::new(row.0, 0);
4291 indent_edits.push((point..point, text));
4292 }
4293 }
4294 editor.edit(indent_edits, cx);
4295 });
4296 }
4297
4298 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4299 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4300 original_indent_columns: Vec::new(),
4301 });
4302 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4303 }
4304
4305 fn insert_with_autoindent_mode(
4306 &mut self,
4307 text: &str,
4308 autoindent_mode: Option<AutoindentMode>,
4309 window: &mut Window,
4310 cx: &mut Context<Self>,
4311 ) {
4312 if self.read_only(cx) {
4313 return;
4314 }
4315
4316 let text: Arc<str> = text.into();
4317 self.transact(window, cx, |this, window, cx| {
4318 let old_selections = this.selections.all_adjusted(cx);
4319 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4320 let anchors = {
4321 let snapshot = buffer.read(cx);
4322 old_selections
4323 .iter()
4324 .map(|s| {
4325 let anchor = snapshot.anchor_after(s.head());
4326 s.map(|_| anchor)
4327 })
4328 .collect::<Vec<_>>()
4329 };
4330 buffer.edit(
4331 old_selections
4332 .iter()
4333 .map(|s| (s.start..s.end, text.clone())),
4334 autoindent_mode,
4335 cx,
4336 );
4337 anchors
4338 });
4339
4340 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4341 s.select_anchors(selection_anchors);
4342 });
4343
4344 cx.notify();
4345 });
4346 }
4347
4348 fn trigger_completion_on_input(
4349 &mut self,
4350 text: &str,
4351 trigger_in_words: bool,
4352 window: &mut Window,
4353 cx: &mut Context<Self>,
4354 ) {
4355 let ignore_completion_provider = self
4356 .context_menu
4357 .borrow()
4358 .as_ref()
4359 .map(|menu| match menu {
4360 CodeContextMenu::Completions(completions_menu) => {
4361 completions_menu.ignore_completion_provider
4362 }
4363 CodeContextMenu::CodeActions(_) => false,
4364 })
4365 .unwrap_or(false);
4366
4367 if ignore_completion_provider {
4368 self.show_word_completions(&ShowWordCompletions, window, cx);
4369 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4370 self.show_completions(
4371 &ShowCompletions {
4372 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4373 },
4374 window,
4375 cx,
4376 );
4377 } else {
4378 self.hide_context_menu(window, cx);
4379 }
4380 }
4381
4382 fn is_completion_trigger(
4383 &self,
4384 text: &str,
4385 trigger_in_words: bool,
4386 cx: &mut Context<Self>,
4387 ) -> bool {
4388 let position = self.selections.newest_anchor().head();
4389 let multibuffer = self.buffer.read(cx);
4390 let Some(buffer) = position
4391 .buffer_id
4392 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4393 else {
4394 return false;
4395 };
4396
4397 if let Some(completion_provider) = &self.completion_provider {
4398 completion_provider.is_completion_trigger(
4399 &buffer,
4400 position.text_anchor,
4401 text,
4402 trigger_in_words,
4403 cx,
4404 )
4405 } else {
4406 false
4407 }
4408 }
4409
4410 /// If any empty selections is touching the start of its innermost containing autoclose
4411 /// region, expand it to select the brackets.
4412 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4413 let selections = self.selections.all::<usize>(cx);
4414 let buffer = self.buffer.read(cx).read(cx);
4415 let new_selections = self
4416 .selections_with_autoclose_regions(selections, &buffer)
4417 .map(|(mut selection, region)| {
4418 if !selection.is_empty() {
4419 return selection;
4420 }
4421
4422 if let Some(region) = region {
4423 let mut range = region.range.to_offset(&buffer);
4424 if selection.start == range.start && range.start >= region.pair.start.len() {
4425 range.start -= region.pair.start.len();
4426 if buffer.contains_str_at(range.start, ®ion.pair.start)
4427 && buffer.contains_str_at(range.end, ®ion.pair.end)
4428 {
4429 range.end += region.pair.end.len();
4430 selection.start = range.start;
4431 selection.end = range.end;
4432
4433 return selection;
4434 }
4435 }
4436 }
4437
4438 let always_treat_brackets_as_autoclosed = buffer
4439 .language_settings_at(selection.start, cx)
4440 .always_treat_brackets_as_autoclosed;
4441
4442 if !always_treat_brackets_as_autoclosed {
4443 return selection;
4444 }
4445
4446 if let Some(scope) = buffer.language_scope_at(selection.start) {
4447 for (pair, enabled) in scope.brackets() {
4448 if !enabled || !pair.close {
4449 continue;
4450 }
4451
4452 if buffer.contains_str_at(selection.start, &pair.end) {
4453 let pair_start_len = pair.start.len();
4454 if buffer.contains_str_at(
4455 selection.start.saturating_sub(pair_start_len),
4456 &pair.start,
4457 ) {
4458 selection.start -= pair_start_len;
4459 selection.end += pair.end.len();
4460
4461 return selection;
4462 }
4463 }
4464 }
4465 }
4466
4467 selection
4468 })
4469 .collect();
4470
4471 drop(buffer);
4472 self.change_selections(None, window, cx, |selections| {
4473 selections.select(new_selections)
4474 });
4475 }
4476
4477 /// Iterate the given selections, and for each one, find the smallest surrounding
4478 /// autoclose region. This uses the ordering of the selections and the autoclose
4479 /// regions to avoid repeated comparisons.
4480 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4481 &'a self,
4482 selections: impl IntoIterator<Item = Selection<D>>,
4483 buffer: &'a MultiBufferSnapshot,
4484 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4485 let mut i = 0;
4486 let mut regions = self.autoclose_regions.as_slice();
4487 selections.into_iter().map(move |selection| {
4488 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4489
4490 let mut enclosing = None;
4491 while let Some(pair_state) = regions.get(i) {
4492 if pair_state.range.end.to_offset(buffer) < range.start {
4493 regions = ®ions[i + 1..];
4494 i = 0;
4495 } else if pair_state.range.start.to_offset(buffer) > range.end {
4496 break;
4497 } else {
4498 if pair_state.selection_id == selection.id {
4499 enclosing = Some(pair_state);
4500 }
4501 i += 1;
4502 }
4503 }
4504
4505 (selection, enclosing)
4506 })
4507 }
4508
4509 /// Remove any autoclose regions that no longer contain their selection.
4510 fn invalidate_autoclose_regions(
4511 &mut self,
4512 mut selections: &[Selection<Anchor>],
4513 buffer: &MultiBufferSnapshot,
4514 ) {
4515 self.autoclose_regions.retain(|state| {
4516 let mut i = 0;
4517 while let Some(selection) = selections.get(i) {
4518 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4519 selections = &selections[1..];
4520 continue;
4521 }
4522 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4523 break;
4524 }
4525 if selection.id == state.selection_id {
4526 return true;
4527 } else {
4528 i += 1;
4529 }
4530 }
4531 false
4532 });
4533 }
4534
4535 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4536 let offset = position.to_offset(buffer);
4537 let (word_range, kind) = buffer.surrounding_word(offset, true);
4538 if offset > word_range.start && kind == Some(CharKind::Word) {
4539 Some(
4540 buffer
4541 .text_for_range(word_range.start..offset)
4542 .collect::<String>(),
4543 )
4544 } else {
4545 None
4546 }
4547 }
4548
4549 pub fn toggle_inline_values(
4550 &mut self,
4551 _: &ToggleInlineValues,
4552 _: &mut Window,
4553 cx: &mut Context<Self>,
4554 ) {
4555 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4556
4557 self.refresh_inline_values(cx);
4558 }
4559
4560 pub fn toggle_inlay_hints(
4561 &mut self,
4562 _: &ToggleInlayHints,
4563 _: &mut Window,
4564 cx: &mut Context<Self>,
4565 ) {
4566 self.refresh_inlay_hints(
4567 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4568 cx,
4569 );
4570 }
4571
4572 pub fn inlay_hints_enabled(&self) -> bool {
4573 self.inlay_hint_cache.enabled
4574 }
4575
4576 pub fn inline_values_enabled(&self) -> bool {
4577 self.inline_value_cache.enabled
4578 }
4579
4580 #[cfg(any(test, feature = "test-support"))]
4581 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4582 self.display_map
4583 .read(cx)
4584 .current_inlays()
4585 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4586 .cloned()
4587 .collect()
4588 }
4589
4590 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4591 if self.semantics_provider.is_none() || !self.mode.is_full() {
4592 return;
4593 }
4594
4595 let reason_description = reason.description();
4596 let ignore_debounce = matches!(
4597 reason,
4598 InlayHintRefreshReason::SettingsChange(_)
4599 | InlayHintRefreshReason::Toggle(_)
4600 | InlayHintRefreshReason::ExcerptsRemoved(_)
4601 | InlayHintRefreshReason::ModifiersChanged(_)
4602 );
4603 let (invalidate_cache, required_languages) = match reason {
4604 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4605 match self.inlay_hint_cache.modifiers_override(enabled) {
4606 Some(enabled) => {
4607 if enabled {
4608 (InvalidationStrategy::RefreshRequested, None)
4609 } else {
4610 self.splice_inlays(
4611 &self
4612 .visible_inlay_hints(cx)
4613 .iter()
4614 .map(|inlay| inlay.id)
4615 .collect::<Vec<InlayId>>(),
4616 Vec::new(),
4617 cx,
4618 );
4619 return;
4620 }
4621 }
4622 None => return,
4623 }
4624 }
4625 InlayHintRefreshReason::Toggle(enabled) => {
4626 if self.inlay_hint_cache.toggle(enabled) {
4627 if enabled {
4628 (InvalidationStrategy::RefreshRequested, None)
4629 } else {
4630 self.splice_inlays(
4631 &self
4632 .visible_inlay_hints(cx)
4633 .iter()
4634 .map(|inlay| inlay.id)
4635 .collect::<Vec<InlayId>>(),
4636 Vec::new(),
4637 cx,
4638 );
4639 return;
4640 }
4641 } else {
4642 return;
4643 }
4644 }
4645 InlayHintRefreshReason::SettingsChange(new_settings) => {
4646 match self.inlay_hint_cache.update_settings(
4647 &self.buffer,
4648 new_settings,
4649 self.visible_inlay_hints(cx),
4650 cx,
4651 ) {
4652 ControlFlow::Break(Some(InlaySplice {
4653 to_remove,
4654 to_insert,
4655 })) => {
4656 self.splice_inlays(&to_remove, to_insert, cx);
4657 return;
4658 }
4659 ControlFlow::Break(None) => return,
4660 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4661 }
4662 }
4663 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4664 if let Some(InlaySplice {
4665 to_remove,
4666 to_insert,
4667 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4668 {
4669 self.splice_inlays(&to_remove, to_insert, cx);
4670 }
4671 self.display_map.update(cx, |display_map, _| {
4672 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4673 });
4674 return;
4675 }
4676 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4677 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4678 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4679 }
4680 InlayHintRefreshReason::RefreshRequested => {
4681 (InvalidationStrategy::RefreshRequested, None)
4682 }
4683 };
4684
4685 if let Some(InlaySplice {
4686 to_remove,
4687 to_insert,
4688 }) = self.inlay_hint_cache.spawn_hint_refresh(
4689 reason_description,
4690 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4691 invalidate_cache,
4692 ignore_debounce,
4693 cx,
4694 ) {
4695 self.splice_inlays(&to_remove, to_insert, cx);
4696 }
4697 }
4698
4699 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4700 self.display_map
4701 .read(cx)
4702 .current_inlays()
4703 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4704 .cloned()
4705 .collect()
4706 }
4707
4708 pub fn excerpts_for_inlay_hints_query(
4709 &self,
4710 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4711 cx: &mut Context<Editor>,
4712 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4713 let Some(project) = self.project.as_ref() else {
4714 return HashMap::default();
4715 };
4716 let project = project.read(cx);
4717 let multi_buffer = self.buffer().read(cx);
4718 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4719 let multi_buffer_visible_start = self
4720 .scroll_manager
4721 .anchor()
4722 .anchor
4723 .to_point(&multi_buffer_snapshot);
4724 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4725 multi_buffer_visible_start
4726 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4727 Bias::Left,
4728 );
4729 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4730 multi_buffer_snapshot
4731 .range_to_buffer_ranges(multi_buffer_visible_range)
4732 .into_iter()
4733 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4734 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4735 let buffer_file = project::File::from_dyn(buffer.file())?;
4736 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4737 let worktree_entry = buffer_worktree
4738 .read(cx)
4739 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4740 if worktree_entry.is_ignored {
4741 return None;
4742 }
4743
4744 let language = buffer.language()?;
4745 if let Some(restrict_to_languages) = restrict_to_languages {
4746 if !restrict_to_languages.contains(language) {
4747 return None;
4748 }
4749 }
4750 Some((
4751 excerpt_id,
4752 (
4753 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4754 buffer.version().clone(),
4755 excerpt_visible_range,
4756 ),
4757 ))
4758 })
4759 .collect()
4760 }
4761
4762 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4763 TextLayoutDetails {
4764 text_system: window.text_system().clone(),
4765 editor_style: self.style.clone().unwrap(),
4766 rem_size: window.rem_size(),
4767 scroll_anchor: self.scroll_manager.anchor(),
4768 visible_rows: self.visible_line_count(),
4769 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4770 }
4771 }
4772
4773 pub fn splice_inlays(
4774 &self,
4775 to_remove: &[InlayId],
4776 to_insert: Vec<Inlay>,
4777 cx: &mut Context<Self>,
4778 ) {
4779 self.display_map.update(cx, |display_map, cx| {
4780 display_map.splice_inlays(to_remove, to_insert, cx)
4781 });
4782 cx.notify();
4783 }
4784
4785 fn trigger_on_type_formatting(
4786 &self,
4787 input: String,
4788 window: &mut Window,
4789 cx: &mut Context<Self>,
4790 ) -> Option<Task<Result<()>>> {
4791 if input.len() != 1 {
4792 return None;
4793 }
4794
4795 let project = self.project.as_ref()?;
4796 let position = self.selections.newest_anchor().head();
4797 let (buffer, buffer_position) = self
4798 .buffer
4799 .read(cx)
4800 .text_anchor_for_position(position, cx)?;
4801
4802 let settings = language_settings::language_settings(
4803 buffer
4804 .read(cx)
4805 .language_at(buffer_position)
4806 .map(|l| l.name()),
4807 buffer.read(cx).file(),
4808 cx,
4809 );
4810 if !settings.use_on_type_format {
4811 return None;
4812 }
4813
4814 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4815 // hence we do LSP request & edit on host side only — add formats to host's history.
4816 let push_to_lsp_host_history = true;
4817 // If this is not the host, append its history with new edits.
4818 let push_to_client_history = project.read(cx).is_via_collab();
4819
4820 let on_type_formatting = project.update(cx, |project, cx| {
4821 project.on_type_format(
4822 buffer.clone(),
4823 buffer_position,
4824 input,
4825 push_to_lsp_host_history,
4826 cx,
4827 )
4828 });
4829 Some(cx.spawn_in(window, async move |editor, cx| {
4830 if let Some(transaction) = on_type_formatting.await? {
4831 if push_to_client_history {
4832 buffer
4833 .update(cx, |buffer, _| {
4834 buffer.push_transaction(transaction, Instant::now());
4835 buffer.finalize_last_transaction();
4836 })
4837 .ok();
4838 }
4839 editor.update(cx, |editor, cx| {
4840 editor.refresh_document_highlights(cx);
4841 })?;
4842 }
4843 Ok(())
4844 }))
4845 }
4846
4847 pub fn show_word_completions(
4848 &mut self,
4849 _: &ShowWordCompletions,
4850 window: &mut Window,
4851 cx: &mut Context<Self>,
4852 ) {
4853 self.open_completions_menu(true, None, window, cx);
4854 }
4855
4856 pub fn show_completions(
4857 &mut self,
4858 options: &ShowCompletions,
4859 window: &mut Window,
4860 cx: &mut Context<Self>,
4861 ) {
4862 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4863 }
4864
4865 fn open_completions_menu(
4866 &mut self,
4867 ignore_completion_provider: bool,
4868 trigger: Option<&str>,
4869 window: &mut Window,
4870 cx: &mut Context<Self>,
4871 ) {
4872 if self.pending_rename.is_some() {
4873 return;
4874 }
4875 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4876 return;
4877 }
4878
4879 let position = self.selections.newest_anchor().head();
4880 if position.diff_base_anchor.is_some() {
4881 return;
4882 }
4883 let (buffer, buffer_position) =
4884 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4885 output
4886 } else {
4887 return;
4888 };
4889 let buffer_snapshot = buffer.read(cx).snapshot();
4890 let show_completion_documentation = buffer_snapshot
4891 .settings_at(buffer_position, cx)
4892 .show_completion_documentation;
4893
4894 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4895
4896 let trigger_kind = match trigger {
4897 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4898 CompletionTriggerKind::TRIGGER_CHARACTER
4899 }
4900 _ => CompletionTriggerKind::INVOKED,
4901 };
4902 let completion_context = CompletionContext {
4903 trigger_character: trigger.and_then(|trigger| {
4904 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4905 Some(String::from(trigger))
4906 } else {
4907 None
4908 }
4909 }),
4910 trigger_kind,
4911 };
4912
4913 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4914 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4915 let word_to_exclude = buffer_snapshot
4916 .text_for_range(old_range.clone())
4917 .collect::<String>();
4918 (
4919 buffer_snapshot.anchor_before(old_range.start)
4920 ..buffer_snapshot.anchor_after(old_range.end),
4921 Some(word_to_exclude),
4922 )
4923 } else {
4924 (buffer_position..buffer_position, None)
4925 };
4926
4927 let completion_settings = language_settings(
4928 buffer_snapshot
4929 .language_at(buffer_position)
4930 .map(|language| language.name()),
4931 buffer_snapshot.file(),
4932 cx,
4933 )
4934 .completions;
4935
4936 // The document can be large, so stay in reasonable bounds when searching for words,
4937 // otherwise completion pop-up might be slow to appear.
4938 const WORD_LOOKUP_ROWS: u32 = 5_000;
4939 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4940 let min_word_search = buffer_snapshot.clip_point(
4941 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4942 Bias::Left,
4943 );
4944 let max_word_search = buffer_snapshot.clip_point(
4945 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4946 Bias::Right,
4947 );
4948 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4949 ..buffer_snapshot.point_to_offset(max_word_search);
4950
4951 let provider = self
4952 .completion_provider
4953 .as_ref()
4954 .filter(|_| !ignore_completion_provider);
4955 let skip_digits = query
4956 .as_ref()
4957 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4958
4959 let (mut words, provided_completions) = match provider {
4960 Some(provider) => {
4961 let completions = provider.completions(
4962 position.excerpt_id,
4963 &buffer,
4964 buffer_position,
4965 completion_context,
4966 window,
4967 cx,
4968 );
4969
4970 let words = match completion_settings.words {
4971 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4972 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4973 .background_spawn(async move {
4974 buffer_snapshot.words_in_range(WordsQuery {
4975 fuzzy_contents: None,
4976 range: word_search_range,
4977 skip_digits,
4978 })
4979 }),
4980 };
4981
4982 (words, completions)
4983 }
4984 None => (
4985 cx.background_spawn(async move {
4986 buffer_snapshot.words_in_range(WordsQuery {
4987 fuzzy_contents: None,
4988 range: word_search_range,
4989 skip_digits,
4990 })
4991 }),
4992 Task::ready(Ok(None)),
4993 ),
4994 };
4995
4996 let sort_completions = provider
4997 .as_ref()
4998 .map_or(false, |provider| provider.sort_completions());
4999
5000 let filter_completions = provider
5001 .as_ref()
5002 .map_or(true, |provider| provider.filter_completions());
5003
5004 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5005
5006 let id = post_inc(&mut self.next_completion_id);
5007 let task = cx.spawn_in(window, async move |editor, cx| {
5008 async move {
5009 editor.update(cx, |this, _| {
5010 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5011 })?;
5012
5013 let mut completions = Vec::new();
5014 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5015 completions.extend(provided_completions);
5016 if completion_settings.words == WordsCompletionMode::Fallback {
5017 words = Task::ready(BTreeMap::default());
5018 }
5019 }
5020
5021 let mut words = words.await;
5022 if let Some(word_to_exclude) = &word_to_exclude {
5023 words.remove(word_to_exclude);
5024 }
5025 for lsp_completion in &completions {
5026 words.remove(&lsp_completion.new_text);
5027 }
5028 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5029 replace_range: old_range.clone(),
5030 new_text: word.clone(),
5031 label: CodeLabel::plain(word, None),
5032 icon_path: None,
5033 documentation: None,
5034 source: CompletionSource::BufferWord {
5035 word_range,
5036 resolved: false,
5037 },
5038 insert_text_mode: Some(InsertTextMode::AS_IS),
5039 confirm: None,
5040 }));
5041
5042 let menu = if completions.is_empty() {
5043 None
5044 } else {
5045 let mut menu = CompletionsMenu::new(
5046 id,
5047 sort_completions,
5048 show_completion_documentation,
5049 ignore_completion_provider,
5050 position,
5051 buffer.clone(),
5052 completions.into(),
5053 snippet_sort_order,
5054 );
5055
5056 menu.filter(
5057 if filter_completions {
5058 query.as_deref()
5059 } else {
5060 None
5061 },
5062 cx.background_executor().clone(),
5063 )
5064 .await;
5065
5066 menu.visible().then_some(menu)
5067 };
5068
5069 editor.update_in(cx, |editor, window, cx| {
5070 match editor.context_menu.borrow().as_ref() {
5071 None => {}
5072 Some(CodeContextMenu::Completions(prev_menu)) => {
5073 if prev_menu.id > id {
5074 return;
5075 }
5076 }
5077 _ => return,
5078 }
5079
5080 if editor.focus_handle.is_focused(window) && menu.is_some() {
5081 let mut menu = menu.unwrap();
5082 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5083
5084 *editor.context_menu.borrow_mut() =
5085 Some(CodeContextMenu::Completions(menu));
5086
5087 if editor.show_edit_predictions_in_menu() {
5088 editor.update_visible_inline_completion(window, cx);
5089 } else {
5090 editor.discard_inline_completion(false, cx);
5091 }
5092
5093 cx.notify();
5094 } else if editor.completion_tasks.len() <= 1 {
5095 // If there are no more completion tasks and the last menu was
5096 // empty, we should hide it.
5097 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5098 // If it was already hidden and we don't show inline
5099 // completions in the menu, we should also show the
5100 // inline-completion when available.
5101 if was_hidden && editor.show_edit_predictions_in_menu() {
5102 editor.update_visible_inline_completion(window, cx);
5103 }
5104 }
5105 })?;
5106
5107 anyhow::Ok(())
5108 }
5109 .log_err()
5110 .await
5111 });
5112
5113 self.completion_tasks.push((id, task));
5114 }
5115
5116 #[cfg(feature = "test-support")]
5117 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5118 let menu = self.context_menu.borrow();
5119 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5120 let completions = menu.completions.borrow();
5121 Some(completions.to_vec())
5122 } else {
5123 None
5124 }
5125 }
5126
5127 pub fn confirm_completion(
5128 &mut self,
5129 action: &ConfirmCompletion,
5130 window: &mut Window,
5131 cx: &mut Context<Self>,
5132 ) -> Option<Task<Result<()>>> {
5133 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5134 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5135 }
5136
5137 pub fn confirm_completion_insert(
5138 &mut self,
5139 _: &ConfirmCompletionInsert,
5140 window: &mut Window,
5141 cx: &mut Context<Self>,
5142 ) -> Option<Task<Result<()>>> {
5143 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5144 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5145 }
5146
5147 pub fn confirm_completion_replace(
5148 &mut self,
5149 _: &ConfirmCompletionReplace,
5150 window: &mut Window,
5151 cx: &mut Context<Self>,
5152 ) -> Option<Task<Result<()>>> {
5153 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5154 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5155 }
5156
5157 pub fn compose_completion(
5158 &mut self,
5159 action: &ComposeCompletion,
5160 window: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) -> Option<Task<Result<()>>> {
5163 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5164 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5165 }
5166
5167 fn do_completion(
5168 &mut self,
5169 item_ix: Option<usize>,
5170 intent: CompletionIntent,
5171 window: &mut Window,
5172 cx: &mut Context<Editor>,
5173 ) -> Option<Task<Result<()>>> {
5174 use language::ToOffset as _;
5175
5176 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5177 else {
5178 return None;
5179 };
5180
5181 let candidate_id = {
5182 let entries = completions_menu.entries.borrow();
5183 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5184 if self.show_edit_predictions_in_menu() {
5185 self.discard_inline_completion(true, cx);
5186 }
5187 mat.candidate_id
5188 };
5189
5190 let buffer_handle = completions_menu.buffer;
5191 let completion = completions_menu
5192 .completions
5193 .borrow()
5194 .get(candidate_id)?
5195 .clone();
5196 cx.stop_propagation();
5197
5198 let snapshot = self.buffer.read(cx).snapshot(cx);
5199 let newest_anchor = self.selections.newest_anchor();
5200
5201 let snippet;
5202 let new_text;
5203 if completion.is_snippet() {
5204 let mut snippet_source = completion.new_text.clone();
5205 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5206 if scope.prefers_label_for_snippet_in_completion() {
5207 if let Some(label) = completion.label() {
5208 if matches!(
5209 completion.kind(),
5210 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5211 ) {
5212 snippet_source = label;
5213 }
5214 }
5215 }
5216 }
5217 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5218 new_text = snippet.as_ref().unwrap().text.clone();
5219 } else {
5220 snippet = None;
5221 new_text = completion.new_text.clone();
5222 };
5223
5224 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5225 let buffer = buffer_handle.read(cx);
5226 let replace_range_multibuffer = {
5227 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5228 let multibuffer_anchor = snapshot
5229 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5230 .unwrap()
5231 ..snapshot
5232 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5233 .unwrap();
5234 multibuffer_anchor.start.to_offset(&snapshot)
5235 ..multibuffer_anchor.end.to_offset(&snapshot)
5236 };
5237 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5238 return None;
5239 }
5240
5241 let old_text = buffer
5242 .text_for_range(replace_range.clone())
5243 .collect::<String>();
5244 let lookbehind = newest_anchor
5245 .start
5246 .text_anchor
5247 .to_offset(buffer)
5248 .saturating_sub(replace_range.start);
5249 let lookahead = replace_range
5250 .end
5251 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5252 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5253 let suffix = &old_text[lookbehind.min(old_text.len())..];
5254
5255 let selections = self.selections.all::<usize>(cx);
5256 let mut ranges = Vec::new();
5257 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5258
5259 for selection in &selections {
5260 let range = if selection.id == newest_anchor.id {
5261 replace_range_multibuffer.clone()
5262 } else {
5263 let mut range = selection.range();
5264
5265 // if prefix is present, don't duplicate it
5266 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5267 range.start = range.start.saturating_sub(lookbehind);
5268
5269 // if suffix is also present, mimic the newest cursor and replace it
5270 if selection.id != newest_anchor.id
5271 && snapshot.contains_str_at(range.end, suffix)
5272 {
5273 range.end += lookahead;
5274 }
5275 }
5276 range
5277 };
5278
5279 ranges.push(range.clone());
5280
5281 if !self.linked_edit_ranges.is_empty() {
5282 let start_anchor = snapshot.anchor_before(range.start);
5283 let end_anchor = snapshot.anchor_after(range.end);
5284 if let Some(ranges) = self
5285 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5286 {
5287 for (buffer, edits) in ranges {
5288 linked_edits
5289 .entry(buffer.clone())
5290 .or_default()
5291 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5292 }
5293 }
5294 }
5295 }
5296
5297 cx.emit(EditorEvent::InputHandled {
5298 utf16_range_to_replace: None,
5299 text: new_text.clone().into(),
5300 });
5301
5302 self.transact(window, cx, |this, window, cx| {
5303 if let Some(mut snippet) = snippet {
5304 snippet.text = new_text.to_string();
5305 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5306 } else {
5307 this.buffer.update(cx, |buffer, cx| {
5308 let auto_indent = match completion.insert_text_mode {
5309 Some(InsertTextMode::AS_IS) => None,
5310 _ => this.autoindent_mode.clone(),
5311 };
5312 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5313 buffer.edit(edits, auto_indent, cx);
5314 });
5315 }
5316 for (buffer, edits) in linked_edits {
5317 buffer.update(cx, |buffer, cx| {
5318 let snapshot = buffer.snapshot();
5319 let edits = edits
5320 .into_iter()
5321 .map(|(range, text)| {
5322 use text::ToPoint as TP;
5323 let end_point = TP::to_point(&range.end, &snapshot);
5324 let start_point = TP::to_point(&range.start, &snapshot);
5325 (start_point..end_point, text)
5326 })
5327 .sorted_by_key(|(range, _)| range.start);
5328 buffer.edit(edits, None, cx);
5329 })
5330 }
5331
5332 this.refresh_inline_completion(true, false, window, cx);
5333 });
5334
5335 let show_new_completions_on_confirm = completion
5336 .confirm
5337 .as_ref()
5338 .map_or(false, |confirm| confirm(intent, window, cx));
5339 if show_new_completions_on_confirm {
5340 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5341 }
5342
5343 let provider = self.completion_provider.as_ref()?;
5344 drop(completion);
5345 let apply_edits = provider.apply_additional_edits_for_completion(
5346 buffer_handle,
5347 completions_menu.completions.clone(),
5348 candidate_id,
5349 true,
5350 cx,
5351 );
5352
5353 let editor_settings = EditorSettings::get_global(cx);
5354 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5355 // After the code completion is finished, users often want to know what signatures are needed.
5356 // so we should automatically call signature_help
5357 self.show_signature_help(&ShowSignatureHelp, window, cx);
5358 }
5359
5360 Some(cx.foreground_executor().spawn(async move {
5361 apply_edits.await?;
5362 Ok(())
5363 }))
5364 }
5365
5366 pub fn toggle_code_actions(
5367 &mut self,
5368 action: &ToggleCodeActions,
5369 window: &mut Window,
5370 cx: &mut Context<Self>,
5371 ) {
5372 let quick_launch = action.quick_launch;
5373 let mut context_menu = self.context_menu.borrow_mut();
5374 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5375 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5376 // Toggle if we're selecting the same one
5377 *context_menu = None;
5378 cx.notify();
5379 return;
5380 } else {
5381 // Otherwise, clear it and start a new one
5382 *context_menu = None;
5383 cx.notify();
5384 }
5385 }
5386 drop(context_menu);
5387 let snapshot = self.snapshot(window, cx);
5388 let deployed_from_indicator = action.deployed_from_indicator;
5389 let mut task = self.code_actions_task.take();
5390 let action = action.clone();
5391 cx.spawn_in(window, async move |editor, cx| {
5392 while let Some(prev_task) = task {
5393 prev_task.await.log_err();
5394 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5395 }
5396
5397 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5398 if editor.focus_handle.is_focused(window) {
5399 let multibuffer_point = action
5400 .deployed_from_indicator
5401 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5402 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5403 let (buffer, buffer_row) = snapshot
5404 .buffer_snapshot
5405 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5406 .and_then(|(buffer_snapshot, range)| {
5407 editor
5408 .buffer
5409 .read(cx)
5410 .buffer(buffer_snapshot.remote_id())
5411 .map(|buffer| (buffer, range.start.row))
5412 })?;
5413 let (_, code_actions) = editor
5414 .available_code_actions
5415 .clone()
5416 .and_then(|(location, code_actions)| {
5417 let snapshot = location.buffer.read(cx).snapshot();
5418 let point_range = location.range.to_point(&snapshot);
5419 let point_range = point_range.start.row..=point_range.end.row;
5420 if point_range.contains(&buffer_row) {
5421 Some((location, code_actions))
5422 } else {
5423 None
5424 }
5425 })
5426 .unzip();
5427 let buffer_id = buffer.read(cx).remote_id();
5428 let tasks = editor
5429 .tasks
5430 .get(&(buffer_id, buffer_row))
5431 .map(|t| Arc::new(t.to_owned()));
5432 if tasks.is_none() && code_actions.is_none() {
5433 return None;
5434 }
5435
5436 editor.completion_tasks.clear();
5437 editor.discard_inline_completion(false, cx);
5438 let task_context =
5439 tasks
5440 .as_ref()
5441 .zip(editor.project.clone())
5442 .map(|(tasks, project)| {
5443 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5444 });
5445
5446 Some(cx.spawn_in(window, async move |editor, cx| {
5447 let task_context = match task_context {
5448 Some(task_context) => task_context.await,
5449 None => None,
5450 };
5451 let resolved_tasks =
5452 tasks
5453 .zip(task_context.clone())
5454 .map(|(tasks, task_context)| ResolvedTasks {
5455 templates: tasks.resolve(&task_context).collect(),
5456 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5457 multibuffer_point.row,
5458 tasks.column,
5459 )),
5460 });
5461 let debug_scenarios = editor.update(cx, |editor, cx| {
5462 if cx.has_flag::<DebuggerFeatureFlag>() {
5463 maybe!({
5464 let project = editor.project.as_ref()?;
5465 let dap_store = project.read(cx).dap_store();
5466 let mut scenarios = vec![];
5467 let resolved_tasks = resolved_tasks.as_ref()?;
5468 let buffer = buffer.read(cx);
5469 let language = buffer.language()?;
5470 let file = buffer.file();
5471 let debug_adapter =
5472 language_settings(language.name().into(), file, cx)
5473 .debuggers
5474 .first()
5475 .map(SharedString::from)
5476 .or_else(|| {
5477 language
5478 .config()
5479 .debuggers
5480 .first()
5481 .map(SharedString::from)
5482 })?;
5483
5484 dap_store.update(cx, |dap_store, cx| {
5485 for (_, task) in &resolved_tasks.templates {
5486 if let Some(scenario) = dap_store
5487 .debug_scenario_for_build_task(
5488 task.original_task().clone(),
5489 debug_adapter.clone().into(),
5490 task.display_label().to_owned().into(),
5491 cx,
5492 )
5493 {
5494 scenarios.push(scenario);
5495 }
5496 }
5497 });
5498 Some(scenarios)
5499 })
5500 .unwrap_or_default()
5501 } else {
5502 vec![]
5503 }
5504 })?;
5505 let spawn_straight_away = quick_launch
5506 && resolved_tasks
5507 .as_ref()
5508 .map_or(false, |tasks| tasks.templates.len() == 1)
5509 && code_actions
5510 .as_ref()
5511 .map_or(true, |actions| actions.is_empty())
5512 && debug_scenarios.is_empty();
5513 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5514 *editor.context_menu.borrow_mut() =
5515 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5516 buffer,
5517 actions: CodeActionContents::new(
5518 resolved_tasks,
5519 code_actions,
5520 debug_scenarios,
5521 task_context.unwrap_or_default(),
5522 ),
5523 selected_item: Default::default(),
5524 scroll_handle: UniformListScrollHandle::default(),
5525 deployed_from_indicator,
5526 }));
5527 if spawn_straight_away {
5528 if let Some(task) = editor.confirm_code_action(
5529 &ConfirmCodeAction { item_ix: Some(0) },
5530 window,
5531 cx,
5532 ) {
5533 cx.notify();
5534 return task;
5535 }
5536 }
5537 cx.notify();
5538 Task::ready(Ok(()))
5539 }) {
5540 task.await
5541 } else {
5542 Ok(())
5543 }
5544 }))
5545 } else {
5546 Some(Task::ready(Ok(())))
5547 }
5548 })?;
5549 if let Some(task) = spawned_test_task {
5550 task.await?;
5551 }
5552
5553 Ok::<_, anyhow::Error>(())
5554 })
5555 .detach_and_log_err(cx);
5556 }
5557
5558 pub fn confirm_code_action(
5559 &mut self,
5560 action: &ConfirmCodeAction,
5561 window: &mut Window,
5562 cx: &mut Context<Self>,
5563 ) -> Option<Task<Result<()>>> {
5564 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5565
5566 let actions_menu =
5567 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5568 menu
5569 } else {
5570 return None;
5571 };
5572
5573 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5574 let action = actions_menu.actions.get(action_ix)?;
5575 let title = action.label();
5576 let buffer = actions_menu.buffer;
5577 let workspace = self.workspace()?;
5578
5579 match action {
5580 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5581 workspace.update(cx, |workspace, cx| {
5582 workspace.schedule_resolved_task(
5583 task_source_kind,
5584 resolved_task,
5585 false,
5586 window,
5587 cx,
5588 );
5589
5590 Some(Task::ready(Ok(())))
5591 })
5592 }
5593 CodeActionsItem::CodeAction {
5594 excerpt_id,
5595 action,
5596 provider,
5597 } => {
5598 let apply_code_action =
5599 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5600 let workspace = workspace.downgrade();
5601 Some(cx.spawn_in(window, async move |editor, cx| {
5602 let project_transaction = apply_code_action.await?;
5603 Self::open_project_transaction(
5604 &editor,
5605 workspace,
5606 project_transaction,
5607 title,
5608 cx,
5609 )
5610 .await
5611 }))
5612 }
5613 CodeActionsItem::DebugScenario(scenario) => {
5614 let context = actions_menu.actions.context.clone();
5615
5616 workspace.update(cx, |workspace, cx| {
5617 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5618 });
5619 Some(Task::ready(Ok(())))
5620 }
5621 }
5622 }
5623
5624 pub async fn open_project_transaction(
5625 this: &WeakEntity<Editor>,
5626 workspace: WeakEntity<Workspace>,
5627 transaction: ProjectTransaction,
5628 title: String,
5629 cx: &mut AsyncWindowContext,
5630 ) -> Result<()> {
5631 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5632 cx.update(|_, cx| {
5633 entries.sort_unstable_by_key(|(buffer, _)| {
5634 buffer.read(cx).file().map(|f| f.path().clone())
5635 });
5636 })?;
5637
5638 // If the project transaction's edits are all contained within this editor, then
5639 // avoid opening a new editor to display them.
5640
5641 if let Some((buffer, transaction)) = entries.first() {
5642 if entries.len() == 1 {
5643 let excerpt = this.update(cx, |editor, cx| {
5644 editor
5645 .buffer()
5646 .read(cx)
5647 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5648 })?;
5649 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5650 if excerpted_buffer == *buffer {
5651 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5652 let excerpt_range = excerpt_range.to_offset(buffer);
5653 buffer
5654 .edited_ranges_for_transaction::<usize>(transaction)
5655 .all(|range| {
5656 excerpt_range.start <= range.start
5657 && excerpt_range.end >= range.end
5658 })
5659 })?;
5660
5661 if all_edits_within_excerpt {
5662 return Ok(());
5663 }
5664 }
5665 }
5666 }
5667 } else {
5668 return Ok(());
5669 }
5670
5671 let mut ranges_to_highlight = Vec::new();
5672 let excerpt_buffer = cx.new(|cx| {
5673 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5674 for (buffer_handle, transaction) in &entries {
5675 let edited_ranges = buffer_handle
5676 .read(cx)
5677 .edited_ranges_for_transaction::<Point>(transaction)
5678 .collect::<Vec<_>>();
5679 let (ranges, _) = multibuffer.set_excerpts_for_path(
5680 PathKey::for_buffer(buffer_handle, cx),
5681 buffer_handle.clone(),
5682 edited_ranges,
5683 DEFAULT_MULTIBUFFER_CONTEXT,
5684 cx,
5685 );
5686
5687 ranges_to_highlight.extend(ranges);
5688 }
5689 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5690 multibuffer
5691 })?;
5692
5693 workspace.update_in(cx, |workspace, window, cx| {
5694 let project = workspace.project().clone();
5695 let editor =
5696 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5697 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5698 editor.update(cx, |editor, cx| {
5699 editor.highlight_background::<Self>(
5700 &ranges_to_highlight,
5701 |theme| theme.editor_highlighted_line_background,
5702 cx,
5703 );
5704 });
5705 })?;
5706
5707 Ok(())
5708 }
5709
5710 pub fn clear_code_action_providers(&mut self) {
5711 self.code_action_providers.clear();
5712 self.available_code_actions.take();
5713 }
5714
5715 pub fn add_code_action_provider(
5716 &mut self,
5717 provider: Rc<dyn CodeActionProvider>,
5718 window: &mut Window,
5719 cx: &mut Context<Self>,
5720 ) {
5721 if self
5722 .code_action_providers
5723 .iter()
5724 .any(|existing_provider| existing_provider.id() == provider.id())
5725 {
5726 return;
5727 }
5728
5729 self.code_action_providers.push(provider);
5730 self.refresh_code_actions(window, cx);
5731 }
5732
5733 pub fn remove_code_action_provider(
5734 &mut self,
5735 id: Arc<str>,
5736 window: &mut Window,
5737 cx: &mut Context<Self>,
5738 ) {
5739 self.code_action_providers
5740 .retain(|provider| provider.id() != id);
5741 self.refresh_code_actions(window, cx);
5742 }
5743
5744 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5745 let newest_selection = self.selections.newest_anchor().clone();
5746 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5747 let buffer = self.buffer.read(cx);
5748 if newest_selection.head().diff_base_anchor.is_some() {
5749 return None;
5750 }
5751 let (start_buffer, start) =
5752 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5753 let (end_buffer, end) =
5754 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5755 if start_buffer != end_buffer {
5756 return None;
5757 }
5758
5759 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5760 cx.background_executor()
5761 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5762 .await;
5763
5764 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5765 let providers = this.code_action_providers.clone();
5766 let tasks = this
5767 .code_action_providers
5768 .iter()
5769 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5770 .collect::<Vec<_>>();
5771 (providers, tasks)
5772 })?;
5773
5774 let mut actions = Vec::new();
5775 for (provider, provider_actions) in
5776 providers.into_iter().zip(future::join_all(tasks).await)
5777 {
5778 if let Some(provider_actions) = provider_actions.log_err() {
5779 actions.extend(provider_actions.into_iter().map(|action| {
5780 AvailableCodeAction {
5781 excerpt_id: newest_selection.start.excerpt_id,
5782 action,
5783 provider: provider.clone(),
5784 }
5785 }));
5786 }
5787 }
5788
5789 this.update(cx, |this, cx| {
5790 this.available_code_actions = if actions.is_empty() {
5791 None
5792 } else {
5793 Some((
5794 Location {
5795 buffer: start_buffer,
5796 range: start..end,
5797 },
5798 actions.into(),
5799 ))
5800 };
5801 cx.notify();
5802 })
5803 }));
5804 None
5805 }
5806
5807 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5808 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5809 self.show_git_blame_inline = false;
5810
5811 self.show_git_blame_inline_delay_task =
5812 Some(cx.spawn_in(window, async move |this, cx| {
5813 cx.background_executor().timer(delay).await;
5814
5815 this.update(cx, |this, cx| {
5816 this.show_git_blame_inline = true;
5817 cx.notify();
5818 })
5819 .log_err();
5820 }));
5821 }
5822 }
5823
5824 fn show_blame_popover(
5825 &mut self,
5826 blame_entry: &BlameEntry,
5827 position: gpui::Point<Pixels>,
5828 cx: &mut Context<Self>,
5829 ) {
5830 if let Some(state) = &mut self.inline_blame_popover {
5831 state.hide_task.take();
5832 cx.notify();
5833 } else {
5834 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5835 let show_task = cx.spawn(async move |editor, cx| {
5836 cx.background_executor()
5837 .timer(std::time::Duration::from_millis(delay))
5838 .await;
5839 editor
5840 .update(cx, |editor, cx| {
5841 if let Some(state) = &mut editor.inline_blame_popover {
5842 state.show_task = None;
5843 cx.notify();
5844 }
5845 })
5846 .ok();
5847 });
5848 let Some(blame) = self.blame.as_ref() else {
5849 return;
5850 };
5851 let blame = blame.read(cx);
5852 let details = blame.details_for_entry(&blame_entry);
5853 let markdown = cx.new(|cx| {
5854 Markdown::new(
5855 details
5856 .as_ref()
5857 .map(|message| message.message.clone())
5858 .unwrap_or_default(),
5859 None,
5860 None,
5861 cx,
5862 )
5863 });
5864 self.inline_blame_popover = Some(InlineBlamePopover {
5865 position,
5866 show_task: Some(show_task),
5867 hide_task: None,
5868 popover_bounds: None,
5869 popover_state: InlineBlamePopoverState {
5870 scroll_handle: ScrollHandle::new(),
5871 commit_message: details,
5872 markdown,
5873 },
5874 });
5875 }
5876 }
5877
5878 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5879 if let Some(state) = &mut self.inline_blame_popover {
5880 if state.show_task.is_some() {
5881 self.inline_blame_popover.take();
5882 cx.notify();
5883 } else {
5884 let hide_task = cx.spawn(async move |editor, cx| {
5885 cx.background_executor()
5886 .timer(std::time::Duration::from_millis(100))
5887 .await;
5888 editor
5889 .update(cx, |editor, cx| {
5890 editor.inline_blame_popover.take();
5891 cx.notify();
5892 })
5893 .ok();
5894 });
5895 state.hide_task = Some(hide_task);
5896 }
5897 }
5898 }
5899
5900 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5901 if self.pending_rename.is_some() {
5902 return None;
5903 }
5904
5905 let provider = self.semantics_provider.clone()?;
5906 let buffer = self.buffer.read(cx);
5907 let newest_selection = self.selections.newest_anchor().clone();
5908 let cursor_position = newest_selection.head();
5909 let (cursor_buffer, cursor_buffer_position) =
5910 buffer.text_anchor_for_position(cursor_position, cx)?;
5911 let (tail_buffer, tail_buffer_position) =
5912 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5913 if cursor_buffer != tail_buffer {
5914 return None;
5915 }
5916
5917 let snapshot = cursor_buffer.read(cx).snapshot();
5918 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
5919 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
5920 if start_word_range != end_word_range {
5921 self.document_highlights_task.take();
5922 self.clear_background_highlights::<DocumentHighlightRead>(cx);
5923 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
5924 return None;
5925 }
5926
5927 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5928 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5929 cx.background_executor()
5930 .timer(Duration::from_millis(debounce))
5931 .await;
5932
5933 let highlights = if let Some(highlights) = cx
5934 .update(|cx| {
5935 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5936 })
5937 .ok()
5938 .flatten()
5939 {
5940 highlights.await.log_err()
5941 } else {
5942 None
5943 };
5944
5945 if let Some(highlights) = highlights {
5946 this.update(cx, |this, cx| {
5947 if this.pending_rename.is_some() {
5948 return;
5949 }
5950
5951 let buffer_id = cursor_position.buffer_id;
5952 let buffer = this.buffer.read(cx);
5953 if !buffer
5954 .text_anchor_for_position(cursor_position, cx)
5955 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5956 {
5957 return;
5958 }
5959
5960 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5961 let mut write_ranges = Vec::new();
5962 let mut read_ranges = Vec::new();
5963 for highlight in highlights {
5964 for (excerpt_id, excerpt_range) in
5965 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5966 {
5967 let start = highlight
5968 .range
5969 .start
5970 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5971 let end = highlight
5972 .range
5973 .end
5974 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5975 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5976 continue;
5977 }
5978
5979 let range = Anchor {
5980 buffer_id,
5981 excerpt_id,
5982 text_anchor: start,
5983 diff_base_anchor: None,
5984 }..Anchor {
5985 buffer_id,
5986 excerpt_id,
5987 text_anchor: end,
5988 diff_base_anchor: None,
5989 };
5990 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5991 write_ranges.push(range);
5992 } else {
5993 read_ranges.push(range);
5994 }
5995 }
5996 }
5997
5998 this.highlight_background::<DocumentHighlightRead>(
5999 &read_ranges,
6000 |theme| theme.editor_document_highlight_read_background,
6001 cx,
6002 );
6003 this.highlight_background::<DocumentHighlightWrite>(
6004 &write_ranges,
6005 |theme| theme.editor_document_highlight_write_background,
6006 cx,
6007 );
6008 cx.notify();
6009 })
6010 .log_err();
6011 }
6012 }));
6013 None
6014 }
6015
6016 fn prepare_highlight_query_from_selection(
6017 &mut self,
6018 cx: &mut Context<Editor>,
6019 ) -> Option<(String, Range<Anchor>)> {
6020 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6021 return None;
6022 }
6023 if !EditorSettings::get_global(cx).selection_highlight {
6024 return None;
6025 }
6026 if self.selections.count() != 1 || self.selections.line_mode {
6027 return None;
6028 }
6029 let selection = self.selections.newest::<Point>(cx);
6030 if selection.is_empty() || selection.start.row != selection.end.row {
6031 return None;
6032 }
6033 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6034 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6035 let query = multi_buffer_snapshot
6036 .text_for_range(selection_anchor_range.clone())
6037 .collect::<String>();
6038 if query.trim().is_empty() {
6039 return None;
6040 }
6041 Some((query, selection_anchor_range))
6042 }
6043
6044 fn update_selection_occurrence_highlights(
6045 &mut self,
6046 query_text: String,
6047 query_range: Range<Anchor>,
6048 multi_buffer_range_to_query: Range<Point>,
6049 use_debounce: bool,
6050 window: &mut Window,
6051 cx: &mut Context<Editor>,
6052 ) -> Task<()> {
6053 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6054 cx.spawn_in(window, async move |editor, cx| {
6055 if use_debounce {
6056 cx.background_executor()
6057 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6058 .await;
6059 }
6060 let match_task = cx.background_spawn(async move {
6061 let buffer_ranges = multi_buffer_snapshot
6062 .range_to_buffer_ranges(multi_buffer_range_to_query)
6063 .into_iter()
6064 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6065 let mut match_ranges = Vec::new();
6066 let Ok(regex) = project::search::SearchQuery::text(
6067 query_text.clone(),
6068 false,
6069 false,
6070 false,
6071 Default::default(),
6072 Default::default(),
6073 false,
6074 None,
6075 ) else {
6076 return Vec::default();
6077 };
6078 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6079 match_ranges.extend(
6080 regex
6081 .search(&buffer_snapshot, Some(search_range.clone()))
6082 .await
6083 .into_iter()
6084 .filter_map(|match_range| {
6085 let match_start = buffer_snapshot
6086 .anchor_after(search_range.start + match_range.start);
6087 let match_end = buffer_snapshot
6088 .anchor_before(search_range.start + match_range.end);
6089 let match_anchor_range = Anchor::range_in_buffer(
6090 excerpt_id,
6091 buffer_snapshot.remote_id(),
6092 match_start..match_end,
6093 );
6094 (match_anchor_range != query_range).then_some(match_anchor_range)
6095 }),
6096 );
6097 }
6098 match_ranges
6099 });
6100 let match_ranges = match_task.await;
6101 editor
6102 .update_in(cx, |editor, _, cx| {
6103 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6104 if !match_ranges.is_empty() {
6105 editor.highlight_background::<SelectedTextHighlight>(
6106 &match_ranges,
6107 |theme| theme.editor_document_highlight_bracket_background,
6108 cx,
6109 )
6110 }
6111 })
6112 .log_err();
6113 })
6114 }
6115
6116 fn refresh_selected_text_highlights(
6117 &mut self,
6118 on_buffer_edit: bool,
6119 window: &mut Window,
6120 cx: &mut Context<Editor>,
6121 ) {
6122 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6123 else {
6124 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6125 self.quick_selection_highlight_task.take();
6126 self.debounced_selection_highlight_task.take();
6127 return;
6128 };
6129 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6130 if on_buffer_edit
6131 || self
6132 .quick_selection_highlight_task
6133 .as_ref()
6134 .map_or(true, |(prev_anchor_range, _)| {
6135 prev_anchor_range != &query_range
6136 })
6137 {
6138 let multi_buffer_visible_start = self
6139 .scroll_manager
6140 .anchor()
6141 .anchor
6142 .to_point(&multi_buffer_snapshot);
6143 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6144 multi_buffer_visible_start
6145 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6146 Bias::Left,
6147 );
6148 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6149 self.quick_selection_highlight_task = Some((
6150 query_range.clone(),
6151 self.update_selection_occurrence_highlights(
6152 query_text.clone(),
6153 query_range.clone(),
6154 multi_buffer_visible_range,
6155 false,
6156 window,
6157 cx,
6158 ),
6159 ));
6160 }
6161 if on_buffer_edit
6162 || self
6163 .debounced_selection_highlight_task
6164 .as_ref()
6165 .map_or(true, |(prev_anchor_range, _)| {
6166 prev_anchor_range != &query_range
6167 })
6168 {
6169 let multi_buffer_start = multi_buffer_snapshot
6170 .anchor_before(0)
6171 .to_point(&multi_buffer_snapshot);
6172 let multi_buffer_end = multi_buffer_snapshot
6173 .anchor_after(multi_buffer_snapshot.len())
6174 .to_point(&multi_buffer_snapshot);
6175 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6176 self.debounced_selection_highlight_task = Some((
6177 query_range.clone(),
6178 self.update_selection_occurrence_highlights(
6179 query_text,
6180 query_range,
6181 multi_buffer_full_range,
6182 true,
6183 window,
6184 cx,
6185 ),
6186 ));
6187 }
6188 }
6189
6190 pub fn refresh_inline_completion(
6191 &mut self,
6192 debounce: bool,
6193 user_requested: bool,
6194 window: &mut Window,
6195 cx: &mut Context<Self>,
6196 ) -> Option<()> {
6197 let provider = self.edit_prediction_provider()?;
6198 let cursor = self.selections.newest_anchor().head();
6199 let (buffer, cursor_buffer_position) =
6200 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6201
6202 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6203 self.discard_inline_completion(false, cx);
6204 return None;
6205 }
6206
6207 if !user_requested
6208 && (!self.should_show_edit_predictions()
6209 || !self.is_focused(window)
6210 || buffer.read(cx).is_empty())
6211 {
6212 self.discard_inline_completion(false, cx);
6213 return None;
6214 }
6215
6216 self.update_visible_inline_completion(window, cx);
6217 provider.refresh(
6218 self.project.clone(),
6219 buffer,
6220 cursor_buffer_position,
6221 debounce,
6222 cx,
6223 );
6224 Some(())
6225 }
6226
6227 fn show_edit_predictions_in_menu(&self) -> bool {
6228 match self.edit_prediction_settings {
6229 EditPredictionSettings::Disabled => false,
6230 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6231 }
6232 }
6233
6234 pub fn edit_predictions_enabled(&self) -> bool {
6235 match self.edit_prediction_settings {
6236 EditPredictionSettings::Disabled => false,
6237 EditPredictionSettings::Enabled { .. } => true,
6238 }
6239 }
6240
6241 fn edit_prediction_requires_modifier(&self) -> bool {
6242 match self.edit_prediction_settings {
6243 EditPredictionSettings::Disabled => false,
6244 EditPredictionSettings::Enabled {
6245 preview_requires_modifier,
6246 ..
6247 } => preview_requires_modifier,
6248 }
6249 }
6250
6251 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6252 if self.edit_prediction_provider.is_none() {
6253 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6254 } else {
6255 let selection = self.selections.newest_anchor();
6256 let cursor = selection.head();
6257
6258 if let Some((buffer, cursor_buffer_position)) =
6259 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6260 {
6261 self.edit_prediction_settings =
6262 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6263 }
6264 }
6265 }
6266
6267 fn edit_prediction_settings_at_position(
6268 &self,
6269 buffer: &Entity<Buffer>,
6270 buffer_position: language::Anchor,
6271 cx: &App,
6272 ) -> EditPredictionSettings {
6273 if !self.mode.is_full()
6274 || !self.show_inline_completions_override.unwrap_or(true)
6275 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6276 {
6277 return EditPredictionSettings::Disabled;
6278 }
6279
6280 let buffer = buffer.read(cx);
6281
6282 let file = buffer.file();
6283
6284 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6285 return EditPredictionSettings::Disabled;
6286 };
6287
6288 let by_provider = matches!(
6289 self.menu_inline_completions_policy,
6290 MenuInlineCompletionsPolicy::ByProvider
6291 );
6292
6293 let show_in_menu = by_provider
6294 && self
6295 .edit_prediction_provider
6296 .as_ref()
6297 .map_or(false, |provider| {
6298 provider.provider.show_completions_in_menu()
6299 });
6300
6301 let preview_requires_modifier =
6302 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6303
6304 EditPredictionSettings::Enabled {
6305 show_in_menu,
6306 preview_requires_modifier,
6307 }
6308 }
6309
6310 fn should_show_edit_predictions(&self) -> bool {
6311 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6312 }
6313
6314 pub fn edit_prediction_preview_is_active(&self) -> bool {
6315 matches!(
6316 self.edit_prediction_preview,
6317 EditPredictionPreview::Active { .. }
6318 )
6319 }
6320
6321 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6322 let cursor = self.selections.newest_anchor().head();
6323 if let Some((buffer, cursor_position)) =
6324 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6325 {
6326 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6327 } else {
6328 false
6329 }
6330 }
6331
6332 pub fn supports_minimap(&self, cx: &App) -> bool {
6333 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6334 }
6335
6336 fn edit_predictions_enabled_in_buffer(
6337 &self,
6338 buffer: &Entity<Buffer>,
6339 buffer_position: language::Anchor,
6340 cx: &App,
6341 ) -> bool {
6342 maybe!({
6343 if self.read_only(cx) {
6344 return Some(false);
6345 }
6346 let provider = self.edit_prediction_provider()?;
6347 if !provider.is_enabled(&buffer, buffer_position, cx) {
6348 return Some(false);
6349 }
6350 let buffer = buffer.read(cx);
6351 let Some(file) = buffer.file() else {
6352 return Some(true);
6353 };
6354 let settings = all_language_settings(Some(file), cx);
6355 Some(settings.edit_predictions_enabled_for_file(file, cx))
6356 })
6357 .unwrap_or(false)
6358 }
6359
6360 fn cycle_inline_completion(
6361 &mut self,
6362 direction: Direction,
6363 window: &mut Window,
6364 cx: &mut Context<Self>,
6365 ) -> Option<()> {
6366 let provider = self.edit_prediction_provider()?;
6367 let cursor = self.selections.newest_anchor().head();
6368 let (buffer, cursor_buffer_position) =
6369 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6370 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6371 return None;
6372 }
6373
6374 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6375 self.update_visible_inline_completion(window, cx);
6376
6377 Some(())
6378 }
6379
6380 pub fn show_inline_completion(
6381 &mut self,
6382 _: &ShowEditPrediction,
6383 window: &mut Window,
6384 cx: &mut Context<Self>,
6385 ) {
6386 if !self.has_active_inline_completion() {
6387 self.refresh_inline_completion(false, true, window, cx);
6388 return;
6389 }
6390
6391 self.update_visible_inline_completion(window, cx);
6392 }
6393
6394 pub fn display_cursor_names(
6395 &mut self,
6396 _: &DisplayCursorNames,
6397 window: &mut Window,
6398 cx: &mut Context<Self>,
6399 ) {
6400 self.show_cursor_names(window, cx);
6401 }
6402
6403 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6404 self.show_cursor_names = true;
6405 cx.notify();
6406 cx.spawn_in(window, async move |this, cx| {
6407 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6408 this.update(cx, |this, cx| {
6409 this.show_cursor_names = false;
6410 cx.notify()
6411 })
6412 .ok()
6413 })
6414 .detach();
6415 }
6416
6417 pub fn next_edit_prediction(
6418 &mut self,
6419 _: &NextEditPrediction,
6420 window: &mut Window,
6421 cx: &mut Context<Self>,
6422 ) {
6423 if self.has_active_inline_completion() {
6424 self.cycle_inline_completion(Direction::Next, window, cx);
6425 } else {
6426 let is_copilot_disabled = self
6427 .refresh_inline_completion(false, true, window, cx)
6428 .is_none();
6429 if is_copilot_disabled {
6430 cx.propagate();
6431 }
6432 }
6433 }
6434
6435 pub fn previous_edit_prediction(
6436 &mut self,
6437 _: &PreviousEditPrediction,
6438 window: &mut Window,
6439 cx: &mut Context<Self>,
6440 ) {
6441 if self.has_active_inline_completion() {
6442 self.cycle_inline_completion(Direction::Prev, window, cx);
6443 } else {
6444 let is_copilot_disabled = self
6445 .refresh_inline_completion(false, true, window, cx)
6446 .is_none();
6447 if is_copilot_disabled {
6448 cx.propagate();
6449 }
6450 }
6451 }
6452
6453 pub fn accept_edit_prediction(
6454 &mut self,
6455 _: &AcceptEditPrediction,
6456 window: &mut Window,
6457 cx: &mut Context<Self>,
6458 ) {
6459 if self.show_edit_predictions_in_menu() {
6460 self.hide_context_menu(window, cx);
6461 }
6462
6463 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6464 return;
6465 };
6466
6467 self.report_inline_completion_event(
6468 active_inline_completion.completion_id.clone(),
6469 true,
6470 cx,
6471 );
6472
6473 match &active_inline_completion.completion {
6474 InlineCompletion::Move { target, .. } => {
6475 let target = *target;
6476
6477 if let Some(position_map) = &self.last_position_map {
6478 if position_map
6479 .visible_row_range
6480 .contains(&target.to_display_point(&position_map.snapshot).row())
6481 || !self.edit_prediction_requires_modifier()
6482 {
6483 self.unfold_ranges(&[target..target], true, false, cx);
6484 // Note that this is also done in vim's handler of the Tab action.
6485 self.change_selections(
6486 Some(Autoscroll::newest()),
6487 window,
6488 cx,
6489 |selections| {
6490 selections.select_anchor_ranges([target..target]);
6491 },
6492 );
6493 self.clear_row_highlights::<EditPredictionPreview>();
6494
6495 self.edit_prediction_preview
6496 .set_previous_scroll_position(None);
6497 } else {
6498 self.edit_prediction_preview
6499 .set_previous_scroll_position(Some(
6500 position_map.snapshot.scroll_anchor,
6501 ));
6502
6503 self.highlight_rows::<EditPredictionPreview>(
6504 target..target,
6505 cx.theme().colors().editor_highlighted_line_background,
6506 RowHighlightOptions {
6507 autoscroll: true,
6508 ..Default::default()
6509 },
6510 cx,
6511 );
6512 self.request_autoscroll(Autoscroll::fit(), cx);
6513 }
6514 }
6515 }
6516 InlineCompletion::Edit { edits, .. } => {
6517 if let Some(provider) = self.edit_prediction_provider() {
6518 provider.accept(cx);
6519 }
6520
6521 let snapshot = self.buffer.read(cx).snapshot(cx);
6522 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6523
6524 self.buffer.update(cx, |buffer, cx| {
6525 buffer.edit(edits.iter().cloned(), None, cx)
6526 });
6527
6528 self.change_selections(None, window, cx, |s| {
6529 s.select_anchor_ranges([last_edit_end..last_edit_end])
6530 });
6531
6532 self.update_visible_inline_completion(window, cx);
6533 if self.active_inline_completion.is_none() {
6534 self.refresh_inline_completion(true, true, window, cx);
6535 }
6536
6537 cx.notify();
6538 }
6539 }
6540
6541 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6542 }
6543
6544 pub fn accept_partial_inline_completion(
6545 &mut self,
6546 _: &AcceptPartialEditPrediction,
6547 window: &mut Window,
6548 cx: &mut Context<Self>,
6549 ) {
6550 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6551 return;
6552 };
6553 if self.selections.count() != 1 {
6554 return;
6555 }
6556
6557 self.report_inline_completion_event(
6558 active_inline_completion.completion_id.clone(),
6559 true,
6560 cx,
6561 );
6562
6563 match &active_inline_completion.completion {
6564 InlineCompletion::Move { target, .. } => {
6565 let target = *target;
6566 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6567 selections.select_anchor_ranges([target..target]);
6568 });
6569 }
6570 InlineCompletion::Edit { edits, .. } => {
6571 // Find an insertion that starts at the cursor position.
6572 let snapshot = self.buffer.read(cx).snapshot(cx);
6573 let cursor_offset = self.selections.newest::<usize>(cx).head();
6574 let insertion = edits.iter().find_map(|(range, text)| {
6575 let range = range.to_offset(&snapshot);
6576 if range.is_empty() && range.start == cursor_offset {
6577 Some(text)
6578 } else {
6579 None
6580 }
6581 });
6582
6583 if let Some(text) = insertion {
6584 let mut partial_completion = text
6585 .chars()
6586 .by_ref()
6587 .take_while(|c| c.is_alphabetic())
6588 .collect::<String>();
6589 if partial_completion.is_empty() {
6590 partial_completion = text
6591 .chars()
6592 .by_ref()
6593 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6594 .collect::<String>();
6595 }
6596
6597 cx.emit(EditorEvent::InputHandled {
6598 utf16_range_to_replace: None,
6599 text: partial_completion.clone().into(),
6600 });
6601
6602 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6603
6604 self.refresh_inline_completion(true, true, window, cx);
6605 cx.notify();
6606 } else {
6607 self.accept_edit_prediction(&Default::default(), window, cx);
6608 }
6609 }
6610 }
6611 }
6612
6613 fn discard_inline_completion(
6614 &mut self,
6615 should_report_inline_completion_event: bool,
6616 cx: &mut Context<Self>,
6617 ) -> bool {
6618 if should_report_inline_completion_event {
6619 let completion_id = self
6620 .active_inline_completion
6621 .as_ref()
6622 .and_then(|active_completion| active_completion.completion_id.clone());
6623
6624 self.report_inline_completion_event(completion_id, false, cx);
6625 }
6626
6627 if let Some(provider) = self.edit_prediction_provider() {
6628 provider.discard(cx);
6629 }
6630
6631 self.take_active_inline_completion(cx)
6632 }
6633
6634 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6635 let Some(provider) = self.edit_prediction_provider() else {
6636 return;
6637 };
6638
6639 let Some((_, buffer, _)) = self
6640 .buffer
6641 .read(cx)
6642 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6643 else {
6644 return;
6645 };
6646
6647 let extension = buffer
6648 .read(cx)
6649 .file()
6650 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6651
6652 let event_type = match accepted {
6653 true => "Edit Prediction Accepted",
6654 false => "Edit Prediction Discarded",
6655 };
6656 telemetry::event!(
6657 event_type,
6658 provider = provider.name(),
6659 prediction_id = id,
6660 suggestion_accepted = accepted,
6661 file_extension = extension,
6662 );
6663 }
6664
6665 pub fn has_active_inline_completion(&self) -> bool {
6666 self.active_inline_completion.is_some()
6667 }
6668
6669 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6670 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6671 return false;
6672 };
6673
6674 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6675 self.clear_highlights::<InlineCompletionHighlight>(cx);
6676 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6677 true
6678 }
6679
6680 /// Returns true when we're displaying the edit prediction popover below the cursor
6681 /// like we are not previewing and the LSP autocomplete menu is visible
6682 /// or we are in `when_holding_modifier` mode.
6683 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6684 if self.edit_prediction_preview_is_active()
6685 || !self.show_edit_predictions_in_menu()
6686 || !self.edit_predictions_enabled()
6687 {
6688 return false;
6689 }
6690
6691 if self.has_visible_completions_menu() {
6692 return true;
6693 }
6694
6695 has_completion && self.edit_prediction_requires_modifier()
6696 }
6697
6698 fn handle_modifiers_changed(
6699 &mut self,
6700 modifiers: Modifiers,
6701 position_map: &PositionMap,
6702 window: &mut Window,
6703 cx: &mut Context<Self>,
6704 ) {
6705 if self.show_edit_predictions_in_menu() {
6706 self.update_edit_prediction_preview(&modifiers, window, cx);
6707 }
6708
6709 self.update_selection_mode(&modifiers, position_map, window, cx);
6710
6711 let mouse_position = window.mouse_position();
6712 if !position_map.text_hitbox.is_hovered(window) {
6713 return;
6714 }
6715
6716 self.update_hovered_link(
6717 position_map.point_for_position(mouse_position),
6718 &position_map.snapshot,
6719 modifiers,
6720 window,
6721 cx,
6722 )
6723 }
6724
6725 fn update_selection_mode(
6726 &mut self,
6727 modifiers: &Modifiers,
6728 position_map: &PositionMap,
6729 window: &mut Window,
6730 cx: &mut Context<Self>,
6731 ) {
6732 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6733 return;
6734 }
6735
6736 let mouse_position = window.mouse_position();
6737 let point_for_position = position_map.point_for_position(mouse_position);
6738 let position = point_for_position.previous_valid;
6739
6740 self.select(
6741 SelectPhase::BeginColumnar {
6742 position,
6743 reset: false,
6744 goal_column: point_for_position.exact_unclipped.column(),
6745 },
6746 window,
6747 cx,
6748 );
6749 }
6750
6751 fn update_edit_prediction_preview(
6752 &mut self,
6753 modifiers: &Modifiers,
6754 window: &mut Window,
6755 cx: &mut Context<Self>,
6756 ) {
6757 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6758 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6759 return;
6760 };
6761
6762 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6763 if matches!(
6764 self.edit_prediction_preview,
6765 EditPredictionPreview::Inactive { .. }
6766 ) {
6767 self.edit_prediction_preview = EditPredictionPreview::Active {
6768 previous_scroll_position: None,
6769 since: Instant::now(),
6770 };
6771
6772 self.update_visible_inline_completion(window, cx);
6773 cx.notify();
6774 }
6775 } else if let EditPredictionPreview::Active {
6776 previous_scroll_position,
6777 since,
6778 } = self.edit_prediction_preview
6779 {
6780 if let (Some(previous_scroll_position), Some(position_map)) =
6781 (previous_scroll_position, self.last_position_map.as_ref())
6782 {
6783 self.set_scroll_position(
6784 previous_scroll_position
6785 .scroll_position(&position_map.snapshot.display_snapshot),
6786 window,
6787 cx,
6788 );
6789 }
6790
6791 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6792 released_too_fast: since.elapsed() < Duration::from_millis(200),
6793 };
6794 self.clear_row_highlights::<EditPredictionPreview>();
6795 self.update_visible_inline_completion(window, cx);
6796 cx.notify();
6797 }
6798 }
6799
6800 fn update_visible_inline_completion(
6801 &mut self,
6802 _window: &mut Window,
6803 cx: &mut Context<Self>,
6804 ) -> Option<()> {
6805 let selection = self.selections.newest_anchor();
6806 let cursor = selection.head();
6807 let multibuffer = self.buffer.read(cx).snapshot(cx);
6808 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6809 let excerpt_id = cursor.excerpt_id;
6810
6811 let show_in_menu = self.show_edit_predictions_in_menu();
6812 let completions_menu_has_precedence = !show_in_menu
6813 && (self.context_menu.borrow().is_some()
6814 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6815
6816 if completions_menu_has_precedence
6817 || !offset_selection.is_empty()
6818 || self
6819 .active_inline_completion
6820 .as_ref()
6821 .map_or(false, |completion| {
6822 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6823 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6824 !invalidation_range.contains(&offset_selection.head())
6825 })
6826 {
6827 self.discard_inline_completion(false, cx);
6828 return None;
6829 }
6830
6831 self.take_active_inline_completion(cx);
6832 let Some(provider) = self.edit_prediction_provider() else {
6833 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6834 return None;
6835 };
6836
6837 let (buffer, cursor_buffer_position) =
6838 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6839
6840 self.edit_prediction_settings =
6841 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6842
6843 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6844
6845 if self.edit_prediction_indent_conflict {
6846 let cursor_point = cursor.to_point(&multibuffer);
6847
6848 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6849
6850 if let Some((_, indent)) = indents.iter().next() {
6851 if indent.len == cursor_point.column {
6852 self.edit_prediction_indent_conflict = false;
6853 }
6854 }
6855 }
6856
6857 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6858 let edits = inline_completion
6859 .edits
6860 .into_iter()
6861 .flat_map(|(range, new_text)| {
6862 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6863 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6864 Some((start..end, new_text))
6865 })
6866 .collect::<Vec<_>>();
6867 if edits.is_empty() {
6868 return None;
6869 }
6870
6871 let first_edit_start = edits.first().unwrap().0.start;
6872 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6873 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6874
6875 let last_edit_end = edits.last().unwrap().0.end;
6876 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6877 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6878
6879 let cursor_row = cursor.to_point(&multibuffer).row;
6880
6881 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6882
6883 let mut inlay_ids = Vec::new();
6884 let invalidation_row_range;
6885 let move_invalidation_row_range = if cursor_row < edit_start_row {
6886 Some(cursor_row..edit_end_row)
6887 } else if cursor_row > edit_end_row {
6888 Some(edit_start_row..cursor_row)
6889 } else {
6890 None
6891 };
6892 let is_move =
6893 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6894 let completion = if is_move {
6895 invalidation_row_range =
6896 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6897 let target = first_edit_start;
6898 InlineCompletion::Move { target, snapshot }
6899 } else {
6900 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6901 && !self.inline_completions_hidden_for_vim_mode;
6902
6903 if show_completions_in_buffer {
6904 if edits
6905 .iter()
6906 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6907 {
6908 let mut inlays = Vec::new();
6909 for (range, new_text) in &edits {
6910 let inlay = Inlay::inline_completion(
6911 post_inc(&mut self.next_inlay_id),
6912 range.start,
6913 new_text.as_str(),
6914 );
6915 inlay_ids.push(inlay.id);
6916 inlays.push(inlay);
6917 }
6918
6919 self.splice_inlays(&[], inlays, cx);
6920 } else {
6921 let background_color = cx.theme().status().deleted_background;
6922 self.highlight_text::<InlineCompletionHighlight>(
6923 edits.iter().map(|(range, _)| range.clone()).collect(),
6924 HighlightStyle {
6925 background_color: Some(background_color),
6926 ..Default::default()
6927 },
6928 cx,
6929 );
6930 }
6931 }
6932
6933 invalidation_row_range = edit_start_row..edit_end_row;
6934
6935 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6936 if provider.show_tab_accept_marker() {
6937 EditDisplayMode::TabAccept
6938 } else {
6939 EditDisplayMode::Inline
6940 }
6941 } else {
6942 EditDisplayMode::DiffPopover
6943 };
6944
6945 InlineCompletion::Edit {
6946 edits,
6947 edit_preview: inline_completion.edit_preview,
6948 display_mode,
6949 snapshot,
6950 }
6951 };
6952
6953 let invalidation_range = multibuffer
6954 .anchor_before(Point::new(invalidation_row_range.start, 0))
6955 ..multibuffer.anchor_after(Point::new(
6956 invalidation_row_range.end,
6957 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6958 ));
6959
6960 self.stale_inline_completion_in_menu = None;
6961 self.active_inline_completion = Some(InlineCompletionState {
6962 inlay_ids,
6963 completion,
6964 completion_id: inline_completion.id,
6965 invalidation_range,
6966 });
6967
6968 cx.notify();
6969
6970 Some(())
6971 }
6972
6973 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6974 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6975 }
6976
6977 fn clear_tasks(&mut self) {
6978 self.tasks.clear()
6979 }
6980
6981 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6982 if self.tasks.insert(key, value).is_some() {
6983 // This case should hopefully be rare, but just in case...
6984 log::error!(
6985 "multiple different run targets found on a single line, only the last target will be rendered"
6986 )
6987 }
6988 }
6989
6990 /// Get all display points of breakpoints that will be rendered within editor
6991 ///
6992 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6993 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6994 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6995 fn active_breakpoints(
6996 &self,
6997 range: Range<DisplayRow>,
6998 window: &mut Window,
6999 cx: &mut Context<Self>,
7000 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
7001 let mut breakpoint_display_points = HashMap::default();
7002
7003 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7004 return breakpoint_display_points;
7005 };
7006
7007 let snapshot = self.snapshot(window, cx);
7008
7009 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7010 let Some(project) = self.project.as_ref() else {
7011 return breakpoint_display_points;
7012 };
7013
7014 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7015 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7016
7017 for (buffer_snapshot, range, excerpt_id) in
7018 multi_buffer_snapshot.range_to_buffer_ranges(range)
7019 {
7020 let Some(buffer) = project.read_with(cx, |this, cx| {
7021 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7022 }) else {
7023 continue;
7024 };
7025 let breakpoints = breakpoint_store.read(cx).breakpoints(
7026 &buffer,
7027 Some(
7028 buffer_snapshot.anchor_before(range.start)
7029 ..buffer_snapshot.anchor_after(range.end),
7030 ),
7031 buffer_snapshot,
7032 cx,
7033 );
7034 for (anchor, breakpoint) in breakpoints {
7035 let multi_buffer_anchor =
7036 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
7037 let position = multi_buffer_anchor
7038 .to_point(&multi_buffer_snapshot)
7039 .to_display_point(&snapshot);
7040
7041 breakpoint_display_points
7042 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
7043 }
7044 }
7045
7046 breakpoint_display_points
7047 }
7048
7049 fn breakpoint_context_menu(
7050 &self,
7051 anchor: Anchor,
7052 window: &mut Window,
7053 cx: &mut Context<Self>,
7054 ) -> Entity<ui::ContextMenu> {
7055 let weak_editor = cx.weak_entity();
7056 let focus_handle = self.focus_handle(cx);
7057
7058 let row = self
7059 .buffer
7060 .read(cx)
7061 .snapshot(cx)
7062 .summary_for_anchor::<Point>(&anchor)
7063 .row;
7064
7065 let breakpoint = self
7066 .breakpoint_at_row(row, window, cx)
7067 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7068
7069 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7070 "Edit Log Breakpoint"
7071 } else {
7072 "Set Log Breakpoint"
7073 };
7074
7075 let condition_breakpoint_msg = if breakpoint
7076 .as_ref()
7077 .is_some_and(|bp| bp.1.condition.is_some())
7078 {
7079 "Edit Condition Breakpoint"
7080 } else {
7081 "Set Condition Breakpoint"
7082 };
7083
7084 let hit_condition_breakpoint_msg = if breakpoint
7085 .as_ref()
7086 .is_some_and(|bp| bp.1.hit_condition.is_some())
7087 {
7088 "Edit Hit Condition Breakpoint"
7089 } else {
7090 "Set Hit Condition Breakpoint"
7091 };
7092
7093 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7094 "Unset Breakpoint"
7095 } else {
7096 "Set Breakpoint"
7097 };
7098
7099 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7100 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7101
7102 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7103 BreakpointState::Enabled => Some("Disable"),
7104 BreakpointState::Disabled => Some("Enable"),
7105 });
7106
7107 let (anchor, breakpoint) =
7108 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7109
7110 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7111 menu.on_blur_subscription(Subscription::new(|| {}))
7112 .context(focus_handle)
7113 .when(run_to_cursor, |this| {
7114 let weak_editor = weak_editor.clone();
7115 this.entry("Run to cursor", None, move |window, cx| {
7116 weak_editor
7117 .update(cx, |editor, cx| {
7118 editor.change_selections(None, window, cx, |s| {
7119 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7120 });
7121 })
7122 .ok();
7123
7124 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7125 })
7126 .separator()
7127 })
7128 .when_some(toggle_state_msg, |this, msg| {
7129 this.entry(msg, None, {
7130 let weak_editor = weak_editor.clone();
7131 let breakpoint = breakpoint.clone();
7132 move |_window, cx| {
7133 weak_editor
7134 .update(cx, |this, cx| {
7135 this.edit_breakpoint_at_anchor(
7136 anchor,
7137 breakpoint.as_ref().clone(),
7138 BreakpointEditAction::InvertState,
7139 cx,
7140 );
7141 })
7142 .log_err();
7143 }
7144 })
7145 })
7146 .entry(set_breakpoint_msg, None, {
7147 let weak_editor = weak_editor.clone();
7148 let breakpoint = breakpoint.clone();
7149 move |_window, cx| {
7150 weak_editor
7151 .update(cx, |this, cx| {
7152 this.edit_breakpoint_at_anchor(
7153 anchor,
7154 breakpoint.as_ref().clone(),
7155 BreakpointEditAction::Toggle,
7156 cx,
7157 );
7158 })
7159 .log_err();
7160 }
7161 })
7162 .entry(log_breakpoint_msg, None, {
7163 let breakpoint = breakpoint.clone();
7164 let weak_editor = weak_editor.clone();
7165 move |window, cx| {
7166 weak_editor
7167 .update(cx, |this, cx| {
7168 this.add_edit_breakpoint_block(
7169 anchor,
7170 breakpoint.as_ref(),
7171 BreakpointPromptEditAction::Log,
7172 window,
7173 cx,
7174 );
7175 })
7176 .log_err();
7177 }
7178 })
7179 .entry(condition_breakpoint_msg, None, {
7180 let breakpoint = breakpoint.clone();
7181 let weak_editor = weak_editor.clone();
7182 move |window, cx| {
7183 weak_editor
7184 .update(cx, |this, cx| {
7185 this.add_edit_breakpoint_block(
7186 anchor,
7187 breakpoint.as_ref(),
7188 BreakpointPromptEditAction::Condition,
7189 window,
7190 cx,
7191 );
7192 })
7193 .log_err();
7194 }
7195 })
7196 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7197 weak_editor
7198 .update(cx, |this, cx| {
7199 this.add_edit_breakpoint_block(
7200 anchor,
7201 breakpoint.as_ref(),
7202 BreakpointPromptEditAction::HitCondition,
7203 window,
7204 cx,
7205 );
7206 })
7207 .log_err();
7208 })
7209 })
7210 }
7211
7212 fn render_breakpoint(
7213 &self,
7214 position: Anchor,
7215 row: DisplayRow,
7216 breakpoint: &Breakpoint,
7217 cx: &mut Context<Self>,
7218 ) -> IconButton {
7219 // Is it a breakpoint that shows up when hovering over gutter?
7220 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7221 (false, false),
7222 |PhantomBreakpointIndicator {
7223 is_active,
7224 display_row,
7225 collides_with_existing_breakpoint,
7226 }| {
7227 (
7228 is_active && display_row == row,
7229 collides_with_existing_breakpoint,
7230 )
7231 },
7232 );
7233
7234 let (color, icon) = {
7235 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7236 (false, false) => ui::IconName::DebugBreakpoint,
7237 (true, false) => ui::IconName::DebugLogBreakpoint,
7238 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7239 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7240 };
7241
7242 let color = if is_phantom {
7243 Color::Hint
7244 } else {
7245 Color::Debugger
7246 };
7247
7248 (color, icon)
7249 };
7250
7251 let breakpoint = Arc::from(breakpoint.clone());
7252
7253 let alt_as_text = gpui::Keystroke {
7254 modifiers: Modifiers::secondary_key(),
7255 ..Default::default()
7256 };
7257 let primary_action_text = if breakpoint.is_disabled() {
7258 "enable"
7259 } else if is_phantom && !collides_with_existing {
7260 "set"
7261 } else {
7262 "unset"
7263 };
7264 let mut primary_text = format!("Click to {primary_action_text}");
7265 if collides_with_existing && !breakpoint.is_disabled() {
7266 use std::fmt::Write;
7267 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7268 }
7269 let primary_text = SharedString::from(primary_text);
7270 let focus_handle = self.focus_handle.clone();
7271 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7272 .icon_size(IconSize::XSmall)
7273 .size(ui::ButtonSize::None)
7274 .icon_color(color)
7275 .style(ButtonStyle::Transparent)
7276 .on_click(cx.listener({
7277 let breakpoint = breakpoint.clone();
7278
7279 move |editor, event: &ClickEvent, window, cx| {
7280 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7281 BreakpointEditAction::InvertState
7282 } else {
7283 BreakpointEditAction::Toggle
7284 };
7285
7286 window.focus(&editor.focus_handle(cx));
7287 editor.edit_breakpoint_at_anchor(
7288 position,
7289 breakpoint.as_ref().clone(),
7290 edit_action,
7291 cx,
7292 );
7293 }
7294 }))
7295 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7296 editor.set_breakpoint_context_menu(
7297 row,
7298 Some(position),
7299 event.down.position,
7300 window,
7301 cx,
7302 );
7303 }))
7304 .tooltip(move |window, cx| {
7305 Tooltip::with_meta_in(
7306 primary_text.clone(),
7307 None,
7308 "Right-click for more options",
7309 &focus_handle,
7310 window,
7311 cx,
7312 )
7313 })
7314 }
7315
7316 fn build_tasks_context(
7317 project: &Entity<Project>,
7318 buffer: &Entity<Buffer>,
7319 buffer_row: u32,
7320 tasks: &Arc<RunnableTasks>,
7321 cx: &mut Context<Self>,
7322 ) -> Task<Option<task::TaskContext>> {
7323 let position = Point::new(buffer_row, tasks.column);
7324 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7325 let location = Location {
7326 buffer: buffer.clone(),
7327 range: range_start..range_start,
7328 };
7329 // Fill in the environmental variables from the tree-sitter captures
7330 let mut captured_task_variables = TaskVariables::default();
7331 for (capture_name, value) in tasks.extra_variables.clone() {
7332 captured_task_variables.insert(
7333 task::VariableName::Custom(capture_name.into()),
7334 value.clone(),
7335 );
7336 }
7337 project.update(cx, |project, cx| {
7338 project.task_store().update(cx, |task_store, cx| {
7339 task_store.task_context_for_location(captured_task_variables, location, cx)
7340 })
7341 })
7342 }
7343
7344 pub fn spawn_nearest_task(
7345 &mut self,
7346 action: &SpawnNearestTask,
7347 window: &mut Window,
7348 cx: &mut Context<Self>,
7349 ) {
7350 let Some((workspace, _)) = self.workspace.clone() else {
7351 return;
7352 };
7353 let Some(project) = self.project.clone() else {
7354 return;
7355 };
7356
7357 // Try to find a closest, enclosing node using tree-sitter that has a
7358 // task
7359 let Some((buffer, buffer_row, tasks)) = self
7360 .find_enclosing_node_task(cx)
7361 // Or find the task that's closest in row-distance.
7362 .or_else(|| self.find_closest_task(cx))
7363 else {
7364 return;
7365 };
7366
7367 let reveal_strategy = action.reveal;
7368 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7369 cx.spawn_in(window, async move |_, cx| {
7370 let context = task_context.await?;
7371 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7372
7373 let resolved = &mut resolved_task.resolved;
7374 resolved.reveal = reveal_strategy;
7375
7376 workspace
7377 .update_in(cx, |workspace, window, cx| {
7378 workspace.schedule_resolved_task(
7379 task_source_kind,
7380 resolved_task,
7381 false,
7382 window,
7383 cx,
7384 );
7385 })
7386 .ok()
7387 })
7388 .detach();
7389 }
7390
7391 fn find_closest_task(
7392 &mut self,
7393 cx: &mut Context<Self>,
7394 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7395 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7396
7397 let ((buffer_id, row), tasks) = self
7398 .tasks
7399 .iter()
7400 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7401
7402 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7403 let tasks = Arc::new(tasks.to_owned());
7404 Some((buffer, *row, tasks))
7405 }
7406
7407 fn find_enclosing_node_task(
7408 &mut self,
7409 cx: &mut Context<Self>,
7410 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7411 let snapshot = self.buffer.read(cx).snapshot(cx);
7412 let offset = self.selections.newest::<usize>(cx).head();
7413 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7414 let buffer_id = excerpt.buffer().remote_id();
7415
7416 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7417 let mut cursor = layer.node().walk();
7418
7419 while cursor.goto_first_child_for_byte(offset).is_some() {
7420 if cursor.node().end_byte() == offset {
7421 cursor.goto_next_sibling();
7422 }
7423 }
7424
7425 // Ascend to the smallest ancestor that contains the range and has a task.
7426 loop {
7427 let node = cursor.node();
7428 let node_range = node.byte_range();
7429 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7430
7431 // Check if this node contains our offset
7432 if node_range.start <= offset && node_range.end >= offset {
7433 // If it contains offset, check for task
7434 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7435 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7436 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7437 }
7438 }
7439
7440 if !cursor.goto_parent() {
7441 break;
7442 }
7443 }
7444 None
7445 }
7446
7447 fn render_run_indicator(
7448 &self,
7449 _style: &EditorStyle,
7450 is_active: bool,
7451 row: DisplayRow,
7452 breakpoint: Option<(Anchor, Breakpoint)>,
7453 cx: &mut Context<Self>,
7454 ) -> IconButton {
7455 let color = Color::Muted;
7456 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7457
7458 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7459 .shape(ui::IconButtonShape::Square)
7460 .icon_size(IconSize::XSmall)
7461 .icon_color(color)
7462 .toggle_state(is_active)
7463 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7464 let quick_launch = e.down.button == MouseButton::Left;
7465 window.focus(&editor.focus_handle(cx));
7466 editor.toggle_code_actions(
7467 &ToggleCodeActions {
7468 deployed_from_indicator: Some(row),
7469 quick_launch,
7470 },
7471 window,
7472 cx,
7473 );
7474 }))
7475 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7476 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7477 }))
7478 }
7479
7480 pub fn context_menu_visible(&self) -> bool {
7481 !self.edit_prediction_preview_is_active()
7482 && self
7483 .context_menu
7484 .borrow()
7485 .as_ref()
7486 .map_or(false, |menu| menu.visible())
7487 }
7488
7489 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7490 self.context_menu
7491 .borrow()
7492 .as_ref()
7493 .map(|menu| menu.origin())
7494 }
7495
7496 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7497 self.context_menu_options = Some(options);
7498 }
7499
7500 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7501 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7502
7503 fn render_edit_prediction_popover(
7504 &mut self,
7505 text_bounds: &Bounds<Pixels>,
7506 content_origin: gpui::Point<Pixels>,
7507 right_margin: Pixels,
7508 editor_snapshot: &EditorSnapshot,
7509 visible_row_range: Range<DisplayRow>,
7510 scroll_top: f32,
7511 scroll_bottom: f32,
7512 line_layouts: &[LineWithInvisibles],
7513 line_height: Pixels,
7514 scroll_pixel_position: gpui::Point<Pixels>,
7515 newest_selection_head: Option<DisplayPoint>,
7516 editor_width: Pixels,
7517 style: &EditorStyle,
7518 window: &mut Window,
7519 cx: &mut App,
7520 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7521 if self.mode().is_minimap() {
7522 return None;
7523 }
7524 let active_inline_completion = self.active_inline_completion.as_ref()?;
7525
7526 if self.edit_prediction_visible_in_cursor_popover(true) {
7527 return None;
7528 }
7529
7530 match &active_inline_completion.completion {
7531 InlineCompletion::Move { target, .. } => {
7532 let target_display_point = target.to_display_point(editor_snapshot);
7533
7534 if self.edit_prediction_requires_modifier() {
7535 if !self.edit_prediction_preview_is_active() {
7536 return None;
7537 }
7538
7539 self.render_edit_prediction_modifier_jump_popover(
7540 text_bounds,
7541 content_origin,
7542 visible_row_range,
7543 line_layouts,
7544 line_height,
7545 scroll_pixel_position,
7546 newest_selection_head,
7547 target_display_point,
7548 window,
7549 cx,
7550 )
7551 } else {
7552 self.render_edit_prediction_eager_jump_popover(
7553 text_bounds,
7554 content_origin,
7555 editor_snapshot,
7556 visible_row_range,
7557 scroll_top,
7558 scroll_bottom,
7559 line_height,
7560 scroll_pixel_position,
7561 target_display_point,
7562 editor_width,
7563 window,
7564 cx,
7565 )
7566 }
7567 }
7568 InlineCompletion::Edit {
7569 display_mode: EditDisplayMode::Inline,
7570 ..
7571 } => None,
7572 InlineCompletion::Edit {
7573 display_mode: EditDisplayMode::TabAccept,
7574 edits,
7575 ..
7576 } => {
7577 let range = &edits.first()?.0;
7578 let target_display_point = range.end.to_display_point(editor_snapshot);
7579
7580 self.render_edit_prediction_end_of_line_popover(
7581 "Accept",
7582 editor_snapshot,
7583 visible_row_range,
7584 target_display_point,
7585 line_height,
7586 scroll_pixel_position,
7587 content_origin,
7588 editor_width,
7589 window,
7590 cx,
7591 )
7592 }
7593 InlineCompletion::Edit {
7594 edits,
7595 edit_preview,
7596 display_mode: EditDisplayMode::DiffPopover,
7597 snapshot,
7598 } => self.render_edit_prediction_diff_popover(
7599 text_bounds,
7600 content_origin,
7601 right_margin,
7602 editor_snapshot,
7603 visible_row_range,
7604 line_layouts,
7605 line_height,
7606 scroll_pixel_position,
7607 newest_selection_head,
7608 editor_width,
7609 style,
7610 edits,
7611 edit_preview,
7612 snapshot,
7613 window,
7614 cx,
7615 ),
7616 }
7617 }
7618
7619 fn render_edit_prediction_modifier_jump_popover(
7620 &mut self,
7621 text_bounds: &Bounds<Pixels>,
7622 content_origin: gpui::Point<Pixels>,
7623 visible_row_range: Range<DisplayRow>,
7624 line_layouts: &[LineWithInvisibles],
7625 line_height: Pixels,
7626 scroll_pixel_position: gpui::Point<Pixels>,
7627 newest_selection_head: Option<DisplayPoint>,
7628 target_display_point: DisplayPoint,
7629 window: &mut Window,
7630 cx: &mut App,
7631 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7632 let scrolled_content_origin =
7633 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7634
7635 const SCROLL_PADDING_Y: Pixels = px(12.);
7636
7637 if target_display_point.row() < visible_row_range.start {
7638 return self.render_edit_prediction_scroll_popover(
7639 |_| SCROLL_PADDING_Y,
7640 IconName::ArrowUp,
7641 visible_row_range,
7642 line_layouts,
7643 newest_selection_head,
7644 scrolled_content_origin,
7645 window,
7646 cx,
7647 );
7648 } else if target_display_point.row() >= visible_row_range.end {
7649 return self.render_edit_prediction_scroll_popover(
7650 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7651 IconName::ArrowDown,
7652 visible_row_range,
7653 line_layouts,
7654 newest_selection_head,
7655 scrolled_content_origin,
7656 window,
7657 cx,
7658 );
7659 }
7660
7661 const POLE_WIDTH: Pixels = px(2.);
7662
7663 let line_layout =
7664 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7665 let target_column = target_display_point.column() as usize;
7666
7667 let target_x = line_layout.x_for_index(target_column);
7668 let target_y =
7669 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7670
7671 let flag_on_right = target_x < text_bounds.size.width / 2.;
7672
7673 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7674 border_color.l += 0.001;
7675
7676 let mut element = v_flex()
7677 .items_end()
7678 .when(flag_on_right, |el| el.items_start())
7679 .child(if flag_on_right {
7680 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7681 .rounded_bl(px(0.))
7682 .rounded_tl(px(0.))
7683 .border_l_2()
7684 .border_color(border_color)
7685 } else {
7686 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7687 .rounded_br(px(0.))
7688 .rounded_tr(px(0.))
7689 .border_r_2()
7690 .border_color(border_color)
7691 })
7692 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7693 .into_any();
7694
7695 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7696
7697 let mut origin = scrolled_content_origin + point(target_x, target_y)
7698 - point(
7699 if flag_on_right {
7700 POLE_WIDTH
7701 } else {
7702 size.width - POLE_WIDTH
7703 },
7704 size.height - line_height,
7705 );
7706
7707 origin.x = origin.x.max(content_origin.x);
7708
7709 element.prepaint_at(origin, window, cx);
7710
7711 Some((element, origin))
7712 }
7713
7714 fn render_edit_prediction_scroll_popover(
7715 &mut self,
7716 to_y: impl Fn(Size<Pixels>) -> Pixels,
7717 scroll_icon: IconName,
7718 visible_row_range: Range<DisplayRow>,
7719 line_layouts: &[LineWithInvisibles],
7720 newest_selection_head: Option<DisplayPoint>,
7721 scrolled_content_origin: gpui::Point<Pixels>,
7722 window: &mut Window,
7723 cx: &mut App,
7724 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7725 let mut element = self
7726 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7727 .into_any();
7728
7729 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7730
7731 let cursor = newest_selection_head?;
7732 let cursor_row_layout =
7733 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7734 let cursor_column = cursor.column() as usize;
7735
7736 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7737
7738 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7739
7740 element.prepaint_at(origin, window, cx);
7741 Some((element, origin))
7742 }
7743
7744 fn render_edit_prediction_eager_jump_popover(
7745 &mut self,
7746 text_bounds: &Bounds<Pixels>,
7747 content_origin: gpui::Point<Pixels>,
7748 editor_snapshot: &EditorSnapshot,
7749 visible_row_range: Range<DisplayRow>,
7750 scroll_top: f32,
7751 scroll_bottom: f32,
7752 line_height: Pixels,
7753 scroll_pixel_position: gpui::Point<Pixels>,
7754 target_display_point: DisplayPoint,
7755 editor_width: Pixels,
7756 window: &mut Window,
7757 cx: &mut App,
7758 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7759 if target_display_point.row().as_f32() < scroll_top {
7760 let mut element = self
7761 .render_edit_prediction_line_popover(
7762 "Jump to Edit",
7763 Some(IconName::ArrowUp),
7764 window,
7765 cx,
7766 )?
7767 .into_any();
7768
7769 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7770 let offset = point(
7771 (text_bounds.size.width - size.width) / 2.,
7772 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7773 );
7774
7775 let origin = text_bounds.origin + offset;
7776 element.prepaint_at(origin, window, cx);
7777 Some((element, origin))
7778 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7779 let mut element = self
7780 .render_edit_prediction_line_popover(
7781 "Jump to Edit",
7782 Some(IconName::ArrowDown),
7783 window,
7784 cx,
7785 )?
7786 .into_any();
7787
7788 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7789 let offset = point(
7790 (text_bounds.size.width - size.width) / 2.,
7791 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7792 );
7793
7794 let origin = text_bounds.origin + offset;
7795 element.prepaint_at(origin, window, cx);
7796 Some((element, origin))
7797 } else {
7798 self.render_edit_prediction_end_of_line_popover(
7799 "Jump to Edit",
7800 editor_snapshot,
7801 visible_row_range,
7802 target_display_point,
7803 line_height,
7804 scroll_pixel_position,
7805 content_origin,
7806 editor_width,
7807 window,
7808 cx,
7809 )
7810 }
7811 }
7812
7813 fn render_edit_prediction_end_of_line_popover(
7814 self: &mut Editor,
7815 label: &'static str,
7816 editor_snapshot: &EditorSnapshot,
7817 visible_row_range: Range<DisplayRow>,
7818 target_display_point: DisplayPoint,
7819 line_height: Pixels,
7820 scroll_pixel_position: gpui::Point<Pixels>,
7821 content_origin: gpui::Point<Pixels>,
7822 editor_width: Pixels,
7823 window: &mut Window,
7824 cx: &mut App,
7825 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7826 let target_line_end = DisplayPoint::new(
7827 target_display_point.row(),
7828 editor_snapshot.line_len(target_display_point.row()),
7829 );
7830
7831 let mut element = self
7832 .render_edit_prediction_line_popover(label, None, window, cx)?
7833 .into_any();
7834
7835 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7836
7837 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7838
7839 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7840 let mut origin = start_point
7841 + line_origin
7842 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7843 origin.x = origin.x.max(content_origin.x);
7844
7845 let max_x = content_origin.x + editor_width - size.width;
7846
7847 if origin.x > max_x {
7848 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7849
7850 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7851 origin.y += offset;
7852 IconName::ArrowUp
7853 } else {
7854 origin.y -= offset;
7855 IconName::ArrowDown
7856 };
7857
7858 element = self
7859 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7860 .into_any();
7861
7862 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7863
7864 origin.x = content_origin.x + editor_width - size.width - px(2.);
7865 }
7866
7867 element.prepaint_at(origin, window, cx);
7868 Some((element, origin))
7869 }
7870
7871 fn render_edit_prediction_diff_popover(
7872 self: &Editor,
7873 text_bounds: &Bounds<Pixels>,
7874 content_origin: gpui::Point<Pixels>,
7875 right_margin: Pixels,
7876 editor_snapshot: &EditorSnapshot,
7877 visible_row_range: Range<DisplayRow>,
7878 line_layouts: &[LineWithInvisibles],
7879 line_height: Pixels,
7880 scroll_pixel_position: gpui::Point<Pixels>,
7881 newest_selection_head: Option<DisplayPoint>,
7882 editor_width: Pixels,
7883 style: &EditorStyle,
7884 edits: &Vec<(Range<Anchor>, String)>,
7885 edit_preview: &Option<language::EditPreview>,
7886 snapshot: &language::BufferSnapshot,
7887 window: &mut Window,
7888 cx: &mut App,
7889 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7890 let edit_start = edits
7891 .first()
7892 .unwrap()
7893 .0
7894 .start
7895 .to_display_point(editor_snapshot);
7896 let edit_end = edits
7897 .last()
7898 .unwrap()
7899 .0
7900 .end
7901 .to_display_point(editor_snapshot);
7902
7903 let is_visible = visible_row_range.contains(&edit_start.row())
7904 || visible_row_range.contains(&edit_end.row());
7905 if !is_visible {
7906 return None;
7907 }
7908
7909 let highlighted_edits =
7910 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7911
7912 let styled_text = highlighted_edits.to_styled_text(&style.text);
7913 let line_count = highlighted_edits.text.lines().count();
7914
7915 const BORDER_WIDTH: Pixels = px(1.);
7916
7917 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7918 let has_keybind = keybind.is_some();
7919
7920 let mut element = h_flex()
7921 .items_start()
7922 .child(
7923 h_flex()
7924 .bg(cx.theme().colors().editor_background)
7925 .border(BORDER_WIDTH)
7926 .shadow_sm()
7927 .border_color(cx.theme().colors().border)
7928 .rounded_l_lg()
7929 .when(line_count > 1, |el| el.rounded_br_lg())
7930 .pr_1()
7931 .child(styled_text),
7932 )
7933 .child(
7934 h_flex()
7935 .h(line_height + BORDER_WIDTH * 2.)
7936 .px_1p5()
7937 .gap_1()
7938 // Workaround: For some reason, there's a gap if we don't do this
7939 .ml(-BORDER_WIDTH)
7940 .shadow(smallvec![gpui::BoxShadow {
7941 color: gpui::black().opacity(0.05),
7942 offset: point(px(1.), px(1.)),
7943 blur_radius: px(2.),
7944 spread_radius: px(0.),
7945 }])
7946 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7947 .border(BORDER_WIDTH)
7948 .border_color(cx.theme().colors().border)
7949 .rounded_r_lg()
7950 .id("edit_prediction_diff_popover_keybind")
7951 .when(!has_keybind, |el| {
7952 let status_colors = cx.theme().status();
7953
7954 el.bg(status_colors.error_background)
7955 .border_color(status_colors.error.opacity(0.6))
7956 .child(Icon::new(IconName::Info).color(Color::Error))
7957 .cursor_default()
7958 .hoverable_tooltip(move |_window, cx| {
7959 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7960 })
7961 })
7962 .children(keybind),
7963 )
7964 .into_any();
7965
7966 let longest_row =
7967 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7968 let longest_line_width = if visible_row_range.contains(&longest_row) {
7969 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7970 } else {
7971 layout_line(
7972 longest_row,
7973 editor_snapshot,
7974 style,
7975 editor_width,
7976 |_| false,
7977 window,
7978 cx,
7979 )
7980 .width
7981 };
7982
7983 let viewport_bounds =
7984 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7985 right: -right_margin,
7986 ..Default::default()
7987 });
7988
7989 let x_after_longest =
7990 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7991 - scroll_pixel_position.x;
7992
7993 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7994
7995 // Fully visible if it can be displayed within the window (allow overlapping other
7996 // panes). However, this is only allowed if the popover starts within text_bounds.
7997 let can_position_to_the_right = x_after_longest < text_bounds.right()
7998 && x_after_longest + element_bounds.width < viewport_bounds.right();
7999
8000 let mut origin = if can_position_to_the_right {
8001 point(
8002 x_after_longest,
8003 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8004 - scroll_pixel_position.y,
8005 )
8006 } else {
8007 let cursor_row = newest_selection_head.map(|head| head.row());
8008 let above_edit = edit_start
8009 .row()
8010 .0
8011 .checked_sub(line_count as u32)
8012 .map(DisplayRow);
8013 let below_edit = Some(edit_end.row() + 1);
8014 let above_cursor =
8015 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8016 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8017
8018 // Place the edit popover adjacent to the edit if there is a location
8019 // available that is onscreen and does not obscure the cursor. Otherwise,
8020 // place it adjacent to the cursor.
8021 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8022 .into_iter()
8023 .flatten()
8024 .find(|&start_row| {
8025 let end_row = start_row + line_count as u32;
8026 visible_row_range.contains(&start_row)
8027 && visible_row_range.contains(&end_row)
8028 && cursor_row.map_or(true, |cursor_row| {
8029 !((start_row..end_row).contains(&cursor_row))
8030 })
8031 })?;
8032
8033 content_origin
8034 + point(
8035 -scroll_pixel_position.x,
8036 row_target.as_f32() * line_height - scroll_pixel_position.y,
8037 )
8038 };
8039
8040 origin.x -= BORDER_WIDTH;
8041
8042 window.defer_draw(element, origin, 1);
8043
8044 // Do not return an element, since it will already be drawn due to defer_draw.
8045 None
8046 }
8047
8048 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8049 px(30.)
8050 }
8051
8052 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8053 if self.read_only(cx) {
8054 cx.theme().players().read_only()
8055 } else {
8056 self.style.as_ref().unwrap().local_player
8057 }
8058 }
8059
8060 fn render_edit_prediction_accept_keybind(
8061 &self,
8062 window: &mut Window,
8063 cx: &App,
8064 ) -> Option<AnyElement> {
8065 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8066 let accept_keystroke = accept_binding.keystroke()?;
8067
8068 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8069
8070 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8071 Color::Accent
8072 } else {
8073 Color::Muted
8074 };
8075
8076 h_flex()
8077 .px_0p5()
8078 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8079 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8080 .text_size(TextSize::XSmall.rems(cx))
8081 .child(h_flex().children(ui::render_modifiers(
8082 &accept_keystroke.modifiers,
8083 PlatformStyle::platform(),
8084 Some(modifiers_color),
8085 Some(IconSize::XSmall.rems().into()),
8086 true,
8087 )))
8088 .when(is_platform_style_mac, |parent| {
8089 parent.child(accept_keystroke.key.clone())
8090 })
8091 .when(!is_platform_style_mac, |parent| {
8092 parent.child(
8093 Key::new(
8094 util::capitalize(&accept_keystroke.key),
8095 Some(Color::Default),
8096 )
8097 .size(Some(IconSize::XSmall.rems().into())),
8098 )
8099 })
8100 .into_any()
8101 .into()
8102 }
8103
8104 fn render_edit_prediction_line_popover(
8105 &self,
8106 label: impl Into<SharedString>,
8107 icon: Option<IconName>,
8108 window: &mut Window,
8109 cx: &App,
8110 ) -> Option<Stateful<Div>> {
8111 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8112
8113 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8114 let has_keybind = keybind.is_some();
8115
8116 let result = h_flex()
8117 .id("ep-line-popover")
8118 .py_0p5()
8119 .pl_1()
8120 .pr(padding_right)
8121 .gap_1()
8122 .rounded_md()
8123 .border_1()
8124 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8125 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8126 .shadow_sm()
8127 .when(!has_keybind, |el| {
8128 let status_colors = cx.theme().status();
8129
8130 el.bg(status_colors.error_background)
8131 .border_color(status_colors.error.opacity(0.6))
8132 .pl_2()
8133 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8134 .cursor_default()
8135 .hoverable_tooltip(move |_window, cx| {
8136 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8137 })
8138 })
8139 .children(keybind)
8140 .child(
8141 Label::new(label)
8142 .size(LabelSize::Small)
8143 .when(!has_keybind, |el| {
8144 el.color(cx.theme().status().error.into()).strikethrough()
8145 }),
8146 )
8147 .when(!has_keybind, |el| {
8148 el.child(
8149 h_flex().ml_1().child(
8150 Icon::new(IconName::Info)
8151 .size(IconSize::Small)
8152 .color(cx.theme().status().error.into()),
8153 ),
8154 )
8155 })
8156 .when_some(icon, |element, icon| {
8157 element.child(
8158 div()
8159 .mt(px(1.5))
8160 .child(Icon::new(icon).size(IconSize::Small)),
8161 )
8162 });
8163
8164 Some(result)
8165 }
8166
8167 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8168 let accent_color = cx.theme().colors().text_accent;
8169 let editor_bg_color = cx.theme().colors().editor_background;
8170 editor_bg_color.blend(accent_color.opacity(0.1))
8171 }
8172
8173 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8174 let accent_color = cx.theme().colors().text_accent;
8175 let editor_bg_color = cx.theme().colors().editor_background;
8176 editor_bg_color.blend(accent_color.opacity(0.6))
8177 }
8178
8179 fn render_edit_prediction_cursor_popover(
8180 &self,
8181 min_width: Pixels,
8182 max_width: Pixels,
8183 cursor_point: Point,
8184 style: &EditorStyle,
8185 accept_keystroke: Option<&gpui::Keystroke>,
8186 _window: &Window,
8187 cx: &mut Context<Editor>,
8188 ) -> Option<AnyElement> {
8189 let provider = self.edit_prediction_provider.as_ref()?;
8190
8191 if provider.provider.needs_terms_acceptance(cx) {
8192 return Some(
8193 h_flex()
8194 .min_w(min_width)
8195 .flex_1()
8196 .px_2()
8197 .py_1()
8198 .gap_3()
8199 .elevation_2(cx)
8200 .hover(|style| style.bg(cx.theme().colors().element_hover))
8201 .id("accept-terms")
8202 .cursor_pointer()
8203 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8204 .on_click(cx.listener(|this, _event, window, cx| {
8205 cx.stop_propagation();
8206 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8207 window.dispatch_action(
8208 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8209 cx,
8210 );
8211 }))
8212 .child(
8213 h_flex()
8214 .flex_1()
8215 .gap_2()
8216 .child(Icon::new(IconName::ZedPredict))
8217 .child(Label::new("Accept Terms of Service"))
8218 .child(div().w_full())
8219 .child(
8220 Icon::new(IconName::ArrowUpRight)
8221 .color(Color::Muted)
8222 .size(IconSize::Small),
8223 )
8224 .into_any_element(),
8225 )
8226 .into_any(),
8227 );
8228 }
8229
8230 let is_refreshing = provider.provider.is_refreshing(cx);
8231
8232 fn pending_completion_container() -> Div {
8233 h_flex()
8234 .h_full()
8235 .flex_1()
8236 .gap_2()
8237 .child(Icon::new(IconName::ZedPredict))
8238 }
8239
8240 let completion = match &self.active_inline_completion {
8241 Some(prediction) => {
8242 if !self.has_visible_completions_menu() {
8243 const RADIUS: Pixels = px(6.);
8244 const BORDER_WIDTH: Pixels = px(1.);
8245
8246 return Some(
8247 h_flex()
8248 .elevation_2(cx)
8249 .border(BORDER_WIDTH)
8250 .border_color(cx.theme().colors().border)
8251 .when(accept_keystroke.is_none(), |el| {
8252 el.border_color(cx.theme().status().error)
8253 })
8254 .rounded(RADIUS)
8255 .rounded_tl(px(0.))
8256 .overflow_hidden()
8257 .child(div().px_1p5().child(match &prediction.completion {
8258 InlineCompletion::Move { target, snapshot } => {
8259 use text::ToPoint as _;
8260 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8261 {
8262 Icon::new(IconName::ZedPredictDown)
8263 } else {
8264 Icon::new(IconName::ZedPredictUp)
8265 }
8266 }
8267 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8268 }))
8269 .child(
8270 h_flex()
8271 .gap_1()
8272 .py_1()
8273 .px_2()
8274 .rounded_r(RADIUS - BORDER_WIDTH)
8275 .border_l_1()
8276 .border_color(cx.theme().colors().border)
8277 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8278 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8279 el.child(
8280 Label::new("Hold")
8281 .size(LabelSize::Small)
8282 .when(accept_keystroke.is_none(), |el| {
8283 el.strikethrough()
8284 })
8285 .line_height_style(LineHeightStyle::UiLabel),
8286 )
8287 })
8288 .id("edit_prediction_cursor_popover_keybind")
8289 .when(accept_keystroke.is_none(), |el| {
8290 let status_colors = cx.theme().status();
8291
8292 el.bg(status_colors.error_background)
8293 .border_color(status_colors.error.opacity(0.6))
8294 .child(Icon::new(IconName::Info).color(Color::Error))
8295 .cursor_default()
8296 .hoverable_tooltip(move |_window, cx| {
8297 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8298 .into()
8299 })
8300 })
8301 .when_some(
8302 accept_keystroke.as_ref(),
8303 |el, accept_keystroke| {
8304 el.child(h_flex().children(ui::render_modifiers(
8305 &accept_keystroke.modifiers,
8306 PlatformStyle::platform(),
8307 Some(Color::Default),
8308 Some(IconSize::XSmall.rems().into()),
8309 false,
8310 )))
8311 },
8312 ),
8313 )
8314 .into_any(),
8315 );
8316 }
8317
8318 self.render_edit_prediction_cursor_popover_preview(
8319 prediction,
8320 cursor_point,
8321 style,
8322 cx,
8323 )?
8324 }
8325
8326 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8327 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8328 stale_completion,
8329 cursor_point,
8330 style,
8331 cx,
8332 )?,
8333
8334 None => {
8335 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8336 }
8337 },
8338
8339 None => pending_completion_container().child(Label::new("No Prediction")),
8340 };
8341
8342 let completion = if is_refreshing {
8343 completion
8344 .with_animation(
8345 "loading-completion",
8346 Animation::new(Duration::from_secs(2))
8347 .repeat()
8348 .with_easing(pulsating_between(0.4, 0.8)),
8349 |label, delta| label.opacity(delta),
8350 )
8351 .into_any_element()
8352 } else {
8353 completion.into_any_element()
8354 };
8355
8356 let has_completion = self.active_inline_completion.is_some();
8357
8358 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8359 Some(
8360 h_flex()
8361 .min_w(min_width)
8362 .max_w(max_width)
8363 .flex_1()
8364 .elevation_2(cx)
8365 .border_color(cx.theme().colors().border)
8366 .child(
8367 div()
8368 .flex_1()
8369 .py_1()
8370 .px_2()
8371 .overflow_hidden()
8372 .child(completion),
8373 )
8374 .when_some(accept_keystroke, |el, accept_keystroke| {
8375 if !accept_keystroke.modifiers.modified() {
8376 return el;
8377 }
8378
8379 el.child(
8380 h_flex()
8381 .h_full()
8382 .border_l_1()
8383 .rounded_r_lg()
8384 .border_color(cx.theme().colors().border)
8385 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8386 .gap_1()
8387 .py_1()
8388 .px_2()
8389 .child(
8390 h_flex()
8391 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8392 .when(is_platform_style_mac, |parent| parent.gap_1())
8393 .child(h_flex().children(ui::render_modifiers(
8394 &accept_keystroke.modifiers,
8395 PlatformStyle::platform(),
8396 Some(if !has_completion {
8397 Color::Muted
8398 } else {
8399 Color::Default
8400 }),
8401 None,
8402 false,
8403 ))),
8404 )
8405 .child(Label::new("Preview").into_any_element())
8406 .opacity(if has_completion { 1.0 } else { 0.4 }),
8407 )
8408 })
8409 .into_any(),
8410 )
8411 }
8412
8413 fn render_edit_prediction_cursor_popover_preview(
8414 &self,
8415 completion: &InlineCompletionState,
8416 cursor_point: Point,
8417 style: &EditorStyle,
8418 cx: &mut Context<Editor>,
8419 ) -> Option<Div> {
8420 use text::ToPoint as _;
8421
8422 fn render_relative_row_jump(
8423 prefix: impl Into<String>,
8424 current_row: u32,
8425 target_row: u32,
8426 ) -> Div {
8427 let (row_diff, arrow) = if target_row < current_row {
8428 (current_row - target_row, IconName::ArrowUp)
8429 } else {
8430 (target_row - current_row, IconName::ArrowDown)
8431 };
8432
8433 h_flex()
8434 .child(
8435 Label::new(format!("{}{}", prefix.into(), row_diff))
8436 .color(Color::Muted)
8437 .size(LabelSize::Small),
8438 )
8439 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8440 }
8441
8442 match &completion.completion {
8443 InlineCompletion::Move {
8444 target, snapshot, ..
8445 } => Some(
8446 h_flex()
8447 .px_2()
8448 .gap_2()
8449 .flex_1()
8450 .child(
8451 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8452 Icon::new(IconName::ZedPredictDown)
8453 } else {
8454 Icon::new(IconName::ZedPredictUp)
8455 },
8456 )
8457 .child(Label::new("Jump to Edit")),
8458 ),
8459
8460 InlineCompletion::Edit {
8461 edits,
8462 edit_preview,
8463 snapshot,
8464 display_mode: _,
8465 } => {
8466 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8467
8468 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8469 &snapshot,
8470 &edits,
8471 edit_preview.as_ref()?,
8472 true,
8473 cx,
8474 )
8475 .first_line_preview();
8476
8477 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8478 .with_default_highlights(&style.text, highlighted_edits.highlights);
8479
8480 let preview = h_flex()
8481 .gap_1()
8482 .min_w_16()
8483 .child(styled_text)
8484 .when(has_more_lines, |parent| parent.child("…"));
8485
8486 let left = if first_edit_row != cursor_point.row {
8487 render_relative_row_jump("", cursor_point.row, first_edit_row)
8488 .into_any_element()
8489 } else {
8490 Icon::new(IconName::ZedPredict).into_any_element()
8491 };
8492
8493 Some(
8494 h_flex()
8495 .h_full()
8496 .flex_1()
8497 .gap_2()
8498 .pr_1()
8499 .overflow_x_hidden()
8500 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8501 .child(left)
8502 .child(preview),
8503 )
8504 }
8505 }
8506 }
8507
8508 fn render_context_menu(
8509 &self,
8510 style: &EditorStyle,
8511 max_height_in_lines: u32,
8512 window: &mut Window,
8513 cx: &mut Context<Editor>,
8514 ) -> Option<AnyElement> {
8515 let menu = self.context_menu.borrow();
8516 let menu = menu.as_ref()?;
8517 if !menu.visible() {
8518 return None;
8519 };
8520 Some(menu.render(style, max_height_in_lines, window, cx))
8521 }
8522
8523 fn render_context_menu_aside(
8524 &mut self,
8525 max_size: Size<Pixels>,
8526 window: &mut Window,
8527 cx: &mut Context<Editor>,
8528 ) -> Option<AnyElement> {
8529 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8530 if menu.visible() {
8531 menu.render_aside(self, max_size, window, cx)
8532 } else {
8533 None
8534 }
8535 })
8536 }
8537
8538 fn hide_context_menu(
8539 &mut self,
8540 window: &mut Window,
8541 cx: &mut Context<Self>,
8542 ) -> Option<CodeContextMenu> {
8543 cx.notify();
8544 self.completion_tasks.clear();
8545 let context_menu = self.context_menu.borrow_mut().take();
8546 self.stale_inline_completion_in_menu.take();
8547 self.update_visible_inline_completion(window, cx);
8548 context_menu
8549 }
8550
8551 fn show_snippet_choices(
8552 &mut self,
8553 choices: &Vec<String>,
8554 selection: Range<Anchor>,
8555 cx: &mut Context<Self>,
8556 ) {
8557 if selection.start.buffer_id.is_none() {
8558 return;
8559 }
8560 let buffer_id = selection.start.buffer_id.unwrap();
8561 let buffer = self.buffer().read(cx).buffer(buffer_id);
8562 let id = post_inc(&mut self.next_completion_id);
8563 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8564
8565 if let Some(buffer) = buffer {
8566 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8567 CompletionsMenu::new_snippet_choices(
8568 id,
8569 true,
8570 choices,
8571 selection,
8572 buffer,
8573 snippet_sort_order,
8574 ),
8575 ));
8576 }
8577 }
8578
8579 pub fn insert_snippet(
8580 &mut self,
8581 insertion_ranges: &[Range<usize>],
8582 snippet: Snippet,
8583 window: &mut Window,
8584 cx: &mut Context<Self>,
8585 ) -> Result<()> {
8586 struct Tabstop<T> {
8587 is_end_tabstop: bool,
8588 ranges: Vec<Range<T>>,
8589 choices: Option<Vec<String>>,
8590 }
8591
8592 let tabstops = self.buffer.update(cx, |buffer, cx| {
8593 let snippet_text: Arc<str> = snippet.text.clone().into();
8594 let edits = insertion_ranges
8595 .iter()
8596 .cloned()
8597 .map(|range| (range, snippet_text.clone()));
8598 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8599
8600 let snapshot = &*buffer.read(cx);
8601 let snippet = &snippet;
8602 snippet
8603 .tabstops
8604 .iter()
8605 .map(|tabstop| {
8606 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8607 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8608 });
8609 let mut tabstop_ranges = tabstop
8610 .ranges
8611 .iter()
8612 .flat_map(|tabstop_range| {
8613 let mut delta = 0_isize;
8614 insertion_ranges.iter().map(move |insertion_range| {
8615 let insertion_start = insertion_range.start as isize + delta;
8616 delta +=
8617 snippet.text.len() as isize - insertion_range.len() as isize;
8618
8619 let start = ((insertion_start + tabstop_range.start) as usize)
8620 .min(snapshot.len());
8621 let end = ((insertion_start + tabstop_range.end) as usize)
8622 .min(snapshot.len());
8623 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8624 })
8625 })
8626 .collect::<Vec<_>>();
8627 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8628
8629 Tabstop {
8630 is_end_tabstop,
8631 ranges: tabstop_ranges,
8632 choices: tabstop.choices.clone(),
8633 }
8634 })
8635 .collect::<Vec<_>>()
8636 });
8637 if let Some(tabstop) = tabstops.first() {
8638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8639 s.select_ranges(tabstop.ranges.iter().cloned());
8640 });
8641
8642 if let Some(choices) = &tabstop.choices {
8643 if let Some(selection) = tabstop.ranges.first() {
8644 self.show_snippet_choices(choices, selection.clone(), cx)
8645 }
8646 }
8647
8648 // If we're already at the last tabstop and it's at the end of the snippet,
8649 // we're done, we don't need to keep the state around.
8650 if !tabstop.is_end_tabstop {
8651 let choices = tabstops
8652 .iter()
8653 .map(|tabstop| tabstop.choices.clone())
8654 .collect();
8655
8656 let ranges = tabstops
8657 .into_iter()
8658 .map(|tabstop| tabstop.ranges)
8659 .collect::<Vec<_>>();
8660
8661 self.snippet_stack.push(SnippetState {
8662 active_index: 0,
8663 ranges,
8664 choices,
8665 });
8666 }
8667
8668 // Check whether the just-entered snippet ends with an auto-closable bracket.
8669 if self.autoclose_regions.is_empty() {
8670 let snapshot = self.buffer.read(cx).snapshot(cx);
8671 for selection in &mut self.selections.all::<Point>(cx) {
8672 let selection_head = selection.head();
8673 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8674 continue;
8675 };
8676
8677 let mut bracket_pair = None;
8678 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8679 let prev_chars = snapshot
8680 .reversed_chars_at(selection_head)
8681 .collect::<String>();
8682 for (pair, enabled) in scope.brackets() {
8683 if enabled
8684 && pair.close
8685 && prev_chars.starts_with(pair.start.as_str())
8686 && next_chars.starts_with(pair.end.as_str())
8687 {
8688 bracket_pair = Some(pair.clone());
8689 break;
8690 }
8691 }
8692 if let Some(pair) = bracket_pair {
8693 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8694 let autoclose_enabled =
8695 self.use_autoclose && snapshot_settings.use_autoclose;
8696 if autoclose_enabled {
8697 let start = snapshot.anchor_after(selection_head);
8698 let end = snapshot.anchor_after(selection_head);
8699 self.autoclose_regions.push(AutocloseRegion {
8700 selection_id: selection.id,
8701 range: start..end,
8702 pair,
8703 });
8704 }
8705 }
8706 }
8707 }
8708 }
8709 Ok(())
8710 }
8711
8712 pub fn move_to_next_snippet_tabstop(
8713 &mut self,
8714 window: &mut Window,
8715 cx: &mut Context<Self>,
8716 ) -> bool {
8717 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8718 }
8719
8720 pub fn move_to_prev_snippet_tabstop(
8721 &mut self,
8722 window: &mut Window,
8723 cx: &mut Context<Self>,
8724 ) -> bool {
8725 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8726 }
8727
8728 pub fn move_to_snippet_tabstop(
8729 &mut self,
8730 bias: Bias,
8731 window: &mut Window,
8732 cx: &mut Context<Self>,
8733 ) -> bool {
8734 if let Some(mut snippet) = self.snippet_stack.pop() {
8735 match bias {
8736 Bias::Left => {
8737 if snippet.active_index > 0 {
8738 snippet.active_index -= 1;
8739 } else {
8740 self.snippet_stack.push(snippet);
8741 return false;
8742 }
8743 }
8744 Bias::Right => {
8745 if snippet.active_index + 1 < snippet.ranges.len() {
8746 snippet.active_index += 1;
8747 } else {
8748 self.snippet_stack.push(snippet);
8749 return false;
8750 }
8751 }
8752 }
8753 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8755 s.select_anchor_ranges(current_ranges.iter().cloned())
8756 });
8757
8758 if let Some(choices) = &snippet.choices[snippet.active_index] {
8759 if let Some(selection) = current_ranges.first() {
8760 self.show_snippet_choices(&choices, selection.clone(), cx);
8761 }
8762 }
8763
8764 // If snippet state is not at the last tabstop, push it back on the stack
8765 if snippet.active_index + 1 < snippet.ranges.len() {
8766 self.snippet_stack.push(snippet);
8767 }
8768 return true;
8769 }
8770 }
8771
8772 false
8773 }
8774
8775 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8776 self.transact(window, cx, |this, window, cx| {
8777 this.select_all(&SelectAll, window, cx);
8778 this.insert("", window, cx);
8779 });
8780 }
8781
8782 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8783 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8784 self.transact(window, cx, |this, window, cx| {
8785 this.select_autoclose_pair(window, cx);
8786 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8787 if !this.linked_edit_ranges.is_empty() {
8788 let selections = this.selections.all::<MultiBufferPoint>(cx);
8789 let snapshot = this.buffer.read(cx).snapshot(cx);
8790
8791 for selection in selections.iter() {
8792 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8793 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8794 if selection_start.buffer_id != selection_end.buffer_id {
8795 continue;
8796 }
8797 if let Some(ranges) =
8798 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8799 {
8800 for (buffer, entries) in ranges {
8801 linked_ranges.entry(buffer).or_default().extend(entries);
8802 }
8803 }
8804 }
8805 }
8806
8807 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8808 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8809 for selection in &mut selections {
8810 if selection.is_empty() {
8811 let old_head = selection.head();
8812 let mut new_head =
8813 movement::left(&display_map, old_head.to_display_point(&display_map))
8814 .to_point(&display_map);
8815 if let Some((buffer, line_buffer_range)) = display_map
8816 .buffer_snapshot
8817 .buffer_line_for_row(MultiBufferRow(old_head.row))
8818 {
8819 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8820 let indent_len = match indent_size.kind {
8821 IndentKind::Space => {
8822 buffer.settings_at(line_buffer_range.start, cx).tab_size
8823 }
8824 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8825 };
8826 if old_head.column <= indent_size.len && old_head.column > 0 {
8827 let indent_len = indent_len.get();
8828 new_head = cmp::min(
8829 new_head,
8830 MultiBufferPoint::new(
8831 old_head.row,
8832 ((old_head.column - 1) / indent_len) * indent_len,
8833 ),
8834 );
8835 }
8836 }
8837
8838 selection.set_head(new_head, SelectionGoal::None);
8839 }
8840 }
8841
8842 this.signature_help_state.set_backspace_pressed(true);
8843 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8844 s.select(selections)
8845 });
8846 this.insert("", window, cx);
8847 let empty_str: Arc<str> = Arc::from("");
8848 for (buffer, edits) in linked_ranges {
8849 let snapshot = buffer.read(cx).snapshot();
8850 use text::ToPoint as TP;
8851
8852 let edits = edits
8853 .into_iter()
8854 .map(|range| {
8855 let end_point = TP::to_point(&range.end, &snapshot);
8856 let mut start_point = TP::to_point(&range.start, &snapshot);
8857
8858 if end_point == start_point {
8859 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8860 .saturating_sub(1);
8861 start_point =
8862 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8863 };
8864
8865 (start_point..end_point, empty_str.clone())
8866 })
8867 .sorted_by_key(|(range, _)| range.start)
8868 .collect::<Vec<_>>();
8869 buffer.update(cx, |this, cx| {
8870 this.edit(edits, None, cx);
8871 })
8872 }
8873 this.refresh_inline_completion(true, false, window, cx);
8874 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8875 });
8876 }
8877
8878 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8879 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8880 self.transact(window, cx, |this, window, cx| {
8881 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8882 s.move_with(|map, selection| {
8883 if selection.is_empty() {
8884 let cursor = movement::right(map, selection.head());
8885 selection.end = cursor;
8886 selection.reversed = true;
8887 selection.goal = SelectionGoal::None;
8888 }
8889 })
8890 });
8891 this.insert("", window, cx);
8892 this.refresh_inline_completion(true, false, window, cx);
8893 });
8894 }
8895
8896 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8897 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8898 if self.move_to_prev_snippet_tabstop(window, cx) {
8899 return;
8900 }
8901 self.outdent(&Outdent, window, cx);
8902 }
8903
8904 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8905 if self.move_to_next_snippet_tabstop(window, cx) {
8906 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8907 return;
8908 }
8909 if self.read_only(cx) {
8910 return;
8911 }
8912 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8913 let mut selections = self.selections.all_adjusted(cx);
8914 let buffer = self.buffer.read(cx);
8915 let snapshot = buffer.snapshot(cx);
8916 let rows_iter = selections.iter().map(|s| s.head().row);
8917 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8918
8919 let has_some_cursor_in_whitespace = selections
8920 .iter()
8921 .filter(|selection| selection.is_empty())
8922 .any(|selection| {
8923 let cursor = selection.head();
8924 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8925 cursor.column < current_indent.len
8926 });
8927
8928 let mut edits = Vec::new();
8929 let mut prev_edited_row = 0;
8930 let mut row_delta = 0;
8931 for selection in &mut selections {
8932 if selection.start.row != prev_edited_row {
8933 row_delta = 0;
8934 }
8935 prev_edited_row = selection.end.row;
8936
8937 // If the selection is non-empty, then increase the indentation of the selected lines.
8938 if !selection.is_empty() {
8939 row_delta =
8940 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8941 continue;
8942 }
8943
8944 let cursor = selection.head();
8945 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8946 if let Some(suggested_indent) =
8947 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8948 {
8949 // Don't do anything if already at suggested indent
8950 // and there is any other cursor which is not
8951 if has_some_cursor_in_whitespace
8952 && cursor.column == current_indent.len
8953 && current_indent.len == suggested_indent.len
8954 {
8955 continue;
8956 }
8957
8958 // Adjust line and move cursor to suggested indent
8959 // if cursor is not at suggested indent
8960 if cursor.column < suggested_indent.len
8961 && cursor.column <= current_indent.len
8962 && current_indent.len <= suggested_indent.len
8963 {
8964 selection.start = Point::new(cursor.row, suggested_indent.len);
8965 selection.end = selection.start;
8966 if row_delta == 0 {
8967 edits.extend(Buffer::edit_for_indent_size_adjustment(
8968 cursor.row,
8969 current_indent,
8970 suggested_indent,
8971 ));
8972 row_delta = suggested_indent.len - current_indent.len;
8973 }
8974 continue;
8975 }
8976
8977 // If current indent is more than suggested indent
8978 // only move cursor to current indent and skip indent
8979 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
8980 selection.start = Point::new(cursor.row, current_indent.len);
8981 selection.end = selection.start;
8982 continue;
8983 }
8984 }
8985
8986 // Otherwise, insert a hard or soft tab.
8987 let settings = buffer.language_settings_at(cursor, cx);
8988 let tab_size = if settings.hard_tabs {
8989 IndentSize::tab()
8990 } else {
8991 let tab_size = settings.tab_size.get();
8992 let indent_remainder = snapshot
8993 .text_for_range(Point::new(cursor.row, 0)..cursor)
8994 .flat_map(str::chars)
8995 .fold(row_delta % tab_size, |counter: u32, c| {
8996 if c == '\t' {
8997 0
8998 } else {
8999 (counter + 1) % tab_size
9000 }
9001 });
9002
9003 let chars_to_next_tab_stop = tab_size - indent_remainder;
9004 IndentSize::spaces(chars_to_next_tab_stop)
9005 };
9006 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9007 selection.end = selection.start;
9008 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9009 row_delta += tab_size.len;
9010 }
9011
9012 self.transact(window, cx, |this, window, cx| {
9013 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9014 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9015 s.select(selections)
9016 });
9017 this.refresh_inline_completion(true, false, window, cx);
9018 });
9019 }
9020
9021 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9022 if self.read_only(cx) {
9023 return;
9024 }
9025 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9026 let mut selections = self.selections.all::<Point>(cx);
9027 let mut prev_edited_row = 0;
9028 let mut row_delta = 0;
9029 let mut edits = Vec::new();
9030 let buffer = self.buffer.read(cx);
9031 let snapshot = buffer.snapshot(cx);
9032 for selection in &mut selections {
9033 if selection.start.row != prev_edited_row {
9034 row_delta = 0;
9035 }
9036 prev_edited_row = selection.end.row;
9037
9038 row_delta =
9039 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9040 }
9041
9042 self.transact(window, cx, |this, window, cx| {
9043 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9045 s.select(selections)
9046 });
9047 });
9048 }
9049
9050 fn indent_selection(
9051 buffer: &MultiBuffer,
9052 snapshot: &MultiBufferSnapshot,
9053 selection: &mut Selection<Point>,
9054 edits: &mut Vec<(Range<Point>, String)>,
9055 delta_for_start_row: u32,
9056 cx: &App,
9057 ) -> u32 {
9058 let settings = buffer.language_settings_at(selection.start, cx);
9059 let tab_size = settings.tab_size.get();
9060 let indent_kind = if settings.hard_tabs {
9061 IndentKind::Tab
9062 } else {
9063 IndentKind::Space
9064 };
9065 let mut start_row = selection.start.row;
9066 let mut end_row = selection.end.row + 1;
9067
9068 // If a selection ends at the beginning of a line, don't indent
9069 // that last line.
9070 if selection.end.column == 0 && selection.end.row > selection.start.row {
9071 end_row -= 1;
9072 }
9073
9074 // Avoid re-indenting a row that has already been indented by a
9075 // previous selection, but still update this selection's column
9076 // to reflect that indentation.
9077 if delta_for_start_row > 0 {
9078 start_row += 1;
9079 selection.start.column += delta_for_start_row;
9080 if selection.end.row == selection.start.row {
9081 selection.end.column += delta_for_start_row;
9082 }
9083 }
9084
9085 let mut delta_for_end_row = 0;
9086 let has_multiple_rows = start_row + 1 != end_row;
9087 for row in start_row..end_row {
9088 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9089 let indent_delta = match (current_indent.kind, indent_kind) {
9090 (IndentKind::Space, IndentKind::Space) => {
9091 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9092 IndentSize::spaces(columns_to_next_tab_stop)
9093 }
9094 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9095 (_, IndentKind::Tab) => IndentSize::tab(),
9096 };
9097
9098 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9099 0
9100 } else {
9101 selection.start.column
9102 };
9103 let row_start = Point::new(row, start);
9104 edits.push((
9105 row_start..row_start,
9106 indent_delta.chars().collect::<String>(),
9107 ));
9108
9109 // Update this selection's endpoints to reflect the indentation.
9110 if row == selection.start.row {
9111 selection.start.column += indent_delta.len;
9112 }
9113 if row == selection.end.row {
9114 selection.end.column += indent_delta.len;
9115 delta_for_end_row = indent_delta.len;
9116 }
9117 }
9118
9119 if selection.start.row == selection.end.row {
9120 delta_for_start_row + delta_for_end_row
9121 } else {
9122 delta_for_end_row
9123 }
9124 }
9125
9126 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9127 if self.read_only(cx) {
9128 return;
9129 }
9130 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9131 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9132 let selections = self.selections.all::<Point>(cx);
9133 let mut deletion_ranges = Vec::new();
9134 let mut last_outdent = None;
9135 {
9136 let buffer = self.buffer.read(cx);
9137 let snapshot = buffer.snapshot(cx);
9138 for selection in &selections {
9139 let settings = buffer.language_settings_at(selection.start, cx);
9140 let tab_size = settings.tab_size.get();
9141 let mut rows = selection.spanned_rows(false, &display_map);
9142
9143 // Avoid re-outdenting a row that has already been outdented by a
9144 // previous selection.
9145 if let Some(last_row) = last_outdent {
9146 if last_row == rows.start {
9147 rows.start = rows.start.next_row();
9148 }
9149 }
9150 let has_multiple_rows = rows.len() > 1;
9151 for row in rows.iter_rows() {
9152 let indent_size = snapshot.indent_size_for_line(row);
9153 if indent_size.len > 0 {
9154 let deletion_len = match indent_size.kind {
9155 IndentKind::Space => {
9156 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9157 if columns_to_prev_tab_stop == 0 {
9158 tab_size
9159 } else {
9160 columns_to_prev_tab_stop
9161 }
9162 }
9163 IndentKind::Tab => 1,
9164 };
9165 let start = if has_multiple_rows
9166 || deletion_len > selection.start.column
9167 || indent_size.len < selection.start.column
9168 {
9169 0
9170 } else {
9171 selection.start.column - deletion_len
9172 };
9173 deletion_ranges.push(
9174 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9175 );
9176 last_outdent = Some(row);
9177 }
9178 }
9179 }
9180 }
9181
9182 self.transact(window, cx, |this, window, cx| {
9183 this.buffer.update(cx, |buffer, cx| {
9184 let empty_str: Arc<str> = Arc::default();
9185 buffer.edit(
9186 deletion_ranges
9187 .into_iter()
9188 .map(|range| (range, empty_str.clone())),
9189 None,
9190 cx,
9191 );
9192 });
9193 let selections = this.selections.all::<usize>(cx);
9194 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9195 s.select(selections)
9196 });
9197 });
9198 }
9199
9200 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9201 if self.read_only(cx) {
9202 return;
9203 }
9204 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9205 let selections = self
9206 .selections
9207 .all::<usize>(cx)
9208 .into_iter()
9209 .map(|s| s.range());
9210
9211 self.transact(window, cx, |this, window, cx| {
9212 this.buffer.update(cx, |buffer, cx| {
9213 buffer.autoindent_ranges(selections, cx);
9214 });
9215 let selections = this.selections.all::<usize>(cx);
9216 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9217 s.select(selections)
9218 });
9219 });
9220 }
9221
9222 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9223 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9225 let selections = self.selections.all::<Point>(cx);
9226
9227 let mut new_cursors = Vec::new();
9228 let mut edit_ranges = Vec::new();
9229 let mut selections = selections.iter().peekable();
9230 while let Some(selection) = selections.next() {
9231 let mut rows = selection.spanned_rows(false, &display_map);
9232 let goal_display_column = selection.head().to_display_point(&display_map).column();
9233
9234 // Accumulate contiguous regions of rows that we want to delete.
9235 while let Some(next_selection) = selections.peek() {
9236 let next_rows = next_selection.spanned_rows(false, &display_map);
9237 if next_rows.start <= rows.end {
9238 rows.end = next_rows.end;
9239 selections.next().unwrap();
9240 } else {
9241 break;
9242 }
9243 }
9244
9245 let buffer = &display_map.buffer_snapshot;
9246 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9247 let edit_end;
9248 let cursor_buffer_row;
9249 if buffer.max_point().row >= rows.end.0 {
9250 // If there's a line after the range, delete the \n from the end of the row range
9251 // and position the cursor on the next line.
9252 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9253 cursor_buffer_row = rows.end;
9254 } else {
9255 // If there isn't a line after the range, delete the \n from the line before the
9256 // start of the row range and position the cursor there.
9257 edit_start = edit_start.saturating_sub(1);
9258 edit_end = buffer.len();
9259 cursor_buffer_row = rows.start.previous_row();
9260 }
9261
9262 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9263 *cursor.column_mut() =
9264 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9265
9266 new_cursors.push((
9267 selection.id,
9268 buffer.anchor_after(cursor.to_point(&display_map)),
9269 ));
9270 edit_ranges.push(edit_start..edit_end);
9271 }
9272
9273 self.transact(window, cx, |this, window, cx| {
9274 let buffer = this.buffer.update(cx, |buffer, cx| {
9275 let empty_str: Arc<str> = Arc::default();
9276 buffer.edit(
9277 edit_ranges
9278 .into_iter()
9279 .map(|range| (range, empty_str.clone())),
9280 None,
9281 cx,
9282 );
9283 buffer.snapshot(cx)
9284 });
9285 let new_selections = new_cursors
9286 .into_iter()
9287 .map(|(id, cursor)| {
9288 let cursor = cursor.to_point(&buffer);
9289 Selection {
9290 id,
9291 start: cursor,
9292 end: cursor,
9293 reversed: false,
9294 goal: SelectionGoal::None,
9295 }
9296 })
9297 .collect();
9298
9299 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9300 s.select(new_selections);
9301 });
9302 });
9303 }
9304
9305 pub fn join_lines_impl(
9306 &mut self,
9307 insert_whitespace: bool,
9308 window: &mut Window,
9309 cx: &mut Context<Self>,
9310 ) {
9311 if self.read_only(cx) {
9312 return;
9313 }
9314 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9315 for selection in self.selections.all::<Point>(cx) {
9316 let start = MultiBufferRow(selection.start.row);
9317 // Treat single line selections as if they include the next line. Otherwise this action
9318 // would do nothing for single line selections individual cursors.
9319 let end = if selection.start.row == selection.end.row {
9320 MultiBufferRow(selection.start.row + 1)
9321 } else {
9322 MultiBufferRow(selection.end.row)
9323 };
9324
9325 if let Some(last_row_range) = row_ranges.last_mut() {
9326 if start <= last_row_range.end {
9327 last_row_range.end = end;
9328 continue;
9329 }
9330 }
9331 row_ranges.push(start..end);
9332 }
9333
9334 let snapshot = self.buffer.read(cx).snapshot(cx);
9335 let mut cursor_positions = Vec::new();
9336 for row_range in &row_ranges {
9337 let anchor = snapshot.anchor_before(Point::new(
9338 row_range.end.previous_row().0,
9339 snapshot.line_len(row_range.end.previous_row()),
9340 ));
9341 cursor_positions.push(anchor..anchor);
9342 }
9343
9344 self.transact(window, cx, |this, window, cx| {
9345 for row_range in row_ranges.into_iter().rev() {
9346 for row in row_range.iter_rows().rev() {
9347 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9348 let next_line_row = row.next_row();
9349 let indent = snapshot.indent_size_for_line(next_line_row);
9350 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9351
9352 let replace =
9353 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9354 " "
9355 } else {
9356 ""
9357 };
9358
9359 this.buffer.update(cx, |buffer, cx| {
9360 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9361 });
9362 }
9363 }
9364
9365 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9366 s.select_anchor_ranges(cursor_positions)
9367 });
9368 });
9369 }
9370
9371 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9373 self.join_lines_impl(true, window, cx);
9374 }
9375
9376 pub fn sort_lines_case_sensitive(
9377 &mut self,
9378 _: &SortLinesCaseSensitive,
9379 window: &mut Window,
9380 cx: &mut Context<Self>,
9381 ) {
9382 self.manipulate_lines(window, cx, |lines| lines.sort())
9383 }
9384
9385 pub fn sort_lines_case_insensitive(
9386 &mut self,
9387 _: &SortLinesCaseInsensitive,
9388 window: &mut Window,
9389 cx: &mut Context<Self>,
9390 ) {
9391 self.manipulate_lines(window, cx, |lines| {
9392 lines.sort_by_key(|line| line.to_lowercase())
9393 })
9394 }
9395
9396 pub fn unique_lines_case_insensitive(
9397 &mut self,
9398 _: &UniqueLinesCaseInsensitive,
9399 window: &mut Window,
9400 cx: &mut Context<Self>,
9401 ) {
9402 self.manipulate_lines(window, cx, |lines| {
9403 let mut seen = HashSet::default();
9404 lines.retain(|line| seen.insert(line.to_lowercase()));
9405 })
9406 }
9407
9408 pub fn unique_lines_case_sensitive(
9409 &mut self,
9410 _: &UniqueLinesCaseSensitive,
9411 window: &mut Window,
9412 cx: &mut Context<Self>,
9413 ) {
9414 self.manipulate_lines(window, cx, |lines| {
9415 let mut seen = HashSet::default();
9416 lines.retain(|line| seen.insert(*line));
9417 })
9418 }
9419
9420 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9421 let Some(project) = self.project.clone() else {
9422 return;
9423 };
9424 self.reload(project, window, cx)
9425 .detach_and_notify_err(window, cx);
9426 }
9427
9428 pub fn restore_file(
9429 &mut self,
9430 _: &::git::RestoreFile,
9431 window: &mut Window,
9432 cx: &mut Context<Self>,
9433 ) {
9434 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9435 let mut buffer_ids = HashSet::default();
9436 let snapshot = self.buffer().read(cx).snapshot(cx);
9437 for selection in self.selections.all::<usize>(cx) {
9438 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9439 }
9440
9441 let buffer = self.buffer().read(cx);
9442 let ranges = buffer_ids
9443 .into_iter()
9444 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9445 .collect::<Vec<_>>();
9446
9447 self.restore_hunks_in_ranges(ranges, window, cx);
9448 }
9449
9450 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9451 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9452 let selections = self
9453 .selections
9454 .all(cx)
9455 .into_iter()
9456 .map(|s| s.range())
9457 .collect();
9458 self.restore_hunks_in_ranges(selections, window, cx);
9459 }
9460
9461 pub fn restore_hunks_in_ranges(
9462 &mut self,
9463 ranges: Vec<Range<Point>>,
9464 window: &mut Window,
9465 cx: &mut Context<Editor>,
9466 ) {
9467 let mut revert_changes = HashMap::default();
9468 let chunk_by = self
9469 .snapshot(window, cx)
9470 .hunks_for_ranges(ranges)
9471 .into_iter()
9472 .chunk_by(|hunk| hunk.buffer_id);
9473 for (buffer_id, hunks) in &chunk_by {
9474 let hunks = hunks.collect::<Vec<_>>();
9475 for hunk in &hunks {
9476 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9477 }
9478 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9479 }
9480 drop(chunk_by);
9481 if !revert_changes.is_empty() {
9482 self.transact(window, cx, |editor, window, cx| {
9483 editor.restore(revert_changes, window, cx);
9484 });
9485 }
9486 }
9487
9488 pub fn open_active_item_in_terminal(
9489 &mut self,
9490 _: &OpenInTerminal,
9491 window: &mut Window,
9492 cx: &mut Context<Self>,
9493 ) {
9494 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9495 let project_path = buffer.read(cx).project_path(cx)?;
9496 let project = self.project.as_ref()?.read(cx);
9497 let entry = project.entry_for_path(&project_path, cx)?;
9498 let parent = match &entry.canonical_path {
9499 Some(canonical_path) => canonical_path.to_path_buf(),
9500 None => project.absolute_path(&project_path, cx)?,
9501 }
9502 .parent()?
9503 .to_path_buf();
9504 Some(parent)
9505 }) {
9506 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9507 }
9508 }
9509
9510 fn set_breakpoint_context_menu(
9511 &mut self,
9512 display_row: DisplayRow,
9513 position: Option<Anchor>,
9514 clicked_point: gpui::Point<Pixels>,
9515 window: &mut Window,
9516 cx: &mut Context<Self>,
9517 ) {
9518 if !cx.has_flag::<DebuggerFeatureFlag>() {
9519 return;
9520 }
9521 let source = self
9522 .buffer
9523 .read(cx)
9524 .snapshot(cx)
9525 .anchor_before(Point::new(display_row.0, 0u32));
9526
9527 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9528
9529 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9530 self,
9531 source,
9532 clicked_point,
9533 context_menu,
9534 window,
9535 cx,
9536 );
9537 }
9538
9539 fn add_edit_breakpoint_block(
9540 &mut self,
9541 anchor: Anchor,
9542 breakpoint: &Breakpoint,
9543 edit_action: BreakpointPromptEditAction,
9544 window: &mut Window,
9545 cx: &mut Context<Self>,
9546 ) {
9547 let weak_editor = cx.weak_entity();
9548 let bp_prompt = cx.new(|cx| {
9549 BreakpointPromptEditor::new(
9550 weak_editor,
9551 anchor,
9552 breakpoint.clone(),
9553 edit_action,
9554 window,
9555 cx,
9556 )
9557 });
9558
9559 let height = bp_prompt.update(cx, |this, cx| {
9560 this.prompt
9561 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9562 });
9563 let cloned_prompt = bp_prompt.clone();
9564 let blocks = vec![BlockProperties {
9565 style: BlockStyle::Sticky,
9566 placement: BlockPlacement::Above(anchor),
9567 height: Some(height),
9568 render: Arc::new(move |cx| {
9569 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9570 cloned_prompt.clone().into_any_element()
9571 }),
9572 priority: 0,
9573 render_in_minimap: true,
9574 }];
9575
9576 let focus_handle = bp_prompt.focus_handle(cx);
9577 window.focus(&focus_handle);
9578
9579 let block_ids = self.insert_blocks(blocks, None, cx);
9580 bp_prompt.update(cx, |prompt, _| {
9581 prompt.add_block_ids(block_ids);
9582 });
9583 }
9584
9585 pub(crate) fn breakpoint_at_row(
9586 &self,
9587 row: u32,
9588 window: &mut Window,
9589 cx: &mut Context<Self>,
9590 ) -> Option<(Anchor, Breakpoint)> {
9591 let snapshot = self.snapshot(window, cx);
9592 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9593
9594 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9595 }
9596
9597 pub(crate) fn breakpoint_at_anchor(
9598 &self,
9599 breakpoint_position: Anchor,
9600 snapshot: &EditorSnapshot,
9601 cx: &mut Context<Self>,
9602 ) -> Option<(Anchor, Breakpoint)> {
9603 let project = self.project.clone()?;
9604
9605 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9606 snapshot
9607 .buffer_snapshot
9608 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9609 })?;
9610
9611 let enclosing_excerpt = breakpoint_position.excerpt_id;
9612 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9613 let buffer_snapshot = buffer.read(cx).snapshot();
9614
9615 let row = buffer_snapshot
9616 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9617 .row;
9618
9619 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9620 let anchor_end = snapshot
9621 .buffer_snapshot
9622 .anchor_after(Point::new(row, line_len));
9623
9624 let bp = self
9625 .breakpoint_store
9626 .as_ref()?
9627 .read_with(cx, |breakpoint_store, cx| {
9628 breakpoint_store
9629 .breakpoints(
9630 &buffer,
9631 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9632 &buffer_snapshot,
9633 cx,
9634 )
9635 .next()
9636 .and_then(|(anchor, bp)| {
9637 let breakpoint_row = buffer_snapshot
9638 .summary_for_anchor::<text::PointUtf16>(anchor)
9639 .row;
9640
9641 if breakpoint_row == row {
9642 snapshot
9643 .buffer_snapshot
9644 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9645 .map(|anchor| (anchor, bp.clone()))
9646 } else {
9647 None
9648 }
9649 })
9650 });
9651 bp
9652 }
9653
9654 pub fn edit_log_breakpoint(
9655 &mut self,
9656 _: &EditLogBreakpoint,
9657 window: &mut Window,
9658 cx: &mut Context<Self>,
9659 ) {
9660 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9661 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9662 message: None,
9663 state: BreakpointState::Enabled,
9664 condition: None,
9665 hit_condition: None,
9666 });
9667
9668 self.add_edit_breakpoint_block(
9669 anchor,
9670 &breakpoint,
9671 BreakpointPromptEditAction::Log,
9672 window,
9673 cx,
9674 );
9675 }
9676 }
9677
9678 fn breakpoints_at_cursors(
9679 &self,
9680 window: &mut Window,
9681 cx: &mut Context<Self>,
9682 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9683 let snapshot = self.snapshot(window, cx);
9684 let cursors = self
9685 .selections
9686 .disjoint_anchors()
9687 .into_iter()
9688 .map(|selection| {
9689 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9690
9691 let breakpoint_position = self
9692 .breakpoint_at_row(cursor_position.row, window, cx)
9693 .map(|bp| bp.0)
9694 .unwrap_or_else(|| {
9695 snapshot
9696 .display_snapshot
9697 .buffer_snapshot
9698 .anchor_after(Point::new(cursor_position.row, 0))
9699 });
9700
9701 let breakpoint = self
9702 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9703 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9704
9705 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9706 })
9707 // 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.
9708 .collect::<HashMap<Anchor, _>>();
9709
9710 cursors.into_iter().collect()
9711 }
9712
9713 pub fn enable_breakpoint(
9714 &mut self,
9715 _: &crate::actions::EnableBreakpoint,
9716 window: &mut Window,
9717 cx: &mut Context<Self>,
9718 ) {
9719 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9720 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9721 continue;
9722 };
9723 self.edit_breakpoint_at_anchor(
9724 anchor,
9725 breakpoint,
9726 BreakpointEditAction::InvertState,
9727 cx,
9728 );
9729 }
9730 }
9731
9732 pub fn disable_breakpoint(
9733 &mut self,
9734 _: &crate::actions::DisableBreakpoint,
9735 window: &mut Window,
9736 cx: &mut Context<Self>,
9737 ) {
9738 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9739 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9740 continue;
9741 };
9742 self.edit_breakpoint_at_anchor(
9743 anchor,
9744 breakpoint,
9745 BreakpointEditAction::InvertState,
9746 cx,
9747 );
9748 }
9749 }
9750
9751 pub fn toggle_breakpoint(
9752 &mut self,
9753 _: &crate::actions::ToggleBreakpoint,
9754 window: &mut Window,
9755 cx: &mut Context<Self>,
9756 ) {
9757 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9758 if let Some(breakpoint) = breakpoint {
9759 self.edit_breakpoint_at_anchor(
9760 anchor,
9761 breakpoint,
9762 BreakpointEditAction::Toggle,
9763 cx,
9764 );
9765 } else {
9766 self.edit_breakpoint_at_anchor(
9767 anchor,
9768 Breakpoint::new_standard(),
9769 BreakpointEditAction::Toggle,
9770 cx,
9771 );
9772 }
9773 }
9774 }
9775
9776 pub fn edit_breakpoint_at_anchor(
9777 &mut self,
9778 breakpoint_position: Anchor,
9779 breakpoint: Breakpoint,
9780 edit_action: BreakpointEditAction,
9781 cx: &mut Context<Self>,
9782 ) {
9783 let Some(breakpoint_store) = &self.breakpoint_store else {
9784 return;
9785 };
9786
9787 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9788 if breakpoint_position == Anchor::min() {
9789 self.buffer()
9790 .read(cx)
9791 .excerpt_buffer_ids()
9792 .into_iter()
9793 .next()
9794 } else {
9795 None
9796 }
9797 }) else {
9798 return;
9799 };
9800
9801 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9802 return;
9803 };
9804
9805 breakpoint_store.update(cx, |breakpoint_store, cx| {
9806 breakpoint_store.toggle_breakpoint(
9807 buffer,
9808 (breakpoint_position.text_anchor, breakpoint),
9809 edit_action,
9810 cx,
9811 );
9812 });
9813
9814 cx.notify();
9815 }
9816
9817 #[cfg(any(test, feature = "test-support"))]
9818 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9819 self.breakpoint_store.clone()
9820 }
9821
9822 pub fn prepare_restore_change(
9823 &self,
9824 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9825 hunk: &MultiBufferDiffHunk,
9826 cx: &mut App,
9827 ) -> Option<()> {
9828 if hunk.is_created_file() {
9829 return None;
9830 }
9831 let buffer = self.buffer.read(cx);
9832 let diff = buffer.diff_for(hunk.buffer_id)?;
9833 let buffer = buffer.buffer(hunk.buffer_id)?;
9834 let buffer = buffer.read(cx);
9835 let original_text = diff
9836 .read(cx)
9837 .base_text()
9838 .as_rope()
9839 .slice(hunk.diff_base_byte_range.clone());
9840 let buffer_snapshot = buffer.snapshot();
9841 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9842 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9843 probe
9844 .0
9845 .start
9846 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9847 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9848 }) {
9849 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9850 Some(())
9851 } else {
9852 None
9853 }
9854 }
9855
9856 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9857 self.manipulate_lines(window, cx, |lines| lines.reverse())
9858 }
9859
9860 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9861 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9862 }
9863
9864 fn manipulate_lines<Fn>(
9865 &mut self,
9866 window: &mut Window,
9867 cx: &mut Context<Self>,
9868 mut callback: Fn,
9869 ) where
9870 Fn: FnMut(&mut Vec<&str>),
9871 {
9872 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9873
9874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9875 let buffer = self.buffer.read(cx).snapshot(cx);
9876
9877 let mut edits = Vec::new();
9878
9879 let selections = self.selections.all::<Point>(cx);
9880 let mut selections = selections.iter().peekable();
9881 let mut contiguous_row_selections = Vec::new();
9882 let mut new_selections = Vec::new();
9883 let mut added_lines = 0;
9884 let mut removed_lines = 0;
9885
9886 while let Some(selection) = selections.next() {
9887 let (start_row, end_row) = consume_contiguous_rows(
9888 &mut contiguous_row_selections,
9889 selection,
9890 &display_map,
9891 &mut selections,
9892 );
9893
9894 let start_point = Point::new(start_row.0, 0);
9895 let end_point = Point::new(
9896 end_row.previous_row().0,
9897 buffer.line_len(end_row.previous_row()),
9898 );
9899 let text = buffer
9900 .text_for_range(start_point..end_point)
9901 .collect::<String>();
9902
9903 let mut lines = text.split('\n').collect_vec();
9904
9905 let lines_before = lines.len();
9906 callback(&mut lines);
9907 let lines_after = lines.len();
9908
9909 edits.push((start_point..end_point, lines.join("\n")));
9910
9911 // Selections must change based on added and removed line count
9912 let start_row =
9913 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9914 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9915 new_selections.push(Selection {
9916 id: selection.id,
9917 start: start_row,
9918 end: end_row,
9919 goal: SelectionGoal::None,
9920 reversed: selection.reversed,
9921 });
9922
9923 if lines_after > lines_before {
9924 added_lines += lines_after - lines_before;
9925 } else if lines_before > lines_after {
9926 removed_lines += lines_before - lines_after;
9927 }
9928 }
9929
9930 self.transact(window, cx, |this, window, cx| {
9931 let buffer = this.buffer.update(cx, |buffer, cx| {
9932 buffer.edit(edits, None, cx);
9933 buffer.snapshot(cx)
9934 });
9935
9936 // Recalculate offsets on newly edited buffer
9937 let new_selections = new_selections
9938 .iter()
9939 .map(|s| {
9940 let start_point = Point::new(s.start.0, 0);
9941 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9942 Selection {
9943 id: s.id,
9944 start: buffer.point_to_offset(start_point),
9945 end: buffer.point_to_offset(end_point),
9946 goal: s.goal,
9947 reversed: s.reversed,
9948 }
9949 })
9950 .collect();
9951
9952 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9953 s.select(new_selections);
9954 });
9955
9956 this.request_autoscroll(Autoscroll::fit(), cx);
9957 });
9958 }
9959
9960 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9961 self.manipulate_text(window, cx, |text| {
9962 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9963 if has_upper_case_characters {
9964 text.to_lowercase()
9965 } else {
9966 text.to_uppercase()
9967 }
9968 })
9969 }
9970
9971 pub fn convert_to_upper_case(
9972 &mut self,
9973 _: &ConvertToUpperCase,
9974 window: &mut Window,
9975 cx: &mut Context<Self>,
9976 ) {
9977 self.manipulate_text(window, cx, |text| text.to_uppercase())
9978 }
9979
9980 pub fn convert_to_lower_case(
9981 &mut self,
9982 _: &ConvertToLowerCase,
9983 window: &mut Window,
9984 cx: &mut Context<Self>,
9985 ) {
9986 self.manipulate_text(window, cx, |text| text.to_lowercase())
9987 }
9988
9989 pub fn convert_to_title_case(
9990 &mut self,
9991 _: &ConvertToTitleCase,
9992 window: &mut Window,
9993 cx: &mut Context<Self>,
9994 ) {
9995 self.manipulate_text(window, cx, |text| {
9996 text.split('\n')
9997 .map(|line| line.to_case(Case::Title))
9998 .join("\n")
9999 })
10000 }
10001
10002 pub fn convert_to_snake_case(
10003 &mut self,
10004 _: &ConvertToSnakeCase,
10005 window: &mut Window,
10006 cx: &mut Context<Self>,
10007 ) {
10008 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10009 }
10010
10011 pub fn convert_to_kebab_case(
10012 &mut self,
10013 _: &ConvertToKebabCase,
10014 window: &mut Window,
10015 cx: &mut Context<Self>,
10016 ) {
10017 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10018 }
10019
10020 pub fn convert_to_upper_camel_case(
10021 &mut self,
10022 _: &ConvertToUpperCamelCase,
10023 window: &mut Window,
10024 cx: &mut Context<Self>,
10025 ) {
10026 self.manipulate_text(window, cx, |text| {
10027 text.split('\n')
10028 .map(|line| line.to_case(Case::UpperCamel))
10029 .join("\n")
10030 })
10031 }
10032
10033 pub fn convert_to_lower_camel_case(
10034 &mut self,
10035 _: &ConvertToLowerCamelCase,
10036 window: &mut Window,
10037 cx: &mut Context<Self>,
10038 ) {
10039 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10040 }
10041
10042 pub fn convert_to_opposite_case(
10043 &mut self,
10044 _: &ConvertToOppositeCase,
10045 window: &mut Window,
10046 cx: &mut Context<Self>,
10047 ) {
10048 self.manipulate_text(window, cx, |text| {
10049 text.chars()
10050 .fold(String::with_capacity(text.len()), |mut t, c| {
10051 if c.is_uppercase() {
10052 t.extend(c.to_lowercase());
10053 } else {
10054 t.extend(c.to_uppercase());
10055 }
10056 t
10057 })
10058 })
10059 }
10060
10061 pub fn convert_to_rot13(
10062 &mut self,
10063 _: &ConvertToRot13,
10064 window: &mut Window,
10065 cx: &mut Context<Self>,
10066 ) {
10067 self.manipulate_text(window, cx, |text| {
10068 text.chars()
10069 .map(|c| match c {
10070 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10071 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10072 _ => c,
10073 })
10074 .collect()
10075 })
10076 }
10077
10078 pub fn convert_to_rot47(
10079 &mut self,
10080 _: &ConvertToRot47,
10081 window: &mut Window,
10082 cx: &mut Context<Self>,
10083 ) {
10084 self.manipulate_text(window, cx, |text| {
10085 text.chars()
10086 .map(|c| {
10087 let code_point = c as u32;
10088 if code_point >= 33 && code_point <= 126 {
10089 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10090 }
10091 c
10092 })
10093 .collect()
10094 })
10095 }
10096
10097 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10098 where
10099 Fn: FnMut(&str) -> String,
10100 {
10101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10102 let buffer = self.buffer.read(cx).snapshot(cx);
10103
10104 let mut new_selections = Vec::new();
10105 let mut edits = Vec::new();
10106 let mut selection_adjustment = 0i32;
10107
10108 for selection in self.selections.all::<usize>(cx) {
10109 let selection_is_empty = selection.is_empty();
10110
10111 let (start, end) = if selection_is_empty {
10112 let word_range = movement::surrounding_word(
10113 &display_map,
10114 selection.start.to_display_point(&display_map),
10115 );
10116 let start = word_range.start.to_offset(&display_map, Bias::Left);
10117 let end = word_range.end.to_offset(&display_map, Bias::Left);
10118 (start, end)
10119 } else {
10120 (selection.start, selection.end)
10121 };
10122
10123 let text = buffer.text_for_range(start..end).collect::<String>();
10124 let old_length = text.len() as i32;
10125 let text = callback(&text);
10126
10127 new_selections.push(Selection {
10128 start: (start as i32 - selection_adjustment) as usize,
10129 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10130 goal: SelectionGoal::None,
10131 ..selection
10132 });
10133
10134 selection_adjustment += old_length - text.len() as i32;
10135
10136 edits.push((start..end, text));
10137 }
10138
10139 self.transact(window, cx, |this, window, cx| {
10140 this.buffer.update(cx, |buffer, cx| {
10141 buffer.edit(edits, None, cx);
10142 });
10143
10144 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10145 s.select(new_selections);
10146 });
10147
10148 this.request_autoscroll(Autoscroll::fit(), cx);
10149 });
10150 }
10151
10152 pub fn duplicate(
10153 &mut self,
10154 upwards: bool,
10155 whole_lines: bool,
10156 window: &mut Window,
10157 cx: &mut Context<Self>,
10158 ) {
10159 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10160
10161 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10162 let buffer = &display_map.buffer_snapshot;
10163 let selections = self.selections.all::<Point>(cx);
10164
10165 let mut edits = Vec::new();
10166 let mut selections_iter = selections.iter().peekable();
10167 while let Some(selection) = selections_iter.next() {
10168 let mut rows = selection.spanned_rows(false, &display_map);
10169 // duplicate line-wise
10170 if whole_lines || selection.start == selection.end {
10171 // Avoid duplicating the same lines twice.
10172 while let Some(next_selection) = selections_iter.peek() {
10173 let next_rows = next_selection.spanned_rows(false, &display_map);
10174 if next_rows.start < rows.end {
10175 rows.end = next_rows.end;
10176 selections_iter.next().unwrap();
10177 } else {
10178 break;
10179 }
10180 }
10181
10182 // Copy the text from the selected row region and splice it either at the start
10183 // or end of the region.
10184 let start = Point::new(rows.start.0, 0);
10185 let end = Point::new(
10186 rows.end.previous_row().0,
10187 buffer.line_len(rows.end.previous_row()),
10188 );
10189 let text = buffer
10190 .text_for_range(start..end)
10191 .chain(Some("\n"))
10192 .collect::<String>();
10193 let insert_location = if upwards {
10194 Point::new(rows.end.0, 0)
10195 } else {
10196 start
10197 };
10198 edits.push((insert_location..insert_location, text));
10199 } else {
10200 // duplicate character-wise
10201 let start = selection.start;
10202 let end = selection.end;
10203 let text = buffer.text_for_range(start..end).collect::<String>();
10204 edits.push((selection.end..selection.end, text));
10205 }
10206 }
10207
10208 self.transact(window, cx, |this, _, cx| {
10209 this.buffer.update(cx, |buffer, cx| {
10210 buffer.edit(edits, None, cx);
10211 });
10212
10213 this.request_autoscroll(Autoscroll::fit(), cx);
10214 });
10215 }
10216
10217 pub fn duplicate_line_up(
10218 &mut self,
10219 _: &DuplicateLineUp,
10220 window: &mut Window,
10221 cx: &mut Context<Self>,
10222 ) {
10223 self.duplicate(true, true, window, cx);
10224 }
10225
10226 pub fn duplicate_line_down(
10227 &mut self,
10228 _: &DuplicateLineDown,
10229 window: &mut Window,
10230 cx: &mut Context<Self>,
10231 ) {
10232 self.duplicate(false, true, window, cx);
10233 }
10234
10235 pub fn duplicate_selection(
10236 &mut self,
10237 _: &DuplicateSelection,
10238 window: &mut Window,
10239 cx: &mut Context<Self>,
10240 ) {
10241 self.duplicate(false, false, window, cx);
10242 }
10243
10244 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10246
10247 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10248 let buffer = self.buffer.read(cx).snapshot(cx);
10249
10250 let mut edits = Vec::new();
10251 let mut unfold_ranges = Vec::new();
10252 let mut refold_creases = Vec::new();
10253
10254 let selections = self.selections.all::<Point>(cx);
10255 let mut selections = selections.iter().peekable();
10256 let mut contiguous_row_selections = Vec::new();
10257 let mut new_selections = Vec::new();
10258
10259 while let Some(selection) = selections.next() {
10260 // Find all the selections that span a contiguous row range
10261 let (start_row, end_row) = consume_contiguous_rows(
10262 &mut contiguous_row_selections,
10263 selection,
10264 &display_map,
10265 &mut selections,
10266 );
10267
10268 // Move the text spanned by the row range to be before the line preceding the row range
10269 if start_row.0 > 0 {
10270 let range_to_move = Point::new(
10271 start_row.previous_row().0,
10272 buffer.line_len(start_row.previous_row()),
10273 )
10274 ..Point::new(
10275 end_row.previous_row().0,
10276 buffer.line_len(end_row.previous_row()),
10277 );
10278 let insertion_point = display_map
10279 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10280 .0;
10281
10282 // Don't move lines across excerpts
10283 if buffer
10284 .excerpt_containing(insertion_point..range_to_move.end)
10285 .is_some()
10286 {
10287 let text = buffer
10288 .text_for_range(range_to_move.clone())
10289 .flat_map(|s| s.chars())
10290 .skip(1)
10291 .chain(['\n'])
10292 .collect::<String>();
10293
10294 edits.push((
10295 buffer.anchor_after(range_to_move.start)
10296 ..buffer.anchor_before(range_to_move.end),
10297 String::new(),
10298 ));
10299 let insertion_anchor = buffer.anchor_after(insertion_point);
10300 edits.push((insertion_anchor..insertion_anchor, text));
10301
10302 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10303
10304 // Move selections up
10305 new_selections.extend(contiguous_row_selections.drain(..).map(
10306 |mut selection| {
10307 selection.start.row -= row_delta;
10308 selection.end.row -= row_delta;
10309 selection
10310 },
10311 ));
10312
10313 // Move folds up
10314 unfold_ranges.push(range_to_move.clone());
10315 for fold in display_map.folds_in_range(
10316 buffer.anchor_before(range_to_move.start)
10317 ..buffer.anchor_after(range_to_move.end),
10318 ) {
10319 let mut start = fold.range.start.to_point(&buffer);
10320 let mut end = fold.range.end.to_point(&buffer);
10321 start.row -= row_delta;
10322 end.row -= row_delta;
10323 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10324 }
10325 }
10326 }
10327
10328 // If we didn't move line(s), preserve the existing selections
10329 new_selections.append(&mut contiguous_row_selections);
10330 }
10331
10332 self.transact(window, cx, |this, window, cx| {
10333 this.unfold_ranges(&unfold_ranges, true, true, cx);
10334 this.buffer.update(cx, |buffer, cx| {
10335 for (range, text) in edits {
10336 buffer.edit([(range, text)], None, cx);
10337 }
10338 });
10339 this.fold_creases(refold_creases, true, window, cx);
10340 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10341 s.select(new_selections);
10342 })
10343 });
10344 }
10345
10346 pub fn move_line_down(
10347 &mut self,
10348 _: &MoveLineDown,
10349 window: &mut Window,
10350 cx: &mut Context<Self>,
10351 ) {
10352 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10353
10354 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10355 let buffer = self.buffer.read(cx).snapshot(cx);
10356
10357 let mut edits = Vec::new();
10358 let mut unfold_ranges = Vec::new();
10359 let mut refold_creases = Vec::new();
10360
10361 let selections = self.selections.all::<Point>(cx);
10362 let mut selections = selections.iter().peekable();
10363 let mut contiguous_row_selections = Vec::new();
10364 let mut new_selections = Vec::new();
10365
10366 while let Some(selection) = selections.next() {
10367 // Find all the selections that span a contiguous row range
10368 let (start_row, end_row) = consume_contiguous_rows(
10369 &mut contiguous_row_selections,
10370 selection,
10371 &display_map,
10372 &mut selections,
10373 );
10374
10375 // Move the text spanned by the row range to be after the last line of the row range
10376 if end_row.0 <= buffer.max_point().row {
10377 let range_to_move =
10378 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10379 let insertion_point = display_map
10380 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10381 .0;
10382
10383 // Don't move lines across excerpt boundaries
10384 if buffer
10385 .excerpt_containing(range_to_move.start..insertion_point)
10386 .is_some()
10387 {
10388 let mut text = String::from("\n");
10389 text.extend(buffer.text_for_range(range_to_move.clone()));
10390 text.pop(); // Drop trailing newline
10391 edits.push((
10392 buffer.anchor_after(range_to_move.start)
10393 ..buffer.anchor_before(range_to_move.end),
10394 String::new(),
10395 ));
10396 let insertion_anchor = buffer.anchor_after(insertion_point);
10397 edits.push((insertion_anchor..insertion_anchor, text));
10398
10399 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10400
10401 // Move selections down
10402 new_selections.extend(contiguous_row_selections.drain(..).map(
10403 |mut selection| {
10404 selection.start.row += row_delta;
10405 selection.end.row += row_delta;
10406 selection
10407 },
10408 ));
10409
10410 // Move folds down
10411 unfold_ranges.push(range_to_move.clone());
10412 for fold in display_map.folds_in_range(
10413 buffer.anchor_before(range_to_move.start)
10414 ..buffer.anchor_after(range_to_move.end),
10415 ) {
10416 let mut start = fold.range.start.to_point(&buffer);
10417 let mut end = fold.range.end.to_point(&buffer);
10418 start.row += row_delta;
10419 end.row += row_delta;
10420 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10421 }
10422 }
10423 }
10424
10425 // If we didn't move line(s), preserve the existing selections
10426 new_selections.append(&mut contiguous_row_selections);
10427 }
10428
10429 self.transact(window, cx, |this, window, cx| {
10430 this.unfold_ranges(&unfold_ranges, true, true, cx);
10431 this.buffer.update(cx, |buffer, cx| {
10432 for (range, text) in edits {
10433 buffer.edit([(range, text)], None, cx);
10434 }
10435 });
10436 this.fold_creases(refold_creases, true, window, cx);
10437 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10438 s.select(new_selections)
10439 });
10440 });
10441 }
10442
10443 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10444 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10445 let text_layout_details = &self.text_layout_details(window);
10446 self.transact(window, cx, |this, window, cx| {
10447 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10448 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10449 s.move_with(|display_map, selection| {
10450 if !selection.is_empty() {
10451 return;
10452 }
10453
10454 let mut head = selection.head();
10455 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10456 if head.column() == display_map.line_len(head.row()) {
10457 transpose_offset = display_map
10458 .buffer_snapshot
10459 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10460 }
10461
10462 if transpose_offset == 0 {
10463 return;
10464 }
10465
10466 *head.column_mut() += 1;
10467 head = display_map.clip_point(head, Bias::Right);
10468 let goal = SelectionGoal::HorizontalPosition(
10469 display_map
10470 .x_for_display_point(head, text_layout_details)
10471 .into(),
10472 );
10473 selection.collapse_to(head, goal);
10474
10475 let transpose_start = display_map
10476 .buffer_snapshot
10477 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10478 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10479 let transpose_end = display_map
10480 .buffer_snapshot
10481 .clip_offset(transpose_offset + 1, Bias::Right);
10482 if let Some(ch) =
10483 display_map.buffer_snapshot.chars_at(transpose_start).next()
10484 {
10485 edits.push((transpose_start..transpose_offset, String::new()));
10486 edits.push((transpose_end..transpose_end, ch.to_string()));
10487 }
10488 }
10489 });
10490 edits
10491 });
10492 this.buffer
10493 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10494 let selections = this.selections.all::<usize>(cx);
10495 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10496 s.select(selections);
10497 });
10498 });
10499 }
10500
10501 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10502 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10503 self.rewrap_impl(RewrapOptions::default(), cx)
10504 }
10505
10506 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10507 let buffer = self.buffer.read(cx).snapshot(cx);
10508 let selections = self.selections.all::<Point>(cx);
10509 let mut selections = selections.iter().peekable();
10510
10511 let mut edits = Vec::new();
10512 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10513
10514 while let Some(selection) = selections.next() {
10515 let mut start_row = selection.start.row;
10516 let mut end_row = selection.end.row;
10517
10518 // Skip selections that overlap with a range that has already been rewrapped.
10519 let selection_range = start_row..end_row;
10520 if rewrapped_row_ranges
10521 .iter()
10522 .any(|range| range.overlaps(&selection_range))
10523 {
10524 continue;
10525 }
10526
10527 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10528
10529 // Since not all lines in the selection may be at the same indent
10530 // level, choose the indent size that is the most common between all
10531 // of the lines.
10532 //
10533 // If there is a tie, we use the deepest indent.
10534 let (indent_size, indent_end) = {
10535 let mut indent_size_occurrences = HashMap::default();
10536 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10537
10538 for row in start_row..=end_row {
10539 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10540 rows_by_indent_size.entry(indent).or_default().push(row);
10541 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10542 }
10543
10544 let indent_size = indent_size_occurrences
10545 .into_iter()
10546 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10547 .map(|(indent, _)| indent)
10548 .unwrap_or_default();
10549 let row = rows_by_indent_size[&indent_size][0];
10550 let indent_end = Point::new(row, indent_size.len);
10551
10552 (indent_size, indent_end)
10553 };
10554
10555 let mut line_prefix = indent_size.chars().collect::<String>();
10556
10557 let mut inside_comment = false;
10558 if let Some(comment_prefix) =
10559 buffer
10560 .language_scope_at(selection.head())
10561 .and_then(|language| {
10562 language
10563 .line_comment_prefixes()
10564 .iter()
10565 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10566 .cloned()
10567 })
10568 {
10569 line_prefix.push_str(&comment_prefix);
10570 inside_comment = true;
10571 }
10572
10573 let language_settings = buffer.language_settings_at(selection.head(), cx);
10574 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10575 RewrapBehavior::InComments => inside_comment,
10576 RewrapBehavior::InSelections => !selection.is_empty(),
10577 RewrapBehavior::Anywhere => true,
10578 };
10579
10580 let should_rewrap = options.override_language_settings
10581 || allow_rewrap_based_on_language
10582 || self.hard_wrap.is_some();
10583 if !should_rewrap {
10584 continue;
10585 }
10586
10587 if selection.is_empty() {
10588 'expand_upwards: while start_row > 0 {
10589 let prev_row = start_row - 1;
10590 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10591 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10592 {
10593 start_row = prev_row;
10594 } else {
10595 break 'expand_upwards;
10596 }
10597 }
10598
10599 'expand_downwards: while end_row < buffer.max_point().row {
10600 let next_row = end_row + 1;
10601 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10602 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10603 {
10604 end_row = next_row;
10605 } else {
10606 break 'expand_downwards;
10607 }
10608 }
10609 }
10610
10611 let start = Point::new(start_row, 0);
10612 let start_offset = start.to_offset(&buffer);
10613 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10614 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10615 let Some(lines_without_prefixes) = selection_text
10616 .lines()
10617 .map(|line| {
10618 line.strip_prefix(&line_prefix)
10619 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10620 .ok_or_else(|| {
10621 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10622 })
10623 })
10624 .collect::<Result<Vec<_>, _>>()
10625 .log_err()
10626 else {
10627 continue;
10628 };
10629
10630 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10631 buffer
10632 .language_settings_at(Point::new(start_row, 0), cx)
10633 .preferred_line_length as usize
10634 });
10635 let wrapped_text = wrap_with_prefix(
10636 line_prefix,
10637 lines_without_prefixes.join("\n"),
10638 wrap_column,
10639 tab_size,
10640 options.preserve_existing_whitespace,
10641 );
10642
10643 // TODO: should always use char-based diff while still supporting cursor behavior that
10644 // matches vim.
10645 let mut diff_options = DiffOptions::default();
10646 if options.override_language_settings {
10647 diff_options.max_word_diff_len = 0;
10648 diff_options.max_word_diff_line_count = 0;
10649 } else {
10650 diff_options.max_word_diff_len = usize::MAX;
10651 diff_options.max_word_diff_line_count = usize::MAX;
10652 }
10653
10654 for (old_range, new_text) in
10655 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10656 {
10657 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10658 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10659 edits.push((edit_start..edit_end, new_text));
10660 }
10661
10662 rewrapped_row_ranges.push(start_row..=end_row);
10663 }
10664
10665 self.buffer
10666 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10667 }
10668
10669 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10670 let mut text = String::new();
10671 let buffer = self.buffer.read(cx).snapshot(cx);
10672 let mut selections = self.selections.all::<Point>(cx);
10673 let mut clipboard_selections = Vec::with_capacity(selections.len());
10674 {
10675 let max_point = buffer.max_point();
10676 let mut is_first = true;
10677 for selection in &mut selections {
10678 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10679 if is_entire_line {
10680 selection.start = Point::new(selection.start.row, 0);
10681 if !selection.is_empty() && selection.end.column == 0 {
10682 selection.end = cmp::min(max_point, selection.end);
10683 } else {
10684 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10685 }
10686 selection.goal = SelectionGoal::None;
10687 }
10688 if is_first {
10689 is_first = false;
10690 } else {
10691 text += "\n";
10692 }
10693 let mut len = 0;
10694 for chunk in buffer.text_for_range(selection.start..selection.end) {
10695 text.push_str(chunk);
10696 len += chunk.len();
10697 }
10698 clipboard_selections.push(ClipboardSelection {
10699 len,
10700 is_entire_line,
10701 first_line_indent: buffer
10702 .indent_size_for_line(MultiBufferRow(selection.start.row))
10703 .len,
10704 });
10705 }
10706 }
10707
10708 self.transact(window, cx, |this, window, cx| {
10709 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10710 s.select(selections);
10711 });
10712 this.insert("", window, cx);
10713 });
10714 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10715 }
10716
10717 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10718 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10719 let item = self.cut_common(window, cx);
10720 cx.write_to_clipboard(item);
10721 }
10722
10723 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10724 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10725 self.change_selections(None, window, cx, |s| {
10726 s.move_with(|snapshot, sel| {
10727 if sel.is_empty() {
10728 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10729 }
10730 });
10731 });
10732 let item = self.cut_common(window, cx);
10733 cx.set_global(KillRing(item))
10734 }
10735
10736 pub fn kill_ring_yank(
10737 &mut self,
10738 _: &KillRingYank,
10739 window: &mut Window,
10740 cx: &mut Context<Self>,
10741 ) {
10742 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10743 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10744 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10745 (kill_ring.text().to_string(), kill_ring.metadata_json())
10746 } else {
10747 return;
10748 }
10749 } else {
10750 return;
10751 };
10752 self.do_paste(&text, metadata, false, window, cx);
10753 }
10754
10755 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10756 self.do_copy(true, cx);
10757 }
10758
10759 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10760 self.do_copy(false, cx);
10761 }
10762
10763 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10764 let selections = self.selections.all::<Point>(cx);
10765 let buffer = self.buffer.read(cx).read(cx);
10766 let mut text = String::new();
10767
10768 let mut clipboard_selections = Vec::with_capacity(selections.len());
10769 {
10770 let max_point = buffer.max_point();
10771 let mut is_first = true;
10772 for selection in &selections {
10773 let mut start = selection.start;
10774 let mut end = selection.end;
10775 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10776 if is_entire_line {
10777 start = Point::new(start.row, 0);
10778 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10779 }
10780
10781 let mut trimmed_selections = Vec::new();
10782 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10783 let row = MultiBufferRow(start.row);
10784 let first_indent = buffer.indent_size_for_line(row);
10785 if first_indent.len == 0 || start.column > first_indent.len {
10786 trimmed_selections.push(start..end);
10787 } else {
10788 trimmed_selections.push(
10789 Point::new(row.0, first_indent.len)
10790 ..Point::new(row.0, buffer.line_len(row)),
10791 );
10792 for row in start.row + 1..=end.row {
10793 let mut line_len = buffer.line_len(MultiBufferRow(row));
10794 if row == end.row {
10795 line_len = end.column;
10796 }
10797 if line_len == 0 {
10798 trimmed_selections
10799 .push(Point::new(row, 0)..Point::new(row, line_len));
10800 continue;
10801 }
10802 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10803 if row_indent_size.len >= first_indent.len {
10804 trimmed_selections.push(
10805 Point::new(row, first_indent.len)..Point::new(row, line_len),
10806 );
10807 } else {
10808 trimmed_selections.clear();
10809 trimmed_selections.push(start..end);
10810 break;
10811 }
10812 }
10813 }
10814 } else {
10815 trimmed_selections.push(start..end);
10816 }
10817
10818 for trimmed_range in trimmed_selections {
10819 if is_first {
10820 is_first = false;
10821 } else {
10822 text += "\n";
10823 }
10824 let mut len = 0;
10825 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10826 text.push_str(chunk);
10827 len += chunk.len();
10828 }
10829 clipboard_selections.push(ClipboardSelection {
10830 len,
10831 is_entire_line,
10832 first_line_indent: buffer
10833 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10834 .len,
10835 });
10836 }
10837 }
10838 }
10839
10840 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10841 text,
10842 clipboard_selections,
10843 ));
10844 }
10845
10846 pub fn do_paste(
10847 &mut self,
10848 text: &String,
10849 clipboard_selections: Option<Vec<ClipboardSelection>>,
10850 handle_entire_lines: bool,
10851 window: &mut Window,
10852 cx: &mut Context<Self>,
10853 ) {
10854 if self.read_only(cx) {
10855 return;
10856 }
10857
10858 let clipboard_text = Cow::Borrowed(text);
10859
10860 self.transact(window, cx, |this, window, cx| {
10861 if let Some(mut clipboard_selections) = clipboard_selections {
10862 let old_selections = this.selections.all::<usize>(cx);
10863 let all_selections_were_entire_line =
10864 clipboard_selections.iter().all(|s| s.is_entire_line);
10865 let first_selection_indent_column =
10866 clipboard_selections.first().map(|s| s.first_line_indent);
10867 if clipboard_selections.len() != old_selections.len() {
10868 clipboard_selections.drain(..);
10869 }
10870 let cursor_offset = this.selections.last::<usize>(cx).head();
10871 let mut auto_indent_on_paste = true;
10872
10873 this.buffer.update(cx, |buffer, cx| {
10874 let snapshot = buffer.read(cx);
10875 auto_indent_on_paste = snapshot
10876 .language_settings_at(cursor_offset, cx)
10877 .auto_indent_on_paste;
10878
10879 let mut start_offset = 0;
10880 let mut edits = Vec::new();
10881 let mut original_indent_columns = Vec::new();
10882 for (ix, selection) in old_selections.iter().enumerate() {
10883 let to_insert;
10884 let entire_line;
10885 let original_indent_column;
10886 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10887 let end_offset = start_offset + clipboard_selection.len;
10888 to_insert = &clipboard_text[start_offset..end_offset];
10889 entire_line = clipboard_selection.is_entire_line;
10890 start_offset = end_offset + 1;
10891 original_indent_column = Some(clipboard_selection.first_line_indent);
10892 } else {
10893 to_insert = clipboard_text.as_str();
10894 entire_line = all_selections_were_entire_line;
10895 original_indent_column = first_selection_indent_column
10896 }
10897
10898 // If the corresponding selection was empty when this slice of the
10899 // clipboard text was written, then the entire line containing the
10900 // selection was copied. If this selection is also currently empty,
10901 // then paste the line before the current line of the buffer.
10902 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10903 let column = selection.start.to_point(&snapshot).column as usize;
10904 let line_start = selection.start - column;
10905 line_start..line_start
10906 } else {
10907 selection.range()
10908 };
10909
10910 edits.push((range, to_insert));
10911 original_indent_columns.push(original_indent_column);
10912 }
10913 drop(snapshot);
10914
10915 buffer.edit(
10916 edits,
10917 if auto_indent_on_paste {
10918 Some(AutoindentMode::Block {
10919 original_indent_columns,
10920 })
10921 } else {
10922 None
10923 },
10924 cx,
10925 );
10926 });
10927
10928 let selections = this.selections.all::<usize>(cx);
10929 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10930 s.select(selections)
10931 });
10932 } else {
10933 this.insert(&clipboard_text, window, cx);
10934 }
10935 });
10936 }
10937
10938 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10939 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10940 if let Some(item) = cx.read_from_clipboard() {
10941 let entries = item.entries();
10942
10943 match entries.first() {
10944 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10945 // of all the pasted entries.
10946 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10947 .do_paste(
10948 clipboard_string.text(),
10949 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10950 true,
10951 window,
10952 cx,
10953 ),
10954 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10955 }
10956 }
10957 }
10958
10959 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10960 if self.read_only(cx) {
10961 return;
10962 }
10963
10964 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10965
10966 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10967 if let Some((selections, _)) =
10968 self.selection_history.transaction(transaction_id).cloned()
10969 {
10970 self.change_selections(None, window, cx, |s| {
10971 s.select_anchors(selections.to_vec());
10972 });
10973 } else {
10974 log::error!(
10975 "No entry in selection_history found for undo. \
10976 This may correspond to a bug where undo does not update the selection. \
10977 If this is occurring, please add details to \
10978 https://github.com/zed-industries/zed/issues/22692"
10979 );
10980 }
10981 self.request_autoscroll(Autoscroll::fit(), cx);
10982 self.unmark_text(window, cx);
10983 self.refresh_inline_completion(true, false, window, cx);
10984 cx.emit(EditorEvent::Edited { transaction_id });
10985 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10986 }
10987 }
10988
10989 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10990 if self.read_only(cx) {
10991 return;
10992 }
10993
10994 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10995
10996 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10997 if let Some((_, Some(selections))) =
10998 self.selection_history.transaction(transaction_id).cloned()
10999 {
11000 self.change_selections(None, window, cx, |s| {
11001 s.select_anchors(selections.to_vec());
11002 });
11003 } else {
11004 log::error!(
11005 "No entry in selection_history found for redo. \
11006 This may correspond to a bug where undo does not update the selection. \
11007 If this is occurring, please add details to \
11008 https://github.com/zed-industries/zed/issues/22692"
11009 );
11010 }
11011 self.request_autoscroll(Autoscroll::fit(), cx);
11012 self.unmark_text(window, cx);
11013 self.refresh_inline_completion(true, false, window, cx);
11014 cx.emit(EditorEvent::Edited { transaction_id });
11015 }
11016 }
11017
11018 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11019 self.buffer
11020 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11021 }
11022
11023 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11024 self.buffer
11025 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11026 }
11027
11028 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11029 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11030 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11031 s.move_with(|map, selection| {
11032 let cursor = if selection.is_empty() {
11033 movement::left(map, selection.start)
11034 } else {
11035 selection.start
11036 };
11037 selection.collapse_to(cursor, SelectionGoal::None);
11038 });
11039 })
11040 }
11041
11042 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11043 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11044 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11045 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11046 })
11047 }
11048
11049 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11050 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11051 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11052 s.move_with(|map, selection| {
11053 let cursor = if selection.is_empty() {
11054 movement::right(map, selection.end)
11055 } else {
11056 selection.end
11057 };
11058 selection.collapse_to(cursor, SelectionGoal::None)
11059 });
11060 })
11061 }
11062
11063 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11064 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11065 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11066 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11067 })
11068 }
11069
11070 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11071 if self.take_rename(true, window, cx).is_some() {
11072 return;
11073 }
11074
11075 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11076 cx.propagate();
11077 return;
11078 }
11079
11080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11081
11082 let text_layout_details = &self.text_layout_details(window);
11083 let selection_count = self.selections.count();
11084 let first_selection = self.selections.first_anchor();
11085
11086 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11087 s.move_with(|map, selection| {
11088 if !selection.is_empty() {
11089 selection.goal = SelectionGoal::None;
11090 }
11091 let (cursor, goal) = movement::up(
11092 map,
11093 selection.start,
11094 selection.goal,
11095 false,
11096 text_layout_details,
11097 );
11098 selection.collapse_to(cursor, goal);
11099 });
11100 });
11101
11102 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11103 {
11104 cx.propagate();
11105 }
11106 }
11107
11108 pub fn move_up_by_lines(
11109 &mut self,
11110 action: &MoveUpByLines,
11111 window: &mut Window,
11112 cx: &mut Context<Self>,
11113 ) {
11114 if self.take_rename(true, window, cx).is_some() {
11115 return;
11116 }
11117
11118 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11119 cx.propagate();
11120 return;
11121 }
11122
11123 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11124
11125 let text_layout_details = &self.text_layout_details(window);
11126
11127 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11128 s.move_with(|map, selection| {
11129 if !selection.is_empty() {
11130 selection.goal = SelectionGoal::None;
11131 }
11132 let (cursor, goal) = movement::up_by_rows(
11133 map,
11134 selection.start,
11135 action.lines,
11136 selection.goal,
11137 false,
11138 text_layout_details,
11139 );
11140 selection.collapse_to(cursor, goal);
11141 });
11142 })
11143 }
11144
11145 pub fn move_down_by_lines(
11146 &mut self,
11147 action: &MoveDownByLines,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 ) {
11151 if self.take_rename(true, window, cx).is_some() {
11152 return;
11153 }
11154
11155 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11156 cx.propagate();
11157 return;
11158 }
11159
11160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11161
11162 let text_layout_details = &self.text_layout_details(window);
11163
11164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11165 s.move_with(|map, selection| {
11166 if !selection.is_empty() {
11167 selection.goal = SelectionGoal::None;
11168 }
11169 let (cursor, goal) = movement::down_by_rows(
11170 map,
11171 selection.start,
11172 action.lines,
11173 selection.goal,
11174 false,
11175 text_layout_details,
11176 );
11177 selection.collapse_to(cursor, goal);
11178 });
11179 })
11180 }
11181
11182 pub fn select_down_by_lines(
11183 &mut self,
11184 action: &SelectDownByLines,
11185 window: &mut Window,
11186 cx: &mut Context<Self>,
11187 ) {
11188 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11189 let text_layout_details = &self.text_layout_details(window);
11190 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11191 s.move_heads_with(|map, head, goal| {
11192 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11193 })
11194 })
11195 }
11196
11197 pub fn select_up_by_lines(
11198 &mut self,
11199 action: &SelectUpByLines,
11200 window: &mut Window,
11201 cx: &mut Context<Self>,
11202 ) {
11203 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11204 let text_layout_details = &self.text_layout_details(window);
11205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11206 s.move_heads_with(|map, head, goal| {
11207 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11208 })
11209 })
11210 }
11211
11212 pub fn select_page_up(
11213 &mut self,
11214 _: &SelectPageUp,
11215 window: &mut Window,
11216 cx: &mut Context<Self>,
11217 ) {
11218 let Some(row_count) = self.visible_row_count() else {
11219 return;
11220 };
11221
11222 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11223
11224 let text_layout_details = &self.text_layout_details(window);
11225
11226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11227 s.move_heads_with(|map, head, goal| {
11228 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11229 })
11230 })
11231 }
11232
11233 pub fn move_page_up(
11234 &mut self,
11235 action: &MovePageUp,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 if self.take_rename(true, window, cx).is_some() {
11240 return;
11241 }
11242
11243 if self
11244 .context_menu
11245 .borrow_mut()
11246 .as_mut()
11247 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
11248 .unwrap_or(false)
11249 {
11250 return;
11251 }
11252
11253 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11254 cx.propagate();
11255 return;
11256 }
11257
11258 let Some(row_count) = self.visible_row_count() else {
11259 return;
11260 };
11261
11262 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11263
11264 let autoscroll = if action.center_cursor {
11265 Autoscroll::center()
11266 } else {
11267 Autoscroll::fit()
11268 };
11269
11270 let text_layout_details = &self.text_layout_details(window);
11271
11272 self.change_selections(Some(autoscroll), window, cx, |s| {
11273 s.move_with(|map, selection| {
11274 if !selection.is_empty() {
11275 selection.goal = SelectionGoal::None;
11276 }
11277 let (cursor, goal) = movement::up_by_rows(
11278 map,
11279 selection.end,
11280 row_count,
11281 selection.goal,
11282 false,
11283 text_layout_details,
11284 );
11285 selection.collapse_to(cursor, goal);
11286 });
11287 });
11288 }
11289
11290 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11292 let text_layout_details = &self.text_layout_details(window);
11293 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11294 s.move_heads_with(|map, head, goal| {
11295 movement::up(map, head, goal, false, text_layout_details)
11296 })
11297 })
11298 }
11299
11300 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11301 self.take_rename(true, window, cx);
11302
11303 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11304 cx.propagate();
11305 return;
11306 }
11307
11308 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11309
11310 let text_layout_details = &self.text_layout_details(window);
11311 let selection_count = self.selections.count();
11312 let first_selection = self.selections.first_anchor();
11313
11314 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11315 s.move_with(|map, selection| {
11316 if !selection.is_empty() {
11317 selection.goal = SelectionGoal::None;
11318 }
11319 let (cursor, goal) = movement::down(
11320 map,
11321 selection.end,
11322 selection.goal,
11323 false,
11324 text_layout_details,
11325 );
11326 selection.collapse_to(cursor, goal);
11327 });
11328 });
11329
11330 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11331 {
11332 cx.propagate();
11333 }
11334 }
11335
11336 pub fn select_page_down(
11337 &mut self,
11338 _: &SelectPageDown,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 ) {
11342 let Some(row_count) = self.visible_row_count() else {
11343 return;
11344 };
11345
11346 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11347
11348 let text_layout_details = &self.text_layout_details(window);
11349
11350 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11351 s.move_heads_with(|map, head, goal| {
11352 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11353 })
11354 })
11355 }
11356
11357 pub fn move_page_down(
11358 &mut self,
11359 action: &MovePageDown,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 if self.take_rename(true, window, cx).is_some() {
11364 return;
11365 }
11366
11367 if self
11368 .context_menu
11369 .borrow_mut()
11370 .as_mut()
11371 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11372 .unwrap_or(false)
11373 {
11374 return;
11375 }
11376
11377 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11378 cx.propagate();
11379 return;
11380 }
11381
11382 let Some(row_count) = self.visible_row_count() else {
11383 return;
11384 };
11385
11386 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11387
11388 let autoscroll = if action.center_cursor {
11389 Autoscroll::center()
11390 } else {
11391 Autoscroll::fit()
11392 };
11393
11394 let text_layout_details = &self.text_layout_details(window);
11395 self.change_selections(Some(autoscroll), window, cx, |s| {
11396 s.move_with(|map, selection| {
11397 if !selection.is_empty() {
11398 selection.goal = SelectionGoal::None;
11399 }
11400 let (cursor, goal) = movement::down_by_rows(
11401 map,
11402 selection.end,
11403 row_count,
11404 selection.goal,
11405 false,
11406 text_layout_details,
11407 );
11408 selection.collapse_to(cursor, goal);
11409 });
11410 });
11411 }
11412
11413 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11415 let text_layout_details = &self.text_layout_details(window);
11416 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11417 s.move_heads_with(|map, head, goal| {
11418 movement::down(map, head, goal, false, text_layout_details)
11419 })
11420 });
11421 }
11422
11423 pub fn context_menu_first(
11424 &mut self,
11425 _: &ContextMenuFirst,
11426 _window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11430 context_menu.select_first(self.completion_provider.as_deref(), cx);
11431 }
11432 }
11433
11434 pub fn context_menu_prev(
11435 &mut self,
11436 _: &ContextMenuPrevious,
11437 _window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11441 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11442 }
11443 }
11444
11445 pub fn context_menu_next(
11446 &mut self,
11447 _: &ContextMenuNext,
11448 _window: &mut Window,
11449 cx: &mut Context<Self>,
11450 ) {
11451 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11452 context_menu.select_next(self.completion_provider.as_deref(), cx);
11453 }
11454 }
11455
11456 pub fn context_menu_last(
11457 &mut self,
11458 _: &ContextMenuLast,
11459 _window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11463 context_menu.select_last(self.completion_provider.as_deref(), cx);
11464 }
11465 }
11466
11467 pub fn move_to_previous_word_start(
11468 &mut self,
11469 _: &MoveToPreviousWordStart,
11470 window: &mut Window,
11471 cx: &mut Context<Self>,
11472 ) {
11473 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11474 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11475 s.move_cursors_with(|map, head, _| {
11476 (
11477 movement::previous_word_start(map, head),
11478 SelectionGoal::None,
11479 )
11480 });
11481 })
11482 }
11483
11484 pub fn move_to_previous_subword_start(
11485 &mut self,
11486 _: &MoveToPreviousSubwordStart,
11487 window: &mut Window,
11488 cx: &mut Context<Self>,
11489 ) {
11490 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11491 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11492 s.move_cursors_with(|map, head, _| {
11493 (
11494 movement::previous_subword_start(map, head),
11495 SelectionGoal::None,
11496 )
11497 });
11498 })
11499 }
11500
11501 pub fn select_to_previous_word_start(
11502 &mut self,
11503 _: &SelectToPreviousWordStart,
11504 window: &mut Window,
11505 cx: &mut Context<Self>,
11506 ) {
11507 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11508 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11509 s.move_heads_with(|map, head, _| {
11510 (
11511 movement::previous_word_start(map, head),
11512 SelectionGoal::None,
11513 )
11514 });
11515 })
11516 }
11517
11518 pub fn select_to_previous_subword_start(
11519 &mut self,
11520 _: &SelectToPreviousSubwordStart,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11525 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11526 s.move_heads_with(|map, head, _| {
11527 (
11528 movement::previous_subword_start(map, head),
11529 SelectionGoal::None,
11530 )
11531 });
11532 })
11533 }
11534
11535 pub fn delete_to_previous_word_start(
11536 &mut self,
11537 action: &DeleteToPreviousWordStart,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11542 self.transact(window, cx, |this, window, cx| {
11543 this.select_autoclose_pair(window, cx);
11544 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11545 s.move_with(|map, selection| {
11546 if selection.is_empty() {
11547 let cursor = if action.ignore_newlines {
11548 movement::previous_word_start(map, selection.head())
11549 } else {
11550 movement::previous_word_start_or_newline(map, selection.head())
11551 };
11552 selection.set_head(cursor, SelectionGoal::None);
11553 }
11554 });
11555 });
11556 this.insert("", window, cx);
11557 });
11558 }
11559
11560 pub fn delete_to_previous_subword_start(
11561 &mut self,
11562 _: &DeleteToPreviousSubwordStart,
11563 window: &mut Window,
11564 cx: &mut Context<Self>,
11565 ) {
11566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11567 self.transact(window, cx, |this, window, cx| {
11568 this.select_autoclose_pair(window, cx);
11569 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11570 s.move_with(|map, selection| {
11571 if selection.is_empty() {
11572 let cursor = movement::previous_subword_start(map, selection.head());
11573 selection.set_head(cursor, SelectionGoal::None);
11574 }
11575 });
11576 });
11577 this.insert("", window, cx);
11578 });
11579 }
11580
11581 pub fn move_to_next_word_end(
11582 &mut self,
11583 _: &MoveToNextWordEnd,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11588 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11589 s.move_cursors_with(|map, head, _| {
11590 (movement::next_word_end(map, head), SelectionGoal::None)
11591 });
11592 })
11593 }
11594
11595 pub fn move_to_next_subword_end(
11596 &mut self,
11597 _: &MoveToNextSubwordEnd,
11598 window: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11602 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11603 s.move_cursors_with(|map, head, _| {
11604 (movement::next_subword_end(map, head), SelectionGoal::None)
11605 });
11606 })
11607 }
11608
11609 pub fn select_to_next_word_end(
11610 &mut self,
11611 _: &SelectToNextWordEnd,
11612 window: &mut Window,
11613 cx: &mut Context<Self>,
11614 ) {
11615 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11616 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11617 s.move_heads_with(|map, head, _| {
11618 (movement::next_word_end(map, head), SelectionGoal::None)
11619 });
11620 })
11621 }
11622
11623 pub fn select_to_next_subword_end(
11624 &mut self,
11625 _: &SelectToNextSubwordEnd,
11626 window: &mut Window,
11627 cx: &mut Context<Self>,
11628 ) {
11629 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11630 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11631 s.move_heads_with(|map, head, _| {
11632 (movement::next_subword_end(map, head), SelectionGoal::None)
11633 });
11634 })
11635 }
11636
11637 pub fn delete_to_next_word_end(
11638 &mut self,
11639 action: &DeleteToNextWordEnd,
11640 window: &mut Window,
11641 cx: &mut Context<Self>,
11642 ) {
11643 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11644 self.transact(window, cx, |this, window, cx| {
11645 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11646 s.move_with(|map, selection| {
11647 if selection.is_empty() {
11648 let cursor = if action.ignore_newlines {
11649 movement::next_word_end(map, selection.head())
11650 } else {
11651 movement::next_word_end_or_newline(map, selection.head())
11652 };
11653 selection.set_head(cursor, SelectionGoal::None);
11654 }
11655 });
11656 });
11657 this.insert("", window, cx);
11658 });
11659 }
11660
11661 pub fn delete_to_next_subword_end(
11662 &mut self,
11663 _: &DeleteToNextSubwordEnd,
11664 window: &mut Window,
11665 cx: &mut Context<Self>,
11666 ) {
11667 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11668 self.transact(window, cx, |this, window, cx| {
11669 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11670 s.move_with(|map, selection| {
11671 if selection.is_empty() {
11672 let cursor = movement::next_subword_end(map, selection.head());
11673 selection.set_head(cursor, SelectionGoal::None);
11674 }
11675 });
11676 });
11677 this.insert("", window, cx);
11678 });
11679 }
11680
11681 pub fn move_to_beginning_of_line(
11682 &mut self,
11683 action: &MoveToBeginningOfLine,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11688 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11689 s.move_cursors_with(|map, head, _| {
11690 (
11691 movement::indented_line_beginning(
11692 map,
11693 head,
11694 action.stop_at_soft_wraps,
11695 action.stop_at_indent,
11696 ),
11697 SelectionGoal::None,
11698 )
11699 });
11700 })
11701 }
11702
11703 pub fn select_to_beginning_of_line(
11704 &mut self,
11705 action: &SelectToBeginningOfLine,
11706 window: &mut Window,
11707 cx: &mut Context<Self>,
11708 ) {
11709 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11710 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11711 s.move_heads_with(|map, head, _| {
11712 (
11713 movement::indented_line_beginning(
11714 map,
11715 head,
11716 action.stop_at_soft_wraps,
11717 action.stop_at_indent,
11718 ),
11719 SelectionGoal::None,
11720 )
11721 });
11722 });
11723 }
11724
11725 pub fn delete_to_beginning_of_line(
11726 &mut self,
11727 action: &DeleteToBeginningOfLine,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11732 self.transact(window, cx, |this, window, cx| {
11733 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11734 s.move_with(|_, selection| {
11735 selection.reversed = true;
11736 });
11737 });
11738
11739 this.select_to_beginning_of_line(
11740 &SelectToBeginningOfLine {
11741 stop_at_soft_wraps: false,
11742 stop_at_indent: action.stop_at_indent,
11743 },
11744 window,
11745 cx,
11746 );
11747 this.backspace(&Backspace, window, cx);
11748 });
11749 }
11750
11751 pub fn move_to_end_of_line(
11752 &mut self,
11753 action: &MoveToEndOfLine,
11754 window: &mut Window,
11755 cx: &mut Context<Self>,
11756 ) {
11757 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11759 s.move_cursors_with(|map, head, _| {
11760 (
11761 movement::line_end(map, head, action.stop_at_soft_wraps),
11762 SelectionGoal::None,
11763 )
11764 });
11765 })
11766 }
11767
11768 pub fn select_to_end_of_line(
11769 &mut self,
11770 action: &SelectToEndOfLine,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11775 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11776 s.move_heads_with(|map, head, _| {
11777 (
11778 movement::line_end(map, head, action.stop_at_soft_wraps),
11779 SelectionGoal::None,
11780 )
11781 });
11782 })
11783 }
11784
11785 pub fn delete_to_end_of_line(
11786 &mut self,
11787 _: &DeleteToEndOfLine,
11788 window: &mut Window,
11789 cx: &mut Context<Self>,
11790 ) {
11791 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11792 self.transact(window, cx, |this, window, cx| {
11793 this.select_to_end_of_line(
11794 &SelectToEndOfLine {
11795 stop_at_soft_wraps: false,
11796 },
11797 window,
11798 cx,
11799 );
11800 this.delete(&Delete, window, cx);
11801 });
11802 }
11803
11804 pub fn cut_to_end_of_line(
11805 &mut self,
11806 _: &CutToEndOfLine,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11811 self.transact(window, cx, |this, window, cx| {
11812 this.select_to_end_of_line(
11813 &SelectToEndOfLine {
11814 stop_at_soft_wraps: false,
11815 },
11816 window,
11817 cx,
11818 );
11819 this.cut(&Cut, window, cx);
11820 });
11821 }
11822
11823 pub fn move_to_start_of_paragraph(
11824 &mut self,
11825 _: &MoveToStartOfParagraph,
11826 window: &mut Window,
11827 cx: &mut Context<Self>,
11828 ) {
11829 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11830 cx.propagate();
11831 return;
11832 }
11833 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11834 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11835 s.move_with(|map, selection| {
11836 selection.collapse_to(
11837 movement::start_of_paragraph(map, selection.head(), 1),
11838 SelectionGoal::None,
11839 )
11840 });
11841 })
11842 }
11843
11844 pub fn move_to_end_of_paragraph(
11845 &mut self,
11846 _: &MoveToEndOfParagraph,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11851 cx.propagate();
11852 return;
11853 }
11854 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11855 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11856 s.move_with(|map, selection| {
11857 selection.collapse_to(
11858 movement::end_of_paragraph(map, selection.head(), 1),
11859 SelectionGoal::None,
11860 )
11861 });
11862 })
11863 }
11864
11865 pub fn select_to_start_of_paragraph(
11866 &mut self,
11867 _: &SelectToStartOfParagraph,
11868 window: &mut Window,
11869 cx: &mut Context<Self>,
11870 ) {
11871 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11872 cx.propagate();
11873 return;
11874 }
11875 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11876 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11877 s.move_heads_with(|map, head, _| {
11878 (
11879 movement::start_of_paragraph(map, head, 1),
11880 SelectionGoal::None,
11881 )
11882 });
11883 })
11884 }
11885
11886 pub fn select_to_end_of_paragraph(
11887 &mut self,
11888 _: &SelectToEndOfParagraph,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11893 cx.propagate();
11894 return;
11895 }
11896 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11897 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11898 s.move_heads_with(|map, head, _| {
11899 (
11900 movement::end_of_paragraph(map, head, 1),
11901 SelectionGoal::None,
11902 )
11903 });
11904 })
11905 }
11906
11907 pub fn move_to_start_of_excerpt(
11908 &mut self,
11909 _: &MoveToStartOfExcerpt,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11914 cx.propagate();
11915 return;
11916 }
11917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11918 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11919 s.move_with(|map, selection| {
11920 selection.collapse_to(
11921 movement::start_of_excerpt(
11922 map,
11923 selection.head(),
11924 workspace::searchable::Direction::Prev,
11925 ),
11926 SelectionGoal::None,
11927 )
11928 });
11929 })
11930 }
11931
11932 pub fn move_to_start_of_next_excerpt(
11933 &mut self,
11934 _: &MoveToStartOfNextExcerpt,
11935 window: &mut Window,
11936 cx: &mut Context<Self>,
11937 ) {
11938 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11939 cx.propagate();
11940 return;
11941 }
11942
11943 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11944 s.move_with(|map, selection| {
11945 selection.collapse_to(
11946 movement::start_of_excerpt(
11947 map,
11948 selection.head(),
11949 workspace::searchable::Direction::Next,
11950 ),
11951 SelectionGoal::None,
11952 )
11953 });
11954 })
11955 }
11956
11957 pub fn move_to_end_of_excerpt(
11958 &mut self,
11959 _: &MoveToEndOfExcerpt,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 ) {
11963 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11964 cx.propagate();
11965 return;
11966 }
11967 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11968 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11969 s.move_with(|map, selection| {
11970 selection.collapse_to(
11971 movement::end_of_excerpt(
11972 map,
11973 selection.head(),
11974 workspace::searchable::Direction::Next,
11975 ),
11976 SelectionGoal::None,
11977 )
11978 });
11979 })
11980 }
11981
11982 pub fn move_to_end_of_previous_excerpt(
11983 &mut self,
11984 _: &MoveToEndOfPreviousExcerpt,
11985 window: &mut Window,
11986 cx: &mut Context<Self>,
11987 ) {
11988 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11989 cx.propagate();
11990 return;
11991 }
11992 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11993 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11994 s.move_with(|map, selection| {
11995 selection.collapse_to(
11996 movement::end_of_excerpt(
11997 map,
11998 selection.head(),
11999 workspace::searchable::Direction::Prev,
12000 ),
12001 SelectionGoal::None,
12002 )
12003 });
12004 })
12005 }
12006
12007 pub fn select_to_start_of_excerpt(
12008 &mut self,
12009 _: &SelectToStartOfExcerpt,
12010 window: &mut Window,
12011 cx: &mut Context<Self>,
12012 ) {
12013 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12014 cx.propagate();
12015 return;
12016 }
12017 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12018 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12019 s.move_heads_with(|map, head, _| {
12020 (
12021 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12022 SelectionGoal::None,
12023 )
12024 });
12025 })
12026 }
12027
12028 pub fn select_to_start_of_next_excerpt(
12029 &mut self,
12030 _: &SelectToStartOfNextExcerpt,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12035 cx.propagate();
12036 return;
12037 }
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12040 s.move_heads_with(|map, head, _| {
12041 (
12042 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12043 SelectionGoal::None,
12044 )
12045 });
12046 })
12047 }
12048
12049 pub fn select_to_end_of_excerpt(
12050 &mut self,
12051 _: &SelectToEndOfExcerpt,
12052 window: &mut Window,
12053 cx: &mut Context<Self>,
12054 ) {
12055 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12056 cx.propagate();
12057 return;
12058 }
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_heads_with(|map, head, _| {
12062 (
12063 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12064 SelectionGoal::None,
12065 )
12066 });
12067 })
12068 }
12069
12070 pub fn select_to_end_of_previous_excerpt(
12071 &mut self,
12072 _: &SelectToEndOfPreviousExcerpt,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12077 cx.propagate();
12078 return;
12079 }
12080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12081 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12082 s.move_heads_with(|map, head, _| {
12083 (
12084 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12085 SelectionGoal::None,
12086 )
12087 });
12088 })
12089 }
12090
12091 pub fn move_to_beginning(
12092 &mut self,
12093 _: &MoveToBeginning,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12098 cx.propagate();
12099 return;
12100 }
12101 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12103 s.select_ranges(vec![0..0]);
12104 });
12105 }
12106
12107 pub fn select_to_beginning(
12108 &mut self,
12109 _: &SelectToBeginning,
12110 window: &mut Window,
12111 cx: &mut Context<Self>,
12112 ) {
12113 let mut selection = self.selections.last::<Point>(cx);
12114 selection.set_head(Point::zero(), SelectionGoal::None);
12115 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12116 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12117 s.select(vec![selection]);
12118 });
12119 }
12120
12121 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12122 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12123 cx.propagate();
12124 return;
12125 }
12126 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12127 let cursor = self.buffer.read(cx).read(cx).len();
12128 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12129 s.select_ranges(vec![cursor..cursor])
12130 });
12131 }
12132
12133 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12134 self.nav_history = nav_history;
12135 }
12136
12137 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12138 self.nav_history.as_ref()
12139 }
12140
12141 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12142 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12143 }
12144
12145 fn push_to_nav_history(
12146 &mut self,
12147 cursor_anchor: Anchor,
12148 new_position: Option<Point>,
12149 is_deactivate: bool,
12150 cx: &mut Context<Self>,
12151 ) {
12152 if let Some(nav_history) = self.nav_history.as_mut() {
12153 let buffer = self.buffer.read(cx).read(cx);
12154 let cursor_position = cursor_anchor.to_point(&buffer);
12155 let scroll_state = self.scroll_manager.anchor();
12156 let scroll_top_row = scroll_state.top_row(&buffer);
12157 drop(buffer);
12158
12159 if let Some(new_position) = new_position {
12160 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12161 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12162 return;
12163 }
12164 }
12165
12166 nav_history.push(
12167 Some(NavigationData {
12168 cursor_anchor,
12169 cursor_position,
12170 scroll_anchor: scroll_state,
12171 scroll_top_row,
12172 }),
12173 cx,
12174 );
12175 cx.emit(EditorEvent::PushedToNavHistory {
12176 anchor: cursor_anchor,
12177 is_deactivate,
12178 })
12179 }
12180 }
12181
12182 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12184 let buffer = self.buffer.read(cx).snapshot(cx);
12185 let mut selection = self.selections.first::<usize>(cx);
12186 selection.set_head(buffer.len(), SelectionGoal::None);
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.select(vec![selection]);
12189 });
12190 }
12191
12192 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12193 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12194 let end = self.buffer.read(cx).read(cx).len();
12195 self.change_selections(None, window, cx, |s| {
12196 s.select_ranges(vec![0..end]);
12197 });
12198 }
12199
12200 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12203 let mut selections = self.selections.all::<Point>(cx);
12204 let max_point = display_map.buffer_snapshot.max_point();
12205 for selection in &mut selections {
12206 let rows = selection.spanned_rows(true, &display_map);
12207 selection.start = Point::new(rows.start.0, 0);
12208 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12209 selection.reversed = false;
12210 }
12211 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12212 s.select(selections);
12213 });
12214 }
12215
12216 pub fn split_selection_into_lines(
12217 &mut self,
12218 _: &SplitSelectionIntoLines,
12219 window: &mut Window,
12220 cx: &mut Context<Self>,
12221 ) {
12222 let selections = self
12223 .selections
12224 .all::<Point>(cx)
12225 .into_iter()
12226 .map(|selection| selection.start..selection.end)
12227 .collect::<Vec<_>>();
12228 self.unfold_ranges(&selections, true, true, cx);
12229
12230 let mut new_selection_ranges = Vec::new();
12231 {
12232 let buffer = self.buffer.read(cx).read(cx);
12233 for selection in selections {
12234 for row in selection.start.row..selection.end.row {
12235 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12236 new_selection_ranges.push(cursor..cursor);
12237 }
12238
12239 let is_multiline_selection = selection.start.row != selection.end.row;
12240 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12241 // so this action feels more ergonomic when paired with other selection operations
12242 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12243 if !should_skip_last {
12244 new_selection_ranges.push(selection.end..selection.end);
12245 }
12246 }
12247 }
12248 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12249 s.select_ranges(new_selection_ranges);
12250 });
12251 }
12252
12253 pub fn add_selection_above(
12254 &mut self,
12255 _: &AddSelectionAbove,
12256 window: &mut Window,
12257 cx: &mut Context<Self>,
12258 ) {
12259 self.add_selection(true, window, cx);
12260 }
12261
12262 pub fn add_selection_below(
12263 &mut self,
12264 _: &AddSelectionBelow,
12265 window: &mut Window,
12266 cx: &mut Context<Self>,
12267 ) {
12268 self.add_selection(false, window, cx);
12269 }
12270
12271 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12272 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12273
12274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12275 let mut selections = self.selections.all::<Point>(cx);
12276 let text_layout_details = self.text_layout_details(window);
12277 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12278 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12279 let range = oldest_selection.display_range(&display_map).sorted();
12280
12281 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12282 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12283 let positions = start_x.min(end_x)..start_x.max(end_x);
12284
12285 selections.clear();
12286 let mut stack = Vec::new();
12287 for row in range.start.row().0..=range.end.row().0 {
12288 if let Some(selection) = self.selections.build_columnar_selection(
12289 &display_map,
12290 DisplayRow(row),
12291 &positions,
12292 oldest_selection.reversed,
12293 &text_layout_details,
12294 ) {
12295 stack.push(selection.id);
12296 selections.push(selection);
12297 }
12298 }
12299
12300 if above {
12301 stack.reverse();
12302 }
12303
12304 AddSelectionsState { above, stack }
12305 });
12306
12307 let last_added_selection = *state.stack.last().unwrap();
12308 let mut new_selections = Vec::new();
12309 if above == state.above {
12310 let end_row = if above {
12311 DisplayRow(0)
12312 } else {
12313 display_map.max_point().row()
12314 };
12315
12316 'outer: for selection in selections {
12317 if selection.id == last_added_selection {
12318 let range = selection.display_range(&display_map).sorted();
12319 debug_assert_eq!(range.start.row(), range.end.row());
12320 let mut row = range.start.row();
12321 let positions =
12322 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12323 px(start)..px(end)
12324 } else {
12325 let start_x =
12326 display_map.x_for_display_point(range.start, &text_layout_details);
12327 let end_x =
12328 display_map.x_for_display_point(range.end, &text_layout_details);
12329 start_x.min(end_x)..start_x.max(end_x)
12330 };
12331
12332 while row != end_row {
12333 if above {
12334 row.0 -= 1;
12335 } else {
12336 row.0 += 1;
12337 }
12338
12339 if let Some(new_selection) = self.selections.build_columnar_selection(
12340 &display_map,
12341 row,
12342 &positions,
12343 selection.reversed,
12344 &text_layout_details,
12345 ) {
12346 state.stack.push(new_selection.id);
12347 if above {
12348 new_selections.push(new_selection);
12349 new_selections.push(selection);
12350 } else {
12351 new_selections.push(selection);
12352 new_selections.push(new_selection);
12353 }
12354
12355 continue 'outer;
12356 }
12357 }
12358 }
12359
12360 new_selections.push(selection);
12361 }
12362 } else {
12363 new_selections = selections;
12364 new_selections.retain(|s| s.id != last_added_selection);
12365 state.stack.pop();
12366 }
12367
12368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12369 s.select(new_selections);
12370 });
12371 if state.stack.len() > 1 {
12372 self.add_selections_state = Some(state);
12373 }
12374 }
12375
12376 fn select_match_ranges(
12377 &mut self,
12378 range: Range<usize>,
12379 reversed: bool,
12380 replace_newest: bool,
12381 auto_scroll: Option<Autoscroll>,
12382 window: &mut Window,
12383 cx: &mut Context<Editor>,
12384 ) {
12385 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12386 self.change_selections(auto_scroll, window, cx, |s| {
12387 if replace_newest {
12388 s.delete(s.newest_anchor().id);
12389 }
12390 if reversed {
12391 s.insert_range(range.end..range.start);
12392 } else {
12393 s.insert_range(range);
12394 }
12395 });
12396 }
12397
12398 pub fn select_next_match_internal(
12399 &mut self,
12400 display_map: &DisplaySnapshot,
12401 replace_newest: bool,
12402 autoscroll: Option<Autoscroll>,
12403 window: &mut Window,
12404 cx: &mut Context<Self>,
12405 ) -> Result<()> {
12406 let buffer = &display_map.buffer_snapshot;
12407 let mut selections = self.selections.all::<usize>(cx);
12408 if let Some(mut select_next_state) = self.select_next_state.take() {
12409 let query = &select_next_state.query;
12410 if !select_next_state.done {
12411 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12412 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12413 let mut next_selected_range = None;
12414
12415 let bytes_after_last_selection =
12416 buffer.bytes_in_range(last_selection.end..buffer.len());
12417 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12418 let query_matches = query
12419 .stream_find_iter(bytes_after_last_selection)
12420 .map(|result| (last_selection.end, result))
12421 .chain(
12422 query
12423 .stream_find_iter(bytes_before_first_selection)
12424 .map(|result| (0, result)),
12425 );
12426
12427 for (start_offset, query_match) in query_matches {
12428 let query_match = query_match.unwrap(); // can only fail due to I/O
12429 let offset_range =
12430 start_offset + query_match.start()..start_offset + query_match.end();
12431 let display_range = offset_range.start.to_display_point(display_map)
12432 ..offset_range.end.to_display_point(display_map);
12433
12434 if !select_next_state.wordwise
12435 || (!movement::is_inside_word(display_map, display_range.start)
12436 && !movement::is_inside_word(display_map, display_range.end))
12437 {
12438 // TODO: This is n^2, because we might check all the selections
12439 if !selections
12440 .iter()
12441 .any(|selection| selection.range().overlaps(&offset_range))
12442 {
12443 next_selected_range = Some(offset_range);
12444 break;
12445 }
12446 }
12447 }
12448
12449 if let Some(next_selected_range) = next_selected_range {
12450 self.select_match_ranges(
12451 next_selected_range,
12452 last_selection.reversed,
12453 replace_newest,
12454 autoscroll,
12455 window,
12456 cx,
12457 );
12458 } else {
12459 select_next_state.done = true;
12460 }
12461 }
12462
12463 self.select_next_state = Some(select_next_state);
12464 } else {
12465 let mut only_carets = true;
12466 let mut same_text_selected = true;
12467 let mut selected_text = None;
12468
12469 let mut selections_iter = selections.iter().peekable();
12470 while let Some(selection) = selections_iter.next() {
12471 if selection.start != selection.end {
12472 only_carets = false;
12473 }
12474
12475 if same_text_selected {
12476 if selected_text.is_none() {
12477 selected_text =
12478 Some(buffer.text_for_range(selection.range()).collect::<String>());
12479 }
12480
12481 if let Some(next_selection) = selections_iter.peek() {
12482 if next_selection.range().len() == selection.range().len() {
12483 let next_selected_text = buffer
12484 .text_for_range(next_selection.range())
12485 .collect::<String>();
12486 if Some(next_selected_text) != selected_text {
12487 same_text_selected = false;
12488 selected_text = None;
12489 }
12490 } else {
12491 same_text_selected = false;
12492 selected_text = None;
12493 }
12494 }
12495 }
12496 }
12497
12498 if only_carets {
12499 for selection in &mut selections {
12500 let word_range = movement::surrounding_word(
12501 display_map,
12502 selection.start.to_display_point(display_map),
12503 );
12504 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12505 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12506 selection.goal = SelectionGoal::None;
12507 selection.reversed = false;
12508 self.select_match_ranges(
12509 selection.start..selection.end,
12510 selection.reversed,
12511 replace_newest,
12512 autoscroll,
12513 window,
12514 cx,
12515 );
12516 }
12517
12518 if selections.len() == 1 {
12519 let selection = selections
12520 .last()
12521 .expect("ensured that there's only one selection");
12522 let query = buffer
12523 .text_for_range(selection.start..selection.end)
12524 .collect::<String>();
12525 let is_empty = query.is_empty();
12526 let select_state = SelectNextState {
12527 query: AhoCorasick::new(&[query])?,
12528 wordwise: true,
12529 done: is_empty,
12530 };
12531 self.select_next_state = Some(select_state);
12532 } else {
12533 self.select_next_state = None;
12534 }
12535 } else if let Some(selected_text) = selected_text {
12536 self.select_next_state = Some(SelectNextState {
12537 query: AhoCorasick::new(&[selected_text])?,
12538 wordwise: false,
12539 done: false,
12540 });
12541 self.select_next_match_internal(
12542 display_map,
12543 replace_newest,
12544 autoscroll,
12545 window,
12546 cx,
12547 )?;
12548 }
12549 }
12550 Ok(())
12551 }
12552
12553 pub fn select_all_matches(
12554 &mut self,
12555 _action: &SelectAllMatches,
12556 window: &mut Window,
12557 cx: &mut Context<Self>,
12558 ) -> Result<()> {
12559 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12560
12561 self.push_to_selection_history();
12562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12563
12564 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12565 let Some(select_next_state) = self.select_next_state.as_mut() else {
12566 return Ok(());
12567 };
12568 if select_next_state.done {
12569 return Ok(());
12570 }
12571
12572 let mut new_selections = Vec::new();
12573
12574 let reversed = self.selections.oldest::<usize>(cx).reversed;
12575 let buffer = &display_map.buffer_snapshot;
12576 let query_matches = select_next_state
12577 .query
12578 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12579
12580 for query_match in query_matches.into_iter() {
12581 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12582 let offset_range = if reversed {
12583 query_match.end()..query_match.start()
12584 } else {
12585 query_match.start()..query_match.end()
12586 };
12587 let display_range = offset_range.start.to_display_point(&display_map)
12588 ..offset_range.end.to_display_point(&display_map);
12589
12590 if !select_next_state.wordwise
12591 || (!movement::is_inside_word(&display_map, display_range.start)
12592 && !movement::is_inside_word(&display_map, display_range.end))
12593 {
12594 new_selections.push(offset_range.start..offset_range.end);
12595 }
12596 }
12597
12598 select_next_state.done = true;
12599 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12600 self.change_selections(None, window, cx, |selections| {
12601 selections.select_ranges(new_selections)
12602 });
12603
12604 Ok(())
12605 }
12606
12607 pub fn select_next(
12608 &mut self,
12609 action: &SelectNext,
12610 window: &mut Window,
12611 cx: &mut Context<Self>,
12612 ) -> Result<()> {
12613 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12614 self.push_to_selection_history();
12615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12616 self.select_next_match_internal(
12617 &display_map,
12618 action.replace_newest,
12619 Some(Autoscroll::newest()),
12620 window,
12621 cx,
12622 )?;
12623 Ok(())
12624 }
12625
12626 pub fn select_previous(
12627 &mut self,
12628 action: &SelectPrevious,
12629 window: &mut Window,
12630 cx: &mut Context<Self>,
12631 ) -> Result<()> {
12632 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12633 self.push_to_selection_history();
12634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12635 let buffer = &display_map.buffer_snapshot;
12636 let mut selections = self.selections.all::<usize>(cx);
12637 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12638 let query = &select_prev_state.query;
12639 if !select_prev_state.done {
12640 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12641 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12642 let mut next_selected_range = None;
12643 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12644 let bytes_before_last_selection =
12645 buffer.reversed_bytes_in_range(0..last_selection.start);
12646 let bytes_after_first_selection =
12647 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12648 let query_matches = query
12649 .stream_find_iter(bytes_before_last_selection)
12650 .map(|result| (last_selection.start, result))
12651 .chain(
12652 query
12653 .stream_find_iter(bytes_after_first_selection)
12654 .map(|result| (buffer.len(), result)),
12655 );
12656 for (end_offset, query_match) in query_matches {
12657 let query_match = query_match.unwrap(); // can only fail due to I/O
12658 let offset_range =
12659 end_offset - query_match.end()..end_offset - query_match.start();
12660 let display_range = offset_range.start.to_display_point(&display_map)
12661 ..offset_range.end.to_display_point(&display_map);
12662
12663 if !select_prev_state.wordwise
12664 || (!movement::is_inside_word(&display_map, display_range.start)
12665 && !movement::is_inside_word(&display_map, display_range.end))
12666 {
12667 next_selected_range = Some(offset_range);
12668 break;
12669 }
12670 }
12671
12672 if let Some(next_selected_range) = next_selected_range {
12673 self.select_match_ranges(
12674 next_selected_range,
12675 last_selection.reversed,
12676 action.replace_newest,
12677 Some(Autoscroll::newest()),
12678 window,
12679 cx,
12680 );
12681 } else {
12682 select_prev_state.done = true;
12683 }
12684 }
12685
12686 self.select_prev_state = Some(select_prev_state);
12687 } else {
12688 let mut only_carets = true;
12689 let mut same_text_selected = true;
12690 let mut selected_text = None;
12691
12692 let mut selections_iter = selections.iter().peekable();
12693 while let Some(selection) = selections_iter.next() {
12694 if selection.start != selection.end {
12695 only_carets = false;
12696 }
12697
12698 if same_text_selected {
12699 if selected_text.is_none() {
12700 selected_text =
12701 Some(buffer.text_for_range(selection.range()).collect::<String>());
12702 }
12703
12704 if let Some(next_selection) = selections_iter.peek() {
12705 if next_selection.range().len() == selection.range().len() {
12706 let next_selected_text = buffer
12707 .text_for_range(next_selection.range())
12708 .collect::<String>();
12709 if Some(next_selected_text) != selected_text {
12710 same_text_selected = false;
12711 selected_text = None;
12712 }
12713 } else {
12714 same_text_selected = false;
12715 selected_text = None;
12716 }
12717 }
12718 }
12719 }
12720
12721 if only_carets {
12722 for selection in &mut selections {
12723 let word_range = movement::surrounding_word(
12724 &display_map,
12725 selection.start.to_display_point(&display_map),
12726 );
12727 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12728 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12729 selection.goal = SelectionGoal::None;
12730 selection.reversed = false;
12731 self.select_match_ranges(
12732 selection.start..selection.end,
12733 selection.reversed,
12734 action.replace_newest,
12735 Some(Autoscroll::newest()),
12736 window,
12737 cx,
12738 );
12739 }
12740 if selections.len() == 1 {
12741 let selection = selections
12742 .last()
12743 .expect("ensured that there's only one selection");
12744 let query = buffer
12745 .text_for_range(selection.start..selection.end)
12746 .collect::<String>();
12747 let is_empty = query.is_empty();
12748 let select_state = SelectNextState {
12749 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12750 wordwise: true,
12751 done: is_empty,
12752 };
12753 self.select_prev_state = Some(select_state);
12754 } else {
12755 self.select_prev_state = None;
12756 }
12757 } else if let Some(selected_text) = selected_text {
12758 self.select_prev_state = Some(SelectNextState {
12759 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12760 wordwise: false,
12761 done: false,
12762 });
12763 self.select_previous(action, window, cx)?;
12764 }
12765 }
12766 Ok(())
12767 }
12768
12769 pub fn find_next_match(
12770 &mut self,
12771 _: &FindNextMatch,
12772 window: &mut Window,
12773 cx: &mut Context<Self>,
12774 ) -> Result<()> {
12775 let selections = self.selections.disjoint_anchors();
12776 match selections.first() {
12777 Some(first) if selections.len() >= 2 => {
12778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12779 s.select_ranges([first.range()]);
12780 });
12781 }
12782 _ => self.select_next(
12783 &SelectNext {
12784 replace_newest: true,
12785 },
12786 window,
12787 cx,
12788 )?,
12789 }
12790 Ok(())
12791 }
12792
12793 pub fn find_previous_match(
12794 &mut self,
12795 _: &FindPreviousMatch,
12796 window: &mut Window,
12797 cx: &mut Context<Self>,
12798 ) -> Result<()> {
12799 let selections = self.selections.disjoint_anchors();
12800 match selections.last() {
12801 Some(last) if selections.len() >= 2 => {
12802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12803 s.select_ranges([last.range()]);
12804 });
12805 }
12806 _ => self.select_previous(
12807 &SelectPrevious {
12808 replace_newest: true,
12809 },
12810 window,
12811 cx,
12812 )?,
12813 }
12814 Ok(())
12815 }
12816
12817 pub fn toggle_comments(
12818 &mut self,
12819 action: &ToggleComments,
12820 window: &mut Window,
12821 cx: &mut Context<Self>,
12822 ) {
12823 if self.read_only(cx) {
12824 return;
12825 }
12826 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12827 let text_layout_details = &self.text_layout_details(window);
12828 self.transact(window, cx, |this, window, cx| {
12829 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12830 let mut edits = Vec::new();
12831 let mut selection_edit_ranges = Vec::new();
12832 let mut last_toggled_row = None;
12833 let snapshot = this.buffer.read(cx).read(cx);
12834 let empty_str: Arc<str> = Arc::default();
12835 let mut suffixes_inserted = Vec::new();
12836 let ignore_indent = action.ignore_indent;
12837
12838 fn comment_prefix_range(
12839 snapshot: &MultiBufferSnapshot,
12840 row: MultiBufferRow,
12841 comment_prefix: &str,
12842 comment_prefix_whitespace: &str,
12843 ignore_indent: bool,
12844 ) -> Range<Point> {
12845 let indent_size = if ignore_indent {
12846 0
12847 } else {
12848 snapshot.indent_size_for_line(row).len
12849 };
12850
12851 let start = Point::new(row.0, indent_size);
12852
12853 let mut line_bytes = snapshot
12854 .bytes_in_range(start..snapshot.max_point())
12855 .flatten()
12856 .copied();
12857
12858 // If this line currently begins with the line comment prefix, then record
12859 // the range containing the prefix.
12860 if line_bytes
12861 .by_ref()
12862 .take(comment_prefix.len())
12863 .eq(comment_prefix.bytes())
12864 {
12865 // Include any whitespace that matches the comment prefix.
12866 let matching_whitespace_len = line_bytes
12867 .zip(comment_prefix_whitespace.bytes())
12868 .take_while(|(a, b)| a == b)
12869 .count() as u32;
12870 let end = Point::new(
12871 start.row,
12872 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12873 );
12874 start..end
12875 } else {
12876 start..start
12877 }
12878 }
12879
12880 fn comment_suffix_range(
12881 snapshot: &MultiBufferSnapshot,
12882 row: MultiBufferRow,
12883 comment_suffix: &str,
12884 comment_suffix_has_leading_space: bool,
12885 ) -> Range<Point> {
12886 let end = Point::new(row.0, snapshot.line_len(row));
12887 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12888
12889 let mut line_end_bytes = snapshot
12890 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12891 .flatten()
12892 .copied();
12893
12894 let leading_space_len = if suffix_start_column > 0
12895 && line_end_bytes.next() == Some(b' ')
12896 && comment_suffix_has_leading_space
12897 {
12898 1
12899 } else {
12900 0
12901 };
12902
12903 // If this line currently begins with the line comment prefix, then record
12904 // the range containing the prefix.
12905 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12906 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12907 start..end
12908 } else {
12909 end..end
12910 }
12911 }
12912
12913 // TODO: Handle selections that cross excerpts
12914 for selection in &mut selections {
12915 let start_column = snapshot
12916 .indent_size_for_line(MultiBufferRow(selection.start.row))
12917 .len;
12918 let language = if let Some(language) =
12919 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12920 {
12921 language
12922 } else {
12923 continue;
12924 };
12925
12926 selection_edit_ranges.clear();
12927
12928 // If multiple selections contain a given row, avoid processing that
12929 // row more than once.
12930 let mut start_row = MultiBufferRow(selection.start.row);
12931 if last_toggled_row == Some(start_row) {
12932 start_row = start_row.next_row();
12933 }
12934 let end_row =
12935 if selection.end.row > selection.start.row && selection.end.column == 0 {
12936 MultiBufferRow(selection.end.row - 1)
12937 } else {
12938 MultiBufferRow(selection.end.row)
12939 };
12940 last_toggled_row = Some(end_row);
12941
12942 if start_row > end_row {
12943 continue;
12944 }
12945
12946 // If the language has line comments, toggle those.
12947 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12948
12949 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12950 if ignore_indent {
12951 full_comment_prefixes = full_comment_prefixes
12952 .into_iter()
12953 .map(|s| Arc::from(s.trim_end()))
12954 .collect();
12955 }
12956
12957 if !full_comment_prefixes.is_empty() {
12958 let first_prefix = full_comment_prefixes
12959 .first()
12960 .expect("prefixes is non-empty");
12961 let prefix_trimmed_lengths = full_comment_prefixes
12962 .iter()
12963 .map(|p| p.trim_end_matches(' ').len())
12964 .collect::<SmallVec<[usize; 4]>>();
12965
12966 let mut all_selection_lines_are_comments = true;
12967
12968 for row in start_row.0..=end_row.0 {
12969 let row = MultiBufferRow(row);
12970 if start_row < end_row && snapshot.is_line_blank(row) {
12971 continue;
12972 }
12973
12974 let prefix_range = full_comment_prefixes
12975 .iter()
12976 .zip(prefix_trimmed_lengths.iter().copied())
12977 .map(|(prefix, trimmed_prefix_len)| {
12978 comment_prefix_range(
12979 snapshot.deref(),
12980 row,
12981 &prefix[..trimmed_prefix_len],
12982 &prefix[trimmed_prefix_len..],
12983 ignore_indent,
12984 )
12985 })
12986 .max_by_key(|range| range.end.column - range.start.column)
12987 .expect("prefixes is non-empty");
12988
12989 if prefix_range.is_empty() {
12990 all_selection_lines_are_comments = false;
12991 }
12992
12993 selection_edit_ranges.push(prefix_range);
12994 }
12995
12996 if all_selection_lines_are_comments {
12997 edits.extend(
12998 selection_edit_ranges
12999 .iter()
13000 .cloned()
13001 .map(|range| (range, empty_str.clone())),
13002 );
13003 } else {
13004 let min_column = selection_edit_ranges
13005 .iter()
13006 .map(|range| range.start.column)
13007 .min()
13008 .unwrap_or(0);
13009 edits.extend(selection_edit_ranges.iter().map(|range| {
13010 let position = Point::new(range.start.row, min_column);
13011 (position..position, first_prefix.clone())
13012 }));
13013 }
13014 } else if let Some((full_comment_prefix, comment_suffix)) =
13015 language.block_comment_delimiters()
13016 {
13017 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13018 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13019 let prefix_range = comment_prefix_range(
13020 snapshot.deref(),
13021 start_row,
13022 comment_prefix,
13023 comment_prefix_whitespace,
13024 ignore_indent,
13025 );
13026 let suffix_range = comment_suffix_range(
13027 snapshot.deref(),
13028 end_row,
13029 comment_suffix.trim_start_matches(' '),
13030 comment_suffix.starts_with(' '),
13031 );
13032
13033 if prefix_range.is_empty() || suffix_range.is_empty() {
13034 edits.push((
13035 prefix_range.start..prefix_range.start,
13036 full_comment_prefix.clone(),
13037 ));
13038 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13039 suffixes_inserted.push((end_row, comment_suffix.len()));
13040 } else {
13041 edits.push((prefix_range, empty_str.clone()));
13042 edits.push((suffix_range, empty_str.clone()));
13043 }
13044 } else {
13045 continue;
13046 }
13047 }
13048
13049 drop(snapshot);
13050 this.buffer.update(cx, |buffer, cx| {
13051 buffer.edit(edits, None, cx);
13052 });
13053
13054 // Adjust selections so that they end before any comment suffixes that
13055 // were inserted.
13056 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13057 let mut selections = this.selections.all::<Point>(cx);
13058 let snapshot = this.buffer.read(cx).read(cx);
13059 for selection in &mut selections {
13060 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13061 match row.cmp(&MultiBufferRow(selection.end.row)) {
13062 Ordering::Less => {
13063 suffixes_inserted.next();
13064 continue;
13065 }
13066 Ordering::Greater => break,
13067 Ordering::Equal => {
13068 if selection.end.column == snapshot.line_len(row) {
13069 if selection.is_empty() {
13070 selection.start.column -= suffix_len as u32;
13071 }
13072 selection.end.column -= suffix_len as u32;
13073 }
13074 break;
13075 }
13076 }
13077 }
13078 }
13079
13080 drop(snapshot);
13081 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13082 s.select(selections)
13083 });
13084
13085 let selections = this.selections.all::<Point>(cx);
13086 let selections_on_single_row = selections.windows(2).all(|selections| {
13087 selections[0].start.row == selections[1].start.row
13088 && selections[0].end.row == selections[1].end.row
13089 && selections[0].start.row == selections[0].end.row
13090 });
13091 let selections_selecting = selections
13092 .iter()
13093 .any(|selection| selection.start != selection.end);
13094 let advance_downwards = action.advance_downwards
13095 && selections_on_single_row
13096 && !selections_selecting
13097 && !matches!(this.mode, EditorMode::SingleLine { .. });
13098
13099 if advance_downwards {
13100 let snapshot = this.buffer.read(cx).snapshot(cx);
13101
13102 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13103 s.move_cursors_with(|display_snapshot, display_point, _| {
13104 let mut point = display_point.to_point(display_snapshot);
13105 point.row += 1;
13106 point = snapshot.clip_point(point, Bias::Left);
13107 let display_point = point.to_display_point(display_snapshot);
13108 let goal = SelectionGoal::HorizontalPosition(
13109 display_snapshot
13110 .x_for_display_point(display_point, text_layout_details)
13111 .into(),
13112 );
13113 (display_point, goal)
13114 })
13115 });
13116 }
13117 });
13118 }
13119
13120 pub fn select_enclosing_symbol(
13121 &mut self,
13122 _: &SelectEnclosingSymbol,
13123 window: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) {
13126 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13127
13128 let buffer = self.buffer.read(cx).snapshot(cx);
13129 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13130
13131 fn update_selection(
13132 selection: &Selection<usize>,
13133 buffer_snap: &MultiBufferSnapshot,
13134 ) -> Option<Selection<usize>> {
13135 let cursor = selection.head();
13136 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13137 for symbol in symbols.iter().rev() {
13138 let start = symbol.range.start.to_offset(buffer_snap);
13139 let end = symbol.range.end.to_offset(buffer_snap);
13140 let new_range = start..end;
13141 if start < selection.start || end > selection.end {
13142 return Some(Selection {
13143 id: selection.id,
13144 start: new_range.start,
13145 end: new_range.end,
13146 goal: SelectionGoal::None,
13147 reversed: selection.reversed,
13148 });
13149 }
13150 }
13151 None
13152 }
13153
13154 let mut selected_larger_symbol = false;
13155 let new_selections = old_selections
13156 .iter()
13157 .map(|selection| match update_selection(selection, &buffer) {
13158 Some(new_selection) => {
13159 if new_selection.range() != selection.range() {
13160 selected_larger_symbol = true;
13161 }
13162 new_selection
13163 }
13164 None => selection.clone(),
13165 })
13166 .collect::<Vec<_>>();
13167
13168 if selected_larger_symbol {
13169 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13170 s.select(new_selections);
13171 });
13172 }
13173 }
13174
13175 pub fn select_larger_syntax_node(
13176 &mut self,
13177 _: &SelectLargerSyntaxNode,
13178 window: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 let Some(visible_row_count) = self.visible_row_count() else {
13182 return;
13183 };
13184 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13185 if old_selections.is_empty() {
13186 return;
13187 }
13188
13189 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13190
13191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13192 let buffer = self.buffer.read(cx).snapshot(cx);
13193
13194 let mut selected_larger_node = false;
13195 let mut new_selections = old_selections
13196 .iter()
13197 .map(|selection| {
13198 let old_range = selection.start..selection.end;
13199
13200 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13201 // manually select word at selection
13202 if ["string_content", "inline"].contains(&node.kind()) {
13203 let word_range = {
13204 let display_point = buffer
13205 .offset_to_point(old_range.start)
13206 .to_display_point(&display_map);
13207 let Range { start, end } =
13208 movement::surrounding_word(&display_map, display_point);
13209 start.to_point(&display_map).to_offset(&buffer)
13210 ..end.to_point(&display_map).to_offset(&buffer)
13211 };
13212 // ignore if word is already selected
13213 if !word_range.is_empty() && old_range != word_range {
13214 let last_word_range = {
13215 let display_point = buffer
13216 .offset_to_point(old_range.end)
13217 .to_display_point(&display_map);
13218 let Range { start, end } =
13219 movement::surrounding_word(&display_map, display_point);
13220 start.to_point(&display_map).to_offset(&buffer)
13221 ..end.to_point(&display_map).to_offset(&buffer)
13222 };
13223 // only select word if start and end point belongs to same word
13224 if word_range == last_word_range {
13225 selected_larger_node = true;
13226 return Selection {
13227 id: selection.id,
13228 start: word_range.start,
13229 end: word_range.end,
13230 goal: SelectionGoal::None,
13231 reversed: selection.reversed,
13232 };
13233 }
13234 }
13235 }
13236 }
13237
13238 let mut new_range = old_range.clone();
13239 while let Some((_node, containing_range)) =
13240 buffer.syntax_ancestor(new_range.clone())
13241 {
13242 new_range = match containing_range {
13243 MultiOrSingleBufferOffsetRange::Single(_) => break,
13244 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13245 };
13246 if !display_map.intersects_fold(new_range.start)
13247 && !display_map.intersects_fold(new_range.end)
13248 {
13249 break;
13250 }
13251 }
13252
13253 selected_larger_node |= new_range != old_range;
13254 Selection {
13255 id: selection.id,
13256 start: new_range.start,
13257 end: new_range.end,
13258 goal: SelectionGoal::None,
13259 reversed: selection.reversed,
13260 }
13261 })
13262 .collect::<Vec<_>>();
13263
13264 if !selected_larger_node {
13265 return; // don't put this call in the history
13266 }
13267
13268 // scroll based on transformation done to the last selection created by the user
13269 let (last_old, last_new) = old_selections
13270 .last()
13271 .zip(new_selections.last().cloned())
13272 .expect("old_selections isn't empty");
13273
13274 // revert selection
13275 let is_selection_reversed = {
13276 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13277 new_selections.last_mut().expect("checked above").reversed =
13278 should_newest_selection_be_reversed;
13279 should_newest_selection_be_reversed
13280 };
13281
13282 if selected_larger_node {
13283 self.select_syntax_node_history.disable_clearing = true;
13284 self.change_selections(None, window, cx, |s| {
13285 s.select(new_selections.clone());
13286 });
13287 self.select_syntax_node_history.disable_clearing = false;
13288 }
13289
13290 let start_row = last_new.start.to_display_point(&display_map).row().0;
13291 let end_row = last_new.end.to_display_point(&display_map).row().0;
13292 let selection_height = end_row - start_row + 1;
13293 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13294
13295 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13296 let scroll_behavior = if fits_on_the_screen {
13297 self.request_autoscroll(Autoscroll::fit(), cx);
13298 SelectSyntaxNodeScrollBehavior::FitSelection
13299 } else if is_selection_reversed {
13300 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13301 SelectSyntaxNodeScrollBehavior::CursorTop
13302 } else {
13303 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13304 SelectSyntaxNodeScrollBehavior::CursorBottom
13305 };
13306
13307 self.select_syntax_node_history.push((
13308 old_selections,
13309 scroll_behavior,
13310 is_selection_reversed,
13311 ));
13312 }
13313
13314 pub fn select_smaller_syntax_node(
13315 &mut self,
13316 _: &SelectSmallerSyntaxNode,
13317 window: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) {
13320 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13321
13322 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13323 self.select_syntax_node_history.pop()
13324 {
13325 if let Some(selection) = selections.last_mut() {
13326 selection.reversed = is_selection_reversed;
13327 }
13328
13329 self.select_syntax_node_history.disable_clearing = true;
13330 self.change_selections(None, window, cx, |s| {
13331 s.select(selections.to_vec());
13332 });
13333 self.select_syntax_node_history.disable_clearing = false;
13334
13335 match scroll_behavior {
13336 SelectSyntaxNodeScrollBehavior::CursorTop => {
13337 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13338 }
13339 SelectSyntaxNodeScrollBehavior::FitSelection => {
13340 self.request_autoscroll(Autoscroll::fit(), cx);
13341 }
13342 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13343 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13344 }
13345 }
13346 }
13347 }
13348
13349 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13350 if !EditorSettings::get_global(cx).gutter.runnables {
13351 self.clear_tasks();
13352 return Task::ready(());
13353 }
13354 let project = self.project.as_ref().map(Entity::downgrade);
13355 let task_sources = self.lsp_task_sources(cx);
13356 cx.spawn_in(window, async move |editor, cx| {
13357 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13358 let Some(project) = project.and_then(|p| p.upgrade()) else {
13359 return;
13360 };
13361 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13362 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13363 }) else {
13364 return;
13365 };
13366
13367 let hide_runnables = project
13368 .update(cx, |project, cx| {
13369 // Do not display any test indicators in non-dev server remote projects.
13370 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13371 })
13372 .unwrap_or(true);
13373 if hide_runnables {
13374 return;
13375 }
13376 let new_rows =
13377 cx.background_spawn({
13378 let snapshot = display_snapshot.clone();
13379 async move {
13380 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13381 }
13382 })
13383 .await;
13384 let Ok(lsp_tasks) =
13385 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13386 else {
13387 return;
13388 };
13389 let lsp_tasks = lsp_tasks.await;
13390
13391 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13392 lsp_tasks
13393 .into_iter()
13394 .flat_map(|(kind, tasks)| {
13395 tasks.into_iter().filter_map(move |(location, task)| {
13396 Some((kind.clone(), location?, task))
13397 })
13398 })
13399 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13400 let buffer = location.target.buffer;
13401 let buffer_snapshot = buffer.read(cx).snapshot();
13402 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13403 |(excerpt_id, snapshot, _)| {
13404 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13405 display_snapshot
13406 .buffer_snapshot
13407 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13408 } else {
13409 None
13410 }
13411 },
13412 );
13413 if let Some(offset) = offset {
13414 let task_buffer_range =
13415 location.target.range.to_point(&buffer_snapshot);
13416 let context_buffer_range =
13417 task_buffer_range.to_offset(&buffer_snapshot);
13418 let context_range = BufferOffset(context_buffer_range.start)
13419 ..BufferOffset(context_buffer_range.end);
13420
13421 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13422 .or_insert_with(|| RunnableTasks {
13423 templates: Vec::new(),
13424 offset,
13425 column: task_buffer_range.start.column,
13426 extra_variables: HashMap::default(),
13427 context_range,
13428 })
13429 .templates
13430 .push((kind, task.original_task().clone()));
13431 }
13432
13433 acc
13434 })
13435 }) else {
13436 return;
13437 };
13438
13439 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13440 editor
13441 .update(cx, |editor, _| {
13442 editor.clear_tasks();
13443 for (key, mut value) in rows {
13444 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13445 value.templates.extend(lsp_tasks.templates);
13446 }
13447
13448 editor.insert_tasks(key, value);
13449 }
13450 for (key, value) in lsp_tasks_by_rows {
13451 editor.insert_tasks(key, value);
13452 }
13453 })
13454 .ok();
13455 })
13456 }
13457 fn fetch_runnable_ranges(
13458 snapshot: &DisplaySnapshot,
13459 range: Range<Anchor>,
13460 ) -> Vec<language::RunnableRange> {
13461 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13462 }
13463
13464 fn runnable_rows(
13465 project: Entity<Project>,
13466 snapshot: DisplaySnapshot,
13467 runnable_ranges: Vec<RunnableRange>,
13468 mut cx: AsyncWindowContext,
13469 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13470 runnable_ranges
13471 .into_iter()
13472 .filter_map(|mut runnable| {
13473 let tasks = cx
13474 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13475 .ok()?;
13476 if tasks.is_empty() {
13477 return None;
13478 }
13479
13480 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13481
13482 let row = snapshot
13483 .buffer_snapshot
13484 .buffer_line_for_row(MultiBufferRow(point.row))?
13485 .1
13486 .start
13487 .row;
13488
13489 let context_range =
13490 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13491 Some((
13492 (runnable.buffer_id, row),
13493 RunnableTasks {
13494 templates: tasks,
13495 offset: snapshot
13496 .buffer_snapshot
13497 .anchor_before(runnable.run_range.start),
13498 context_range,
13499 column: point.column,
13500 extra_variables: runnable.extra_captures,
13501 },
13502 ))
13503 })
13504 .collect()
13505 }
13506
13507 fn templates_with_tags(
13508 project: &Entity<Project>,
13509 runnable: &mut Runnable,
13510 cx: &mut App,
13511 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13512 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13513 let (worktree_id, file) = project
13514 .buffer_for_id(runnable.buffer, cx)
13515 .and_then(|buffer| buffer.read(cx).file())
13516 .map(|file| (file.worktree_id(cx), file.clone()))
13517 .unzip();
13518
13519 (
13520 project.task_store().read(cx).task_inventory().cloned(),
13521 worktree_id,
13522 file,
13523 )
13524 });
13525
13526 let mut templates_with_tags = mem::take(&mut runnable.tags)
13527 .into_iter()
13528 .flat_map(|RunnableTag(tag)| {
13529 inventory
13530 .as_ref()
13531 .into_iter()
13532 .flat_map(|inventory| {
13533 inventory.read(cx).list_tasks(
13534 file.clone(),
13535 Some(runnable.language.clone()),
13536 worktree_id,
13537 cx,
13538 )
13539 })
13540 .filter(move |(_, template)| {
13541 template.tags.iter().any(|source_tag| source_tag == &tag)
13542 })
13543 })
13544 .sorted_by_key(|(kind, _)| kind.to_owned())
13545 .collect::<Vec<_>>();
13546 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13547 // Strongest source wins; if we have worktree tag binding, prefer that to
13548 // global and language bindings;
13549 // if we have a global binding, prefer that to language binding.
13550 let first_mismatch = templates_with_tags
13551 .iter()
13552 .position(|(tag_source, _)| tag_source != leading_tag_source);
13553 if let Some(index) = first_mismatch {
13554 templates_with_tags.truncate(index);
13555 }
13556 }
13557
13558 templates_with_tags
13559 }
13560
13561 pub fn move_to_enclosing_bracket(
13562 &mut self,
13563 _: &MoveToEnclosingBracket,
13564 window: &mut Window,
13565 cx: &mut Context<Self>,
13566 ) {
13567 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13568 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13569 s.move_offsets_with(|snapshot, selection| {
13570 let Some(enclosing_bracket_ranges) =
13571 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13572 else {
13573 return;
13574 };
13575
13576 let mut best_length = usize::MAX;
13577 let mut best_inside = false;
13578 let mut best_in_bracket_range = false;
13579 let mut best_destination = None;
13580 for (open, close) in enclosing_bracket_ranges {
13581 let close = close.to_inclusive();
13582 let length = close.end() - open.start;
13583 let inside = selection.start >= open.end && selection.end <= *close.start();
13584 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13585 || close.contains(&selection.head());
13586
13587 // If best is next to a bracket and current isn't, skip
13588 if !in_bracket_range && best_in_bracket_range {
13589 continue;
13590 }
13591
13592 // Prefer smaller lengths unless best is inside and current isn't
13593 if length > best_length && (best_inside || !inside) {
13594 continue;
13595 }
13596
13597 best_length = length;
13598 best_inside = inside;
13599 best_in_bracket_range = in_bracket_range;
13600 best_destination = Some(
13601 if close.contains(&selection.start) && close.contains(&selection.end) {
13602 if inside { open.end } else { open.start }
13603 } else if inside {
13604 *close.start()
13605 } else {
13606 *close.end()
13607 },
13608 );
13609 }
13610
13611 if let Some(destination) = best_destination {
13612 selection.collapse_to(destination, SelectionGoal::None);
13613 }
13614 })
13615 });
13616 }
13617
13618 pub fn undo_selection(
13619 &mut self,
13620 _: &UndoSelection,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13625 self.end_selection(window, cx);
13626 self.selection_history.mode = SelectionHistoryMode::Undoing;
13627 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13628 self.change_selections(None, window, cx, |s| {
13629 s.select_anchors(entry.selections.to_vec())
13630 });
13631 self.select_next_state = entry.select_next_state;
13632 self.select_prev_state = entry.select_prev_state;
13633 self.add_selections_state = entry.add_selections_state;
13634 self.request_autoscroll(Autoscroll::newest(), cx);
13635 }
13636 self.selection_history.mode = SelectionHistoryMode::Normal;
13637 }
13638
13639 pub fn redo_selection(
13640 &mut self,
13641 _: &RedoSelection,
13642 window: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) {
13645 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13646 self.end_selection(window, cx);
13647 self.selection_history.mode = SelectionHistoryMode::Redoing;
13648 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13649 self.change_selections(None, window, cx, |s| {
13650 s.select_anchors(entry.selections.to_vec())
13651 });
13652 self.select_next_state = entry.select_next_state;
13653 self.select_prev_state = entry.select_prev_state;
13654 self.add_selections_state = entry.add_selections_state;
13655 self.request_autoscroll(Autoscroll::newest(), cx);
13656 }
13657 self.selection_history.mode = SelectionHistoryMode::Normal;
13658 }
13659
13660 pub fn expand_excerpts(
13661 &mut self,
13662 action: &ExpandExcerpts,
13663 _: &mut Window,
13664 cx: &mut Context<Self>,
13665 ) {
13666 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13667 }
13668
13669 pub fn expand_excerpts_down(
13670 &mut self,
13671 action: &ExpandExcerptsDown,
13672 _: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13676 }
13677
13678 pub fn expand_excerpts_up(
13679 &mut self,
13680 action: &ExpandExcerptsUp,
13681 _: &mut Window,
13682 cx: &mut Context<Self>,
13683 ) {
13684 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13685 }
13686
13687 pub fn expand_excerpts_for_direction(
13688 &mut self,
13689 lines: u32,
13690 direction: ExpandExcerptDirection,
13691
13692 cx: &mut Context<Self>,
13693 ) {
13694 let selections = self.selections.disjoint_anchors();
13695
13696 let lines = if lines == 0 {
13697 EditorSettings::get_global(cx).expand_excerpt_lines
13698 } else {
13699 lines
13700 };
13701
13702 self.buffer.update(cx, |buffer, cx| {
13703 let snapshot = buffer.snapshot(cx);
13704 let mut excerpt_ids = selections
13705 .iter()
13706 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13707 .collect::<Vec<_>>();
13708 excerpt_ids.sort();
13709 excerpt_ids.dedup();
13710 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13711 })
13712 }
13713
13714 pub fn expand_excerpt(
13715 &mut self,
13716 excerpt: ExcerptId,
13717 direction: ExpandExcerptDirection,
13718 window: &mut Window,
13719 cx: &mut Context<Self>,
13720 ) {
13721 let current_scroll_position = self.scroll_position(cx);
13722 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13723 let mut should_scroll_up = false;
13724
13725 if direction == ExpandExcerptDirection::Down {
13726 let multi_buffer = self.buffer.read(cx);
13727 let snapshot = multi_buffer.snapshot(cx);
13728 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13729 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13730 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13731 let buffer_snapshot = buffer.read(cx).snapshot();
13732 let excerpt_end_row =
13733 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13734 let last_row = buffer_snapshot.max_point().row;
13735 let lines_below = last_row.saturating_sub(excerpt_end_row);
13736 should_scroll_up = lines_below >= lines_to_expand;
13737 }
13738 }
13739 }
13740 }
13741
13742 self.buffer.update(cx, |buffer, cx| {
13743 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13744 });
13745
13746 if should_scroll_up {
13747 let new_scroll_position =
13748 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13749 self.set_scroll_position(new_scroll_position, window, cx);
13750 }
13751 }
13752
13753 pub fn go_to_singleton_buffer_point(
13754 &mut self,
13755 point: Point,
13756 window: &mut Window,
13757 cx: &mut Context<Self>,
13758 ) {
13759 self.go_to_singleton_buffer_range(point..point, window, cx);
13760 }
13761
13762 pub fn go_to_singleton_buffer_range(
13763 &mut self,
13764 range: Range<Point>,
13765 window: &mut Window,
13766 cx: &mut Context<Self>,
13767 ) {
13768 let multibuffer = self.buffer().read(cx);
13769 let Some(buffer) = multibuffer.as_singleton() else {
13770 return;
13771 };
13772 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13773 return;
13774 };
13775 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13776 return;
13777 };
13778 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13779 s.select_anchor_ranges([start..end])
13780 });
13781 }
13782
13783 pub fn go_to_diagnostic(
13784 &mut self,
13785 _: &GoToDiagnostic,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) {
13789 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13790 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13791 }
13792
13793 pub fn go_to_prev_diagnostic(
13794 &mut self,
13795 _: &GoToPreviousDiagnostic,
13796 window: &mut Window,
13797 cx: &mut Context<Self>,
13798 ) {
13799 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13800 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13801 }
13802
13803 pub fn go_to_diagnostic_impl(
13804 &mut self,
13805 direction: Direction,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 let buffer = self.buffer.read(cx).snapshot(cx);
13810 let selection = self.selections.newest::<usize>(cx);
13811
13812 let mut active_group_id = None;
13813 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13814 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13815 active_group_id = Some(active_group.group_id);
13816 }
13817 }
13818
13819 fn filtered(
13820 snapshot: EditorSnapshot,
13821 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13822 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13823 diagnostics
13824 .filter(|entry| entry.range.start != entry.range.end)
13825 .filter(|entry| !entry.diagnostic.is_unnecessary)
13826 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13827 }
13828
13829 let snapshot = self.snapshot(window, cx);
13830 let before = filtered(
13831 snapshot.clone(),
13832 buffer
13833 .diagnostics_in_range(0..selection.start)
13834 .filter(|entry| entry.range.start <= selection.start),
13835 );
13836 let after = filtered(
13837 snapshot,
13838 buffer
13839 .diagnostics_in_range(selection.start..buffer.len())
13840 .filter(|entry| entry.range.start >= selection.start),
13841 );
13842
13843 let mut found: Option<DiagnosticEntry<usize>> = None;
13844 if direction == Direction::Prev {
13845 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13846 {
13847 for diagnostic in prev_diagnostics.into_iter().rev() {
13848 if diagnostic.range.start != selection.start
13849 || active_group_id
13850 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13851 {
13852 found = Some(diagnostic);
13853 break 'outer;
13854 }
13855 }
13856 }
13857 } else {
13858 for diagnostic in after.chain(before) {
13859 if diagnostic.range.start != selection.start
13860 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13861 {
13862 found = Some(diagnostic);
13863 break;
13864 }
13865 }
13866 }
13867 let Some(next_diagnostic) = found else {
13868 return;
13869 };
13870
13871 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13872 return;
13873 };
13874 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13875 s.select_ranges(vec![
13876 next_diagnostic.range.start..next_diagnostic.range.start,
13877 ])
13878 });
13879 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13880 self.refresh_inline_completion(false, true, window, cx);
13881 }
13882
13883 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13884 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13885 let snapshot = self.snapshot(window, cx);
13886 let selection = self.selections.newest::<Point>(cx);
13887 self.go_to_hunk_before_or_after_position(
13888 &snapshot,
13889 selection.head(),
13890 Direction::Next,
13891 window,
13892 cx,
13893 );
13894 }
13895
13896 pub fn go_to_hunk_before_or_after_position(
13897 &mut self,
13898 snapshot: &EditorSnapshot,
13899 position: Point,
13900 direction: Direction,
13901 window: &mut Window,
13902 cx: &mut Context<Editor>,
13903 ) {
13904 let row = if direction == Direction::Next {
13905 self.hunk_after_position(snapshot, position)
13906 .map(|hunk| hunk.row_range.start)
13907 } else {
13908 self.hunk_before_position(snapshot, position)
13909 };
13910
13911 if let Some(row) = row {
13912 let destination = Point::new(row.0, 0);
13913 let autoscroll = Autoscroll::center();
13914
13915 self.unfold_ranges(&[destination..destination], false, false, cx);
13916 self.change_selections(Some(autoscroll), window, cx, |s| {
13917 s.select_ranges([destination..destination]);
13918 });
13919 }
13920 }
13921
13922 fn hunk_after_position(
13923 &mut self,
13924 snapshot: &EditorSnapshot,
13925 position: Point,
13926 ) -> Option<MultiBufferDiffHunk> {
13927 snapshot
13928 .buffer_snapshot
13929 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13930 .find(|hunk| hunk.row_range.start.0 > position.row)
13931 .or_else(|| {
13932 snapshot
13933 .buffer_snapshot
13934 .diff_hunks_in_range(Point::zero()..position)
13935 .find(|hunk| hunk.row_range.end.0 < position.row)
13936 })
13937 }
13938
13939 fn go_to_prev_hunk(
13940 &mut self,
13941 _: &GoToPreviousHunk,
13942 window: &mut Window,
13943 cx: &mut Context<Self>,
13944 ) {
13945 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13946 let snapshot = self.snapshot(window, cx);
13947 let selection = self.selections.newest::<Point>(cx);
13948 self.go_to_hunk_before_or_after_position(
13949 &snapshot,
13950 selection.head(),
13951 Direction::Prev,
13952 window,
13953 cx,
13954 );
13955 }
13956
13957 fn hunk_before_position(
13958 &mut self,
13959 snapshot: &EditorSnapshot,
13960 position: Point,
13961 ) -> Option<MultiBufferRow> {
13962 snapshot
13963 .buffer_snapshot
13964 .diff_hunk_before(position)
13965 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13966 }
13967
13968 fn go_to_next_change(
13969 &mut self,
13970 _: &GoToNextChange,
13971 window: &mut Window,
13972 cx: &mut Context<Self>,
13973 ) {
13974 if let Some(selections) = self
13975 .change_list
13976 .next_change(1, Direction::Next)
13977 .map(|s| s.to_vec())
13978 {
13979 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13980 let map = s.display_map();
13981 s.select_display_ranges(selections.iter().map(|a| {
13982 let point = a.to_display_point(&map);
13983 point..point
13984 }))
13985 })
13986 }
13987 }
13988
13989 fn go_to_previous_change(
13990 &mut self,
13991 _: &GoToPreviousChange,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 if let Some(selections) = self
13996 .change_list
13997 .next_change(1, Direction::Prev)
13998 .map(|s| s.to_vec())
13999 {
14000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14001 let map = s.display_map();
14002 s.select_display_ranges(selections.iter().map(|a| {
14003 let point = a.to_display_point(&map);
14004 point..point
14005 }))
14006 })
14007 }
14008 }
14009
14010 fn go_to_line<T: 'static>(
14011 &mut self,
14012 position: Anchor,
14013 highlight_color: Option<Hsla>,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 let snapshot = self.snapshot(window, cx).display_snapshot;
14018 let position = position.to_point(&snapshot.buffer_snapshot);
14019 let start = snapshot
14020 .buffer_snapshot
14021 .clip_point(Point::new(position.row, 0), Bias::Left);
14022 let end = start + Point::new(1, 0);
14023 let start = snapshot.buffer_snapshot.anchor_before(start);
14024 let end = snapshot.buffer_snapshot.anchor_before(end);
14025
14026 self.highlight_rows::<T>(
14027 start..end,
14028 highlight_color
14029 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14030 Default::default(),
14031 cx,
14032 );
14033
14034 if self.buffer.read(cx).is_singleton() {
14035 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14036 }
14037 }
14038
14039 pub fn go_to_definition(
14040 &mut self,
14041 _: &GoToDefinition,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) -> Task<Result<Navigated>> {
14045 let definition =
14046 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14047 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14048 cx.spawn_in(window, async move |editor, cx| {
14049 if definition.await? == Navigated::Yes {
14050 return Ok(Navigated::Yes);
14051 }
14052 match fallback_strategy {
14053 GoToDefinitionFallback::None => Ok(Navigated::No),
14054 GoToDefinitionFallback::FindAllReferences => {
14055 match editor.update_in(cx, |editor, window, cx| {
14056 editor.find_all_references(&FindAllReferences, window, cx)
14057 })? {
14058 Some(references) => references.await,
14059 None => Ok(Navigated::No),
14060 }
14061 }
14062 }
14063 })
14064 }
14065
14066 pub fn go_to_declaration(
14067 &mut self,
14068 _: &GoToDeclaration,
14069 window: &mut Window,
14070 cx: &mut Context<Self>,
14071 ) -> Task<Result<Navigated>> {
14072 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14073 }
14074
14075 pub fn go_to_declaration_split(
14076 &mut self,
14077 _: &GoToDeclaration,
14078 window: &mut Window,
14079 cx: &mut Context<Self>,
14080 ) -> Task<Result<Navigated>> {
14081 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14082 }
14083
14084 pub fn go_to_implementation(
14085 &mut self,
14086 _: &GoToImplementation,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) -> Task<Result<Navigated>> {
14090 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14091 }
14092
14093 pub fn go_to_implementation_split(
14094 &mut self,
14095 _: &GoToImplementationSplit,
14096 window: &mut Window,
14097 cx: &mut Context<Self>,
14098 ) -> Task<Result<Navigated>> {
14099 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14100 }
14101
14102 pub fn go_to_type_definition(
14103 &mut self,
14104 _: &GoToTypeDefinition,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) -> Task<Result<Navigated>> {
14108 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14109 }
14110
14111 pub fn go_to_definition_split(
14112 &mut self,
14113 _: &GoToDefinitionSplit,
14114 window: &mut Window,
14115 cx: &mut Context<Self>,
14116 ) -> Task<Result<Navigated>> {
14117 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14118 }
14119
14120 pub fn go_to_type_definition_split(
14121 &mut self,
14122 _: &GoToTypeDefinitionSplit,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) -> Task<Result<Navigated>> {
14126 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14127 }
14128
14129 fn go_to_definition_of_kind(
14130 &mut self,
14131 kind: GotoDefinitionKind,
14132 split: bool,
14133 window: &mut Window,
14134 cx: &mut Context<Self>,
14135 ) -> Task<Result<Navigated>> {
14136 let Some(provider) = self.semantics_provider.clone() else {
14137 return Task::ready(Ok(Navigated::No));
14138 };
14139 let head = self.selections.newest::<usize>(cx).head();
14140 let buffer = self.buffer.read(cx);
14141 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14142 text_anchor
14143 } else {
14144 return Task::ready(Ok(Navigated::No));
14145 };
14146
14147 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14148 return Task::ready(Ok(Navigated::No));
14149 };
14150
14151 cx.spawn_in(window, async move |editor, cx| {
14152 let definitions = definitions.await?;
14153 let navigated = editor
14154 .update_in(cx, |editor, window, cx| {
14155 editor.navigate_to_hover_links(
14156 Some(kind),
14157 definitions
14158 .into_iter()
14159 .filter(|location| {
14160 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14161 })
14162 .map(HoverLink::Text)
14163 .collect::<Vec<_>>(),
14164 split,
14165 window,
14166 cx,
14167 )
14168 })?
14169 .await?;
14170 anyhow::Ok(navigated)
14171 })
14172 }
14173
14174 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14175 let selection = self.selections.newest_anchor();
14176 let head = selection.head();
14177 let tail = selection.tail();
14178
14179 let Some((buffer, start_position)) =
14180 self.buffer.read(cx).text_anchor_for_position(head, cx)
14181 else {
14182 return;
14183 };
14184
14185 let end_position = if head != tail {
14186 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14187 return;
14188 };
14189 Some(pos)
14190 } else {
14191 None
14192 };
14193
14194 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14195 let url = if let Some(end_pos) = end_position {
14196 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14197 } else {
14198 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14199 };
14200
14201 if let Some(url) = url {
14202 editor.update(cx, |_, cx| {
14203 cx.open_url(&url);
14204 })
14205 } else {
14206 Ok(())
14207 }
14208 });
14209
14210 url_finder.detach();
14211 }
14212
14213 pub fn open_selected_filename(
14214 &mut self,
14215 _: &OpenSelectedFilename,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) {
14219 let Some(workspace) = self.workspace() else {
14220 return;
14221 };
14222
14223 let position = self.selections.newest_anchor().head();
14224
14225 let Some((buffer, buffer_position)) =
14226 self.buffer.read(cx).text_anchor_for_position(position, cx)
14227 else {
14228 return;
14229 };
14230
14231 let project = self.project.clone();
14232
14233 cx.spawn_in(window, async move |_, cx| {
14234 let result = find_file(&buffer, project, buffer_position, cx).await;
14235
14236 if let Some((_, path)) = result {
14237 workspace
14238 .update_in(cx, |workspace, window, cx| {
14239 workspace.open_resolved_path(path, window, cx)
14240 })?
14241 .await?;
14242 }
14243 anyhow::Ok(())
14244 })
14245 .detach();
14246 }
14247
14248 pub(crate) fn navigate_to_hover_links(
14249 &mut self,
14250 kind: Option<GotoDefinitionKind>,
14251 mut definitions: Vec<HoverLink>,
14252 split: bool,
14253 window: &mut Window,
14254 cx: &mut Context<Editor>,
14255 ) -> Task<Result<Navigated>> {
14256 // If there is one definition, just open it directly
14257 if definitions.len() == 1 {
14258 let definition = definitions.pop().unwrap();
14259
14260 enum TargetTaskResult {
14261 Location(Option<Location>),
14262 AlreadyNavigated,
14263 }
14264
14265 let target_task = match definition {
14266 HoverLink::Text(link) => {
14267 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14268 }
14269 HoverLink::InlayHint(lsp_location, server_id) => {
14270 let computation =
14271 self.compute_target_location(lsp_location, server_id, window, cx);
14272 cx.background_spawn(async move {
14273 let location = computation.await?;
14274 Ok(TargetTaskResult::Location(location))
14275 })
14276 }
14277 HoverLink::Url(url) => {
14278 cx.open_url(&url);
14279 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14280 }
14281 HoverLink::File(path) => {
14282 if let Some(workspace) = self.workspace() {
14283 cx.spawn_in(window, async move |_, cx| {
14284 workspace
14285 .update_in(cx, |workspace, window, cx| {
14286 workspace.open_resolved_path(path, window, cx)
14287 })?
14288 .await
14289 .map(|_| TargetTaskResult::AlreadyNavigated)
14290 })
14291 } else {
14292 Task::ready(Ok(TargetTaskResult::Location(None)))
14293 }
14294 }
14295 };
14296 cx.spawn_in(window, async move |editor, cx| {
14297 let target = match target_task.await.context("target resolution task")? {
14298 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14299 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14300 TargetTaskResult::Location(Some(target)) => target,
14301 };
14302
14303 editor.update_in(cx, |editor, window, cx| {
14304 let Some(workspace) = editor.workspace() else {
14305 return Navigated::No;
14306 };
14307 let pane = workspace.read(cx).active_pane().clone();
14308
14309 let range = target.range.to_point(target.buffer.read(cx));
14310 let range = editor.range_for_match(&range);
14311 let range = collapse_multiline_range(range);
14312
14313 if !split
14314 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14315 {
14316 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14317 } else {
14318 window.defer(cx, move |window, cx| {
14319 let target_editor: Entity<Self> =
14320 workspace.update(cx, |workspace, cx| {
14321 let pane = if split {
14322 workspace.adjacent_pane(window, cx)
14323 } else {
14324 workspace.active_pane().clone()
14325 };
14326
14327 workspace.open_project_item(
14328 pane,
14329 target.buffer.clone(),
14330 true,
14331 true,
14332 window,
14333 cx,
14334 )
14335 });
14336 target_editor.update(cx, |target_editor, cx| {
14337 // When selecting a definition in a different buffer, disable the nav history
14338 // to avoid creating a history entry at the previous cursor location.
14339 pane.update(cx, |pane, _| pane.disable_history());
14340 target_editor.go_to_singleton_buffer_range(range, window, cx);
14341 pane.update(cx, |pane, _| pane.enable_history());
14342 });
14343 });
14344 }
14345 Navigated::Yes
14346 })
14347 })
14348 } else if !definitions.is_empty() {
14349 cx.spawn_in(window, async move |editor, cx| {
14350 let (title, location_tasks, workspace) = editor
14351 .update_in(cx, |editor, window, cx| {
14352 let tab_kind = match kind {
14353 Some(GotoDefinitionKind::Implementation) => "Implementations",
14354 _ => "Definitions",
14355 };
14356 let title = definitions
14357 .iter()
14358 .find_map(|definition| match definition {
14359 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14360 let buffer = origin.buffer.read(cx);
14361 format!(
14362 "{} for {}",
14363 tab_kind,
14364 buffer
14365 .text_for_range(origin.range.clone())
14366 .collect::<String>()
14367 )
14368 }),
14369 HoverLink::InlayHint(_, _) => None,
14370 HoverLink::Url(_) => None,
14371 HoverLink::File(_) => None,
14372 })
14373 .unwrap_or(tab_kind.to_string());
14374 let location_tasks = definitions
14375 .into_iter()
14376 .map(|definition| match definition {
14377 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14378 HoverLink::InlayHint(lsp_location, server_id) => editor
14379 .compute_target_location(lsp_location, server_id, window, cx),
14380 HoverLink::Url(_) => Task::ready(Ok(None)),
14381 HoverLink::File(_) => Task::ready(Ok(None)),
14382 })
14383 .collect::<Vec<_>>();
14384 (title, location_tasks, editor.workspace().clone())
14385 })
14386 .context("location tasks preparation")?;
14387
14388 let locations = future::join_all(location_tasks)
14389 .await
14390 .into_iter()
14391 .filter_map(|location| location.transpose())
14392 .collect::<Result<_>>()
14393 .context("location tasks")?;
14394
14395 let Some(workspace) = workspace else {
14396 return Ok(Navigated::No);
14397 };
14398 let opened = workspace
14399 .update_in(cx, |workspace, window, cx| {
14400 Self::open_locations_in_multibuffer(
14401 workspace,
14402 locations,
14403 title,
14404 split,
14405 MultibufferSelectionMode::First,
14406 window,
14407 cx,
14408 )
14409 })
14410 .ok();
14411
14412 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14413 })
14414 } else {
14415 Task::ready(Ok(Navigated::No))
14416 }
14417 }
14418
14419 fn compute_target_location(
14420 &self,
14421 lsp_location: lsp::Location,
14422 server_id: LanguageServerId,
14423 window: &mut Window,
14424 cx: &mut Context<Self>,
14425 ) -> Task<anyhow::Result<Option<Location>>> {
14426 let Some(project) = self.project.clone() else {
14427 return Task::ready(Ok(None));
14428 };
14429
14430 cx.spawn_in(window, async move |editor, cx| {
14431 let location_task = editor.update(cx, |_, cx| {
14432 project.update(cx, |project, cx| {
14433 let language_server_name = project
14434 .language_server_statuses(cx)
14435 .find(|(id, _)| server_id == *id)
14436 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14437 language_server_name.map(|language_server_name| {
14438 project.open_local_buffer_via_lsp(
14439 lsp_location.uri.clone(),
14440 server_id,
14441 language_server_name,
14442 cx,
14443 )
14444 })
14445 })
14446 })?;
14447 let location = match location_task {
14448 Some(task) => Some({
14449 let target_buffer_handle = task.await.context("open local buffer")?;
14450 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14451 let target_start = target_buffer
14452 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14453 let target_end = target_buffer
14454 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14455 target_buffer.anchor_after(target_start)
14456 ..target_buffer.anchor_before(target_end)
14457 })?;
14458 Location {
14459 buffer: target_buffer_handle,
14460 range,
14461 }
14462 }),
14463 None => None,
14464 };
14465 Ok(location)
14466 })
14467 }
14468
14469 pub fn find_all_references(
14470 &mut self,
14471 _: &FindAllReferences,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) -> Option<Task<Result<Navigated>>> {
14475 let selection = self.selections.newest::<usize>(cx);
14476 let multi_buffer = self.buffer.read(cx);
14477 let head = selection.head();
14478
14479 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14480 let head_anchor = multi_buffer_snapshot.anchor_at(
14481 head,
14482 if head < selection.tail() {
14483 Bias::Right
14484 } else {
14485 Bias::Left
14486 },
14487 );
14488
14489 match self
14490 .find_all_references_task_sources
14491 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14492 {
14493 Ok(_) => {
14494 log::info!(
14495 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14496 );
14497 return None;
14498 }
14499 Err(i) => {
14500 self.find_all_references_task_sources.insert(i, head_anchor);
14501 }
14502 }
14503
14504 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14505 let workspace = self.workspace()?;
14506 let project = workspace.read(cx).project().clone();
14507 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14508 Some(cx.spawn_in(window, async move |editor, cx| {
14509 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14510 if let Ok(i) = editor
14511 .find_all_references_task_sources
14512 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14513 {
14514 editor.find_all_references_task_sources.remove(i);
14515 }
14516 });
14517
14518 let locations = references.await?;
14519 if locations.is_empty() {
14520 return anyhow::Ok(Navigated::No);
14521 }
14522
14523 workspace.update_in(cx, |workspace, window, cx| {
14524 let title = locations
14525 .first()
14526 .as_ref()
14527 .map(|location| {
14528 let buffer = location.buffer.read(cx);
14529 format!(
14530 "References to `{}`",
14531 buffer
14532 .text_for_range(location.range.clone())
14533 .collect::<String>()
14534 )
14535 })
14536 .unwrap();
14537 Self::open_locations_in_multibuffer(
14538 workspace,
14539 locations,
14540 title,
14541 false,
14542 MultibufferSelectionMode::First,
14543 window,
14544 cx,
14545 );
14546 Navigated::Yes
14547 })
14548 }))
14549 }
14550
14551 /// Opens a multibuffer with the given project locations in it
14552 pub fn open_locations_in_multibuffer(
14553 workspace: &mut Workspace,
14554 mut locations: Vec<Location>,
14555 title: String,
14556 split: bool,
14557 multibuffer_selection_mode: MultibufferSelectionMode,
14558 window: &mut Window,
14559 cx: &mut Context<Workspace>,
14560 ) {
14561 // If there are multiple definitions, open them in a multibuffer
14562 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14563 let mut locations = locations.into_iter().peekable();
14564 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14565 let capability = workspace.project().read(cx).capability();
14566
14567 let excerpt_buffer = cx.new(|cx| {
14568 let mut multibuffer = MultiBuffer::new(capability);
14569 while let Some(location) = locations.next() {
14570 let buffer = location.buffer.read(cx);
14571 let mut ranges_for_buffer = Vec::new();
14572 let range = location.range.to_point(buffer);
14573 ranges_for_buffer.push(range.clone());
14574
14575 while let Some(next_location) = locations.peek() {
14576 if next_location.buffer == location.buffer {
14577 ranges_for_buffer.push(next_location.range.to_point(buffer));
14578 locations.next();
14579 } else {
14580 break;
14581 }
14582 }
14583
14584 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14585 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14586 PathKey::for_buffer(&location.buffer, cx),
14587 location.buffer.clone(),
14588 ranges_for_buffer,
14589 DEFAULT_MULTIBUFFER_CONTEXT,
14590 cx,
14591 );
14592 ranges.extend(new_ranges)
14593 }
14594
14595 multibuffer.with_title(title)
14596 });
14597
14598 let editor = cx.new(|cx| {
14599 Editor::for_multibuffer(
14600 excerpt_buffer,
14601 Some(workspace.project().clone()),
14602 window,
14603 cx,
14604 )
14605 });
14606 editor.update(cx, |editor, cx| {
14607 match multibuffer_selection_mode {
14608 MultibufferSelectionMode::First => {
14609 if let Some(first_range) = ranges.first() {
14610 editor.change_selections(None, window, cx, |selections| {
14611 selections.clear_disjoint();
14612 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14613 });
14614 }
14615 editor.highlight_background::<Self>(
14616 &ranges,
14617 |theme| theme.editor_highlighted_line_background,
14618 cx,
14619 );
14620 }
14621 MultibufferSelectionMode::All => {
14622 editor.change_selections(None, window, cx, |selections| {
14623 selections.clear_disjoint();
14624 selections.select_anchor_ranges(ranges);
14625 });
14626 }
14627 }
14628 editor.register_buffers_with_language_servers(cx);
14629 });
14630
14631 let item = Box::new(editor);
14632 let item_id = item.item_id();
14633
14634 if split {
14635 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14636 } else {
14637 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14638 let (preview_item_id, preview_item_idx) =
14639 workspace.active_pane().update(cx, |pane, _| {
14640 (pane.preview_item_id(), pane.preview_item_idx())
14641 });
14642
14643 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14644
14645 if let Some(preview_item_id) = preview_item_id {
14646 workspace.active_pane().update(cx, |pane, cx| {
14647 pane.remove_item(preview_item_id, false, false, window, cx);
14648 });
14649 }
14650 } else {
14651 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14652 }
14653 }
14654 workspace.active_pane().update(cx, |pane, cx| {
14655 pane.set_preview_item_id(Some(item_id), cx);
14656 });
14657 }
14658
14659 pub fn rename(
14660 &mut self,
14661 _: &Rename,
14662 window: &mut Window,
14663 cx: &mut Context<Self>,
14664 ) -> Option<Task<Result<()>>> {
14665 use language::ToOffset as _;
14666
14667 let provider = self.semantics_provider.clone()?;
14668 let selection = self.selections.newest_anchor().clone();
14669 let (cursor_buffer, cursor_buffer_position) = self
14670 .buffer
14671 .read(cx)
14672 .text_anchor_for_position(selection.head(), cx)?;
14673 let (tail_buffer, cursor_buffer_position_end) = self
14674 .buffer
14675 .read(cx)
14676 .text_anchor_for_position(selection.tail(), cx)?;
14677 if tail_buffer != cursor_buffer {
14678 return None;
14679 }
14680
14681 let snapshot = cursor_buffer.read(cx).snapshot();
14682 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14683 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14684 let prepare_rename = provider
14685 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14686 .unwrap_or_else(|| Task::ready(Ok(None)));
14687 drop(snapshot);
14688
14689 Some(cx.spawn_in(window, async move |this, cx| {
14690 let rename_range = if let Some(range) = prepare_rename.await? {
14691 Some(range)
14692 } else {
14693 this.update(cx, |this, cx| {
14694 let buffer = this.buffer.read(cx).snapshot(cx);
14695 let mut buffer_highlights = this
14696 .document_highlights_for_position(selection.head(), &buffer)
14697 .filter(|highlight| {
14698 highlight.start.excerpt_id == selection.head().excerpt_id
14699 && highlight.end.excerpt_id == selection.head().excerpt_id
14700 });
14701 buffer_highlights
14702 .next()
14703 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14704 })?
14705 };
14706 if let Some(rename_range) = rename_range {
14707 this.update_in(cx, |this, window, cx| {
14708 let snapshot = cursor_buffer.read(cx).snapshot();
14709 let rename_buffer_range = rename_range.to_offset(&snapshot);
14710 let cursor_offset_in_rename_range =
14711 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14712 let cursor_offset_in_rename_range_end =
14713 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14714
14715 this.take_rename(false, window, cx);
14716 let buffer = this.buffer.read(cx).read(cx);
14717 let cursor_offset = selection.head().to_offset(&buffer);
14718 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14719 let rename_end = rename_start + rename_buffer_range.len();
14720 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14721 let mut old_highlight_id = None;
14722 let old_name: Arc<str> = buffer
14723 .chunks(rename_start..rename_end, true)
14724 .map(|chunk| {
14725 if old_highlight_id.is_none() {
14726 old_highlight_id = chunk.syntax_highlight_id;
14727 }
14728 chunk.text
14729 })
14730 .collect::<String>()
14731 .into();
14732
14733 drop(buffer);
14734
14735 // Position the selection in the rename editor so that it matches the current selection.
14736 this.show_local_selections = false;
14737 let rename_editor = cx.new(|cx| {
14738 let mut editor = Editor::single_line(window, cx);
14739 editor.buffer.update(cx, |buffer, cx| {
14740 buffer.edit([(0..0, old_name.clone())], None, cx)
14741 });
14742 let rename_selection_range = match cursor_offset_in_rename_range
14743 .cmp(&cursor_offset_in_rename_range_end)
14744 {
14745 Ordering::Equal => {
14746 editor.select_all(&SelectAll, window, cx);
14747 return editor;
14748 }
14749 Ordering::Less => {
14750 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14751 }
14752 Ordering::Greater => {
14753 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14754 }
14755 };
14756 if rename_selection_range.end > old_name.len() {
14757 editor.select_all(&SelectAll, window, cx);
14758 } else {
14759 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14760 s.select_ranges([rename_selection_range]);
14761 });
14762 }
14763 editor
14764 });
14765 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14766 if e == &EditorEvent::Focused {
14767 cx.emit(EditorEvent::FocusedIn)
14768 }
14769 })
14770 .detach();
14771
14772 let write_highlights =
14773 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14774 let read_highlights =
14775 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14776 let ranges = write_highlights
14777 .iter()
14778 .flat_map(|(_, ranges)| ranges.iter())
14779 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14780 .cloned()
14781 .collect();
14782
14783 this.highlight_text::<Rename>(
14784 ranges,
14785 HighlightStyle {
14786 fade_out: Some(0.6),
14787 ..Default::default()
14788 },
14789 cx,
14790 );
14791 let rename_focus_handle = rename_editor.focus_handle(cx);
14792 window.focus(&rename_focus_handle);
14793 let block_id = this.insert_blocks(
14794 [BlockProperties {
14795 style: BlockStyle::Flex,
14796 placement: BlockPlacement::Below(range.start),
14797 height: Some(1),
14798 render: Arc::new({
14799 let rename_editor = rename_editor.clone();
14800 move |cx: &mut BlockContext| {
14801 let mut text_style = cx.editor_style.text.clone();
14802 if let Some(highlight_style) = old_highlight_id
14803 .and_then(|h| h.style(&cx.editor_style.syntax))
14804 {
14805 text_style = text_style.highlight(highlight_style);
14806 }
14807 div()
14808 .block_mouse_down()
14809 .pl(cx.anchor_x)
14810 .child(EditorElement::new(
14811 &rename_editor,
14812 EditorStyle {
14813 background: cx.theme().system().transparent,
14814 local_player: cx.editor_style.local_player,
14815 text: text_style,
14816 scrollbar_width: cx.editor_style.scrollbar_width,
14817 syntax: cx.editor_style.syntax.clone(),
14818 status: cx.editor_style.status.clone(),
14819 inlay_hints_style: HighlightStyle {
14820 font_weight: Some(FontWeight::BOLD),
14821 ..make_inlay_hints_style(cx.app)
14822 },
14823 inline_completion_styles: make_suggestion_styles(
14824 cx.app,
14825 ),
14826 ..EditorStyle::default()
14827 },
14828 ))
14829 .into_any_element()
14830 }
14831 }),
14832 priority: 0,
14833 render_in_minimap: true,
14834 }],
14835 Some(Autoscroll::fit()),
14836 cx,
14837 )[0];
14838 this.pending_rename = Some(RenameState {
14839 range,
14840 old_name,
14841 editor: rename_editor,
14842 block_id,
14843 });
14844 })?;
14845 }
14846
14847 Ok(())
14848 }))
14849 }
14850
14851 pub fn confirm_rename(
14852 &mut self,
14853 _: &ConfirmRename,
14854 window: &mut Window,
14855 cx: &mut Context<Self>,
14856 ) -> Option<Task<Result<()>>> {
14857 let rename = self.take_rename(false, window, cx)?;
14858 let workspace = self.workspace()?.downgrade();
14859 let (buffer, start) = self
14860 .buffer
14861 .read(cx)
14862 .text_anchor_for_position(rename.range.start, cx)?;
14863 let (end_buffer, _) = self
14864 .buffer
14865 .read(cx)
14866 .text_anchor_for_position(rename.range.end, cx)?;
14867 if buffer != end_buffer {
14868 return None;
14869 }
14870
14871 let old_name = rename.old_name;
14872 let new_name = rename.editor.read(cx).text(cx);
14873
14874 let rename = self.semantics_provider.as_ref()?.perform_rename(
14875 &buffer,
14876 start,
14877 new_name.clone(),
14878 cx,
14879 )?;
14880
14881 Some(cx.spawn_in(window, async move |editor, cx| {
14882 let project_transaction = rename.await?;
14883 Self::open_project_transaction(
14884 &editor,
14885 workspace,
14886 project_transaction,
14887 format!("Rename: {} → {}", old_name, new_name),
14888 cx,
14889 )
14890 .await?;
14891
14892 editor.update(cx, |editor, cx| {
14893 editor.refresh_document_highlights(cx);
14894 })?;
14895 Ok(())
14896 }))
14897 }
14898
14899 fn take_rename(
14900 &mut self,
14901 moving_cursor: bool,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) -> Option<RenameState> {
14905 let rename = self.pending_rename.take()?;
14906 if rename.editor.focus_handle(cx).is_focused(window) {
14907 window.focus(&self.focus_handle);
14908 }
14909
14910 self.remove_blocks(
14911 [rename.block_id].into_iter().collect(),
14912 Some(Autoscroll::fit()),
14913 cx,
14914 );
14915 self.clear_highlights::<Rename>(cx);
14916 self.show_local_selections = true;
14917
14918 if moving_cursor {
14919 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14920 editor.selections.newest::<usize>(cx).head()
14921 });
14922
14923 // Update the selection to match the position of the selection inside
14924 // the rename editor.
14925 let snapshot = self.buffer.read(cx).read(cx);
14926 let rename_range = rename.range.to_offset(&snapshot);
14927 let cursor_in_editor = snapshot
14928 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14929 .min(rename_range.end);
14930 drop(snapshot);
14931
14932 self.change_selections(None, window, cx, |s| {
14933 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14934 });
14935 } else {
14936 self.refresh_document_highlights(cx);
14937 }
14938
14939 Some(rename)
14940 }
14941
14942 pub fn pending_rename(&self) -> Option<&RenameState> {
14943 self.pending_rename.as_ref()
14944 }
14945
14946 fn format(
14947 &mut self,
14948 _: &Format,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) -> Option<Task<Result<()>>> {
14952 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14953
14954 let project = match &self.project {
14955 Some(project) => project.clone(),
14956 None => return None,
14957 };
14958
14959 Some(self.perform_format(
14960 project,
14961 FormatTrigger::Manual,
14962 FormatTarget::Buffers,
14963 window,
14964 cx,
14965 ))
14966 }
14967
14968 fn format_selections(
14969 &mut self,
14970 _: &FormatSelections,
14971 window: &mut Window,
14972 cx: &mut Context<Self>,
14973 ) -> Option<Task<Result<()>>> {
14974 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14975
14976 let project = match &self.project {
14977 Some(project) => project.clone(),
14978 None => return None,
14979 };
14980
14981 let ranges = self
14982 .selections
14983 .all_adjusted(cx)
14984 .into_iter()
14985 .map(|selection| selection.range())
14986 .collect_vec();
14987
14988 Some(self.perform_format(
14989 project,
14990 FormatTrigger::Manual,
14991 FormatTarget::Ranges(ranges),
14992 window,
14993 cx,
14994 ))
14995 }
14996
14997 fn perform_format(
14998 &mut self,
14999 project: Entity<Project>,
15000 trigger: FormatTrigger,
15001 target: FormatTarget,
15002 window: &mut Window,
15003 cx: &mut Context<Self>,
15004 ) -> Task<Result<()>> {
15005 let buffer = self.buffer.clone();
15006 let (buffers, target) = match target {
15007 FormatTarget::Buffers => {
15008 let mut buffers = buffer.read(cx).all_buffers();
15009 if trigger == FormatTrigger::Save {
15010 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15011 }
15012 (buffers, LspFormatTarget::Buffers)
15013 }
15014 FormatTarget::Ranges(selection_ranges) => {
15015 let multi_buffer = buffer.read(cx);
15016 let snapshot = multi_buffer.read(cx);
15017 let mut buffers = HashSet::default();
15018 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15019 BTreeMap::new();
15020 for selection_range in selection_ranges {
15021 for (buffer, buffer_range, _) in
15022 snapshot.range_to_buffer_ranges(selection_range)
15023 {
15024 let buffer_id = buffer.remote_id();
15025 let start = buffer.anchor_before(buffer_range.start);
15026 let end = buffer.anchor_after(buffer_range.end);
15027 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15028 buffer_id_to_ranges
15029 .entry(buffer_id)
15030 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15031 .or_insert_with(|| vec![start..end]);
15032 }
15033 }
15034 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15035 }
15036 };
15037
15038 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15039 let selections_prev = transaction_id_prev
15040 .and_then(|transaction_id_prev| {
15041 // default to selections as they were after the last edit, if we have them,
15042 // instead of how they are now.
15043 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15044 // will take you back to where you made the last edit, instead of staying where you scrolled
15045 self.selection_history
15046 .transaction(transaction_id_prev)
15047 .map(|t| t.0.clone())
15048 })
15049 .unwrap_or_else(|| {
15050 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15051 self.selections.disjoint_anchors()
15052 });
15053
15054 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15055 let format = project.update(cx, |project, cx| {
15056 project.format(buffers, target, true, trigger, cx)
15057 });
15058
15059 cx.spawn_in(window, async move |editor, cx| {
15060 let transaction = futures::select_biased! {
15061 transaction = format.log_err().fuse() => transaction,
15062 () = timeout => {
15063 log::warn!("timed out waiting for formatting");
15064 None
15065 }
15066 };
15067
15068 buffer
15069 .update(cx, |buffer, cx| {
15070 if let Some(transaction) = transaction {
15071 if !buffer.is_singleton() {
15072 buffer.push_transaction(&transaction.0, cx);
15073 }
15074 }
15075 cx.notify();
15076 })
15077 .ok();
15078
15079 if let Some(transaction_id_now) =
15080 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15081 {
15082 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15083 if has_new_transaction {
15084 _ = editor.update(cx, |editor, _| {
15085 editor
15086 .selection_history
15087 .insert_transaction(transaction_id_now, selections_prev);
15088 });
15089 }
15090 }
15091
15092 Ok(())
15093 })
15094 }
15095
15096 fn organize_imports(
15097 &mut self,
15098 _: &OrganizeImports,
15099 window: &mut Window,
15100 cx: &mut Context<Self>,
15101 ) -> Option<Task<Result<()>>> {
15102 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15103 let project = match &self.project {
15104 Some(project) => project.clone(),
15105 None => return None,
15106 };
15107 Some(self.perform_code_action_kind(
15108 project,
15109 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15110 window,
15111 cx,
15112 ))
15113 }
15114
15115 fn perform_code_action_kind(
15116 &mut self,
15117 project: Entity<Project>,
15118 kind: CodeActionKind,
15119 window: &mut Window,
15120 cx: &mut Context<Self>,
15121 ) -> Task<Result<()>> {
15122 let buffer = self.buffer.clone();
15123 let buffers = buffer.read(cx).all_buffers();
15124 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15125 let apply_action = project.update(cx, |project, cx| {
15126 project.apply_code_action_kind(buffers, kind, true, cx)
15127 });
15128 cx.spawn_in(window, async move |_, cx| {
15129 let transaction = futures::select_biased! {
15130 () = timeout => {
15131 log::warn!("timed out waiting for executing code action");
15132 None
15133 }
15134 transaction = apply_action.log_err().fuse() => transaction,
15135 };
15136 buffer
15137 .update(cx, |buffer, cx| {
15138 // check if we need this
15139 if let Some(transaction) = transaction {
15140 if !buffer.is_singleton() {
15141 buffer.push_transaction(&transaction.0, cx);
15142 }
15143 }
15144 cx.notify();
15145 })
15146 .ok();
15147 Ok(())
15148 })
15149 }
15150
15151 fn restart_language_server(
15152 &mut self,
15153 _: &RestartLanguageServer,
15154 _: &mut Window,
15155 cx: &mut Context<Self>,
15156 ) {
15157 if let Some(project) = self.project.clone() {
15158 self.buffer.update(cx, |multi_buffer, cx| {
15159 project.update(cx, |project, cx| {
15160 project.restart_language_servers_for_buffers(
15161 multi_buffer.all_buffers().into_iter().collect(),
15162 cx,
15163 );
15164 });
15165 })
15166 }
15167 }
15168
15169 fn stop_language_server(
15170 &mut self,
15171 _: &StopLanguageServer,
15172 _: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 if let Some(project) = self.project.clone() {
15176 self.buffer.update(cx, |multi_buffer, cx| {
15177 project.update(cx, |project, cx| {
15178 project.stop_language_servers_for_buffers(
15179 multi_buffer.all_buffers().into_iter().collect(),
15180 cx,
15181 );
15182 cx.emit(project::Event::RefreshInlayHints);
15183 });
15184 });
15185 }
15186 }
15187
15188 fn cancel_language_server_work(
15189 workspace: &mut Workspace,
15190 _: &actions::CancelLanguageServerWork,
15191 _: &mut Window,
15192 cx: &mut Context<Workspace>,
15193 ) {
15194 let project = workspace.project();
15195 let buffers = workspace
15196 .active_item(cx)
15197 .and_then(|item| item.act_as::<Editor>(cx))
15198 .map_or(HashSet::default(), |editor| {
15199 editor.read(cx).buffer.read(cx).all_buffers()
15200 });
15201 project.update(cx, |project, cx| {
15202 project.cancel_language_server_work_for_buffers(buffers, cx);
15203 });
15204 }
15205
15206 fn show_character_palette(
15207 &mut self,
15208 _: &ShowCharacterPalette,
15209 window: &mut Window,
15210 _: &mut Context<Self>,
15211 ) {
15212 window.show_character_palette();
15213 }
15214
15215 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15216 if self.mode.is_minimap() {
15217 return;
15218 }
15219
15220 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15221 let buffer = self.buffer.read(cx).snapshot(cx);
15222 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15223 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15224 let is_valid = buffer
15225 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15226 .any(|entry| {
15227 entry.diagnostic.is_primary
15228 && !entry.range.is_empty()
15229 && entry.range.start == primary_range_start
15230 && entry.diagnostic.message == active_diagnostics.active_message
15231 });
15232
15233 if !is_valid {
15234 self.dismiss_diagnostics(cx);
15235 }
15236 }
15237 }
15238
15239 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15240 match &self.active_diagnostics {
15241 ActiveDiagnostic::Group(group) => Some(group),
15242 _ => None,
15243 }
15244 }
15245
15246 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15247 self.dismiss_diagnostics(cx);
15248 self.active_diagnostics = ActiveDiagnostic::All;
15249 }
15250
15251 fn activate_diagnostics(
15252 &mut self,
15253 buffer_id: BufferId,
15254 diagnostic: DiagnosticEntry<usize>,
15255 window: &mut Window,
15256 cx: &mut Context<Self>,
15257 ) {
15258 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15259 return;
15260 }
15261 self.dismiss_diagnostics(cx);
15262 let snapshot = self.snapshot(window, cx);
15263 let buffer = self.buffer.read(cx).snapshot(cx);
15264 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15265 return;
15266 };
15267
15268 let diagnostic_group = buffer
15269 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15270 .collect::<Vec<_>>();
15271
15272 let blocks =
15273 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15274
15275 let blocks = self.display_map.update(cx, |display_map, cx| {
15276 display_map.insert_blocks(blocks, cx).into_iter().collect()
15277 });
15278 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15279 active_range: buffer.anchor_before(diagnostic.range.start)
15280 ..buffer.anchor_after(diagnostic.range.end),
15281 active_message: diagnostic.diagnostic.message.clone(),
15282 group_id: diagnostic.diagnostic.group_id,
15283 blocks,
15284 });
15285 cx.notify();
15286 }
15287
15288 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15289 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15290 return;
15291 };
15292
15293 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15294 if let ActiveDiagnostic::Group(group) = prev {
15295 self.display_map.update(cx, |display_map, cx| {
15296 display_map.remove_blocks(group.blocks, cx);
15297 });
15298 cx.notify();
15299 }
15300 }
15301
15302 /// Disable inline diagnostics rendering for this editor.
15303 pub fn disable_inline_diagnostics(&mut self) {
15304 self.inline_diagnostics_enabled = false;
15305 self.inline_diagnostics_update = Task::ready(());
15306 self.inline_diagnostics.clear();
15307 }
15308
15309 pub fn diagnostics_enabled(&self) -> bool {
15310 self.mode.is_full()
15311 }
15312
15313 pub fn inline_diagnostics_enabled(&self) -> bool {
15314 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15315 }
15316
15317 pub fn show_inline_diagnostics(&self) -> bool {
15318 self.show_inline_diagnostics
15319 }
15320
15321 pub fn toggle_inline_diagnostics(
15322 &mut self,
15323 _: &ToggleInlineDiagnostics,
15324 window: &mut Window,
15325 cx: &mut Context<Editor>,
15326 ) {
15327 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15328 self.refresh_inline_diagnostics(false, window, cx);
15329 }
15330
15331 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15332 self.diagnostics_max_severity = severity;
15333 self.display_map.update(cx, |display_map, _| {
15334 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15335 });
15336 }
15337
15338 pub fn toggle_diagnostics(
15339 &mut self,
15340 _: &ToggleDiagnostics,
15341 window: &mut Window,
15342 cx: &mut Context<Editor>,
15343 ) {
15344 if !self.diagnostics_enabled() {
15345 return;
15346 }
15347
15348 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15349 EditorSettings::get_global(cx)
15350 .diagnostics_max_severity
15351 .filter(|severity| severity != &DiagnosticSeverity::Off)
15352 .unwrap_or(DiagnosticSeverity::Hint)
15353 } else {
15354 DiagnosticSeverity::Off
15355 };
15356 self.set_max_diagnostics_severity(new_severity, cx);
15357 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15358 self.active_diagnostics = ActiveDiagnostic::None;
15359 self.inline_diagnostics_update = Task::ready(());
15360 self.inline_diagnostics.clear();
15361 } else {
15362 self.refresh_inline_diagnostics(false, window, cx);
15363 }
15364
15365 cx.notify();
15366 }
15367
15368 pub fn toggle_minimap(
15369 &mut self,
15370 _: &ToggleMinimap,
15371 window: &mut Window,
15372 cx: &mut Context<Editor>,
15373 ) {
15374 if self.supports_minimap(cx) {
15375 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15376 }
15377 }
15378
15379 fn refresh_inline_diagnostics(
15380 &mut self,
15381 debounce: bool,
15382 window: &mut Window,
15383 cx: &mut Context<Self>,
15384 ) {
15385 let max_severity = ProjectSettings::get_global(cx)
15386 .diagnostics
15387 .inline
15388 .max_severity
15389 .unwrap_or(self.diagnostics_max_severity);
15390
15391 if self.mode.is_minimap()
15392 || !self.inline_diagnostics_enabled()
15393 || !self.show_inline_diagnostics
15394 || max_severity == DiagnosticSeverity::Off
15395 {
15396 self.inline_diagnostics_update = Task::ready(());
15397 self.inline_diagnostics.clear();
15398 return;
15399 }
15400
15401 let debounce_ms = ProjectSettings::get_global(cx)
15402 .diagnostics
15403 .inline
15404 .update_debounce_ms;
15405 let debounce = if debounce && debounce_ms > 0 {
15406 Some(Duration::from_millis(debounce_ms))
15407 } else {
15408 None
15409 };
15410 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15411 let editor = editor.upgrade().unwrap();
15412
15413 if let Some(debounce) = debounce {
15414 cx.background_executor().timer(debounce).await;
15415 }
15416 let Some(snapshot) = editor
15417 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15418 .ok()
15419 else {
15420 return;
15421 };
15422
15423 let new_inline_diagnostics = cx
15424 .background_spawn(async move {
15425 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15426 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15427 let message = diagnostic_entry
15428 .diagnostic
15429 .message
15430 .split_once('\n')
15431 .map(|(line, _)| line)
15432 .map(SharedString::new)
15433 .unwrap_or_else(|| {
15434 SharedString::from(diagnostic_entry.diagnostic.message)
15435 });
15436 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15437 let (Ok(i) | Err(i)) = inline_diagnostics
15438 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15439 inline_diagnostics.insert(
15440 i,
15441 (
15442 start_anchor,
15443 InlineDiagnostic {
15444 message,
15445 group_id: diagnostic_entry.diagnostic.group_id,
15446 start: diagnostic_entry.range.start.to_point(&snapshot),
15447 is_primary: diagnostic_entry.diagnostic.is_primary,
15448 severity: diagnostic_entry.diagnostic.severity,
15449 },
15450 ),
15451 );
15452 }
15453 inline_diagnostics
15454 })
15455 .await;
15456
15457 editor
15458 .update(cx, |editor, cx| {
15459 editor.inline_diagnostics = new_inline_diagnostics;
15460 cx.notify();
15461 })
15462 .ok();
15463 });
15464 }
15465
15466 pub fn set_selections_from_remote(
15467 &mut self,
15468 selections: Vec<Selection<Anchor>>,
15469 pending_selection: Option<Selection<Anchor>>,
15470 window: &mut Window,
15471 cx: &mut Context<Self>,
15472 ) {
15473 let old_cursor_position = self.selections.newest_anchor().head();
15474 self.selections.change_with(cx, |s| {
15475 s.select_anchors(selections);
15476 if let Some(pending_selection) = pending_selection {
15477 s.set_pending(pending_selection, SelectMode::Character);
15478 } else {
15479 s.clear_pending();
15480 }
15481 });
15482 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15483 }
15484
15485 fn push_to_selection_history(&mut self) {
15486 self.selection_history.push(SelectionHistoryEntry {
15487 selections: self.selections.disjoint_anchors(),
15488 select_next_state: self.select_next_state.clone(),
15489 select_prev_state: self.select_prev_state.clone(),
15490 add_selections_state: self.add_selections_state.clone(),
15491 });
15492 }
15493
15494 pub fn transact(
15495 &mut self,
15496 window: &mut Window,
15497 cx: &mut Context<Self>,
15498 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15499 ) -> Option<TransactionId> {
15500 self.start_transaction_at(Instant::now(), window, cx);
15501 update(self, window, cx);
15502 self.end_transaction_at(Instant::now(), cx)
15503 }
15504
15505 pub fn start_transaction_at(
15506 &mut self,
15507 now: Instant,
15508 window: &mut Window,
15509 cx: &mut Context<Self>,
15510 ) {
15511 self.end_selection(window, cx);
15512 if let Some(tx_id) = self
15513 .buffer
15514 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15515 {
15516 self.selection_history
15517 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15518 cx.emit(EditorEvent::TransactionBegun {
15519 transaction_id: tx_id,
15520 })
15521 }
15522 }
15523
15524 pub fn end_transaction_at(
15525 &mut self,
15526 now: Instant,
15527 cx: &mut Context<Self>,
15528 ) -> Option<TransactionId> {
15529 if let Some(transaction_id) = self
15530 .buffer
15531 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15532 {
15533 if let Some((_, end_selections)) =
15534 self.selection_history.transaction_mut(transaction_id)
15535 {
15536 *end_selections = Some(self.selections.disjoint_anchors());
15537 } else {
15538 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15539 }
15540
15541 cx.emit(EditorEvent::Edited { transaction_id });
15542 Some(transaction_id)
15543 } else {
15544 None
15545 }
15546 }
15547
15548 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15549 if self.selection_mark_mode {
15550 self.change_selections(None, window, cx, |s| {
15551 s.move_with(|_, sel| {
15552 sel.collapse_to(sel.head(), SelectionGoal::None);
15553 });
15554 })
15555 }
15556 self.selection_mark_mode = true;
15557 cx.notify();
15558 }
15559
15560 pub fn swap_selection_ends(
15561 &mut self,
15562 _: &actions::SwapSelectionEnds,
15563 window: &mut Window,
15564 cx: &mut Context<Self>,
15565 ) {
15566 self.change_selections(None, window, cx, |s| {
15567 s.move_with(|_, sel| {
15568 if sel.start != sel.end {
15569 sel.reversed = !sel.reversed
15570 }
15571 });
15572 });
15573 self.request_autoscroll(Autoscroll::newest(), cx);
15574 cx.notify();
15575 }
15576
15577 pub fn toggle_fold(
15578 &mut self,
15579 _: &actions::ToggleFold,
15580 window: &mut Window,
15581 cx: &mut Context<Self>,
15582 ) {
15583 if self.is_singleton(cx) {
15584 let selection = self.selections.newest::<Point>(cx);
15585
15586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15587 let range = if selection.is_empty() {
15588 let point = selection.head().to_display_point(&display_map);
15589 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15590 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15591 .to_point(&display_map);
15592 start..end
15593 } else {
15594 selection.range()
15595 };
15596 if display_map.folds_in_range(range).next().is_some() {
15597 self.unfold_lines(&Default::default(), window, cx)
15598 } else {
15599 self.fold(&Default::default(), window, cx)
15600 }
15601 } else {
15602 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15603 let buffer_ids: HashSet<_> = self
15604 .selections
15605 .disjoint_anchor_ranges()
15606 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15607 .collect();
15608
15609 let should_unfold = buffer_ids
15610 .iter()
15611 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15612
15613 for buffer_id in buffer_ids {
15614 if should_unfold {
15615 self.unfold_buffer(buffer_id, cx);
15616 } else {
15617 self.fold_buffer(buffer_id, cx);
15618 }
15619 }
15620 }
15621 }
15622
15623 pub fn toggle_fold_recursive(
15624 &mut self,
15625 _: &actions::ToggleFoldRecursive,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) {
15629 let selection = self.selections.newest::<Point>(cx);
15630
15631 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15632 let range = if selection.is_empty() {
15633 let point = selection.head().to_display_point(&display_map);
15634 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15635 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15636 .to_point(&display_map);
15637 start..end
15638 } else {
15639 selection.range()
15640 };
15641 if display_map.folds_in_range(range).next().is_some() {
15642 self.unfold_recursive(&Default::default(), window, cx)
15643 } else {
15644 self.fold_recursive(&Default::default(), window, cx)
15645 }
15646 }
15647
15648 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15649 if self.is_singleton(cx) {
15650 let mut to_fold = Vec::new();
15651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15652 let selections = self.selections.all_adjusted(cx);
15653
15654 for selection in selections {
15655 let range = selection.range().sorted();
15656 let buffer_start_row = range.start.row;
15657
15658 if range.start.row != range.end.row {
15659 let mut found = false;
15660 let mut row = range.start.row;
15661 while row <= range.end.row {
15662 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15663 {
15664 found = true;
15665 row = crease.range().end.row + 1;
15666 to_fold.push(crease);
15667 } else {
15668 row += 1
15669 }
15670 }
15671 if found {
15672 continue;
15673 }
15674 }
15675
15676 for row in (0..=range.start.row).rev() {
15677 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15678 if crease.range().end.row >= buffer_start_row {
15679 to_fold.push(crease);
15680 if row <= range.start.row {
15681 break;
15682 }
15683 }
15684 }
15685 }
15686 }
15687
15688 self.fold_creases(to_fold, true, window, cx);
15689 } else {
15690 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15691 let buffer_ids = self
15692 .selections
15693 .disjoint_anchor_ranges()
15694 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15695 .collect::<HashSet<_>>();
15696 for buffer_id in buffer_ids {
15697 self.fold_buffer(buffer_id, cx);
15698 }
15699 }
15700 }
15701
15702 fn fold_at_level(
15703 &mut self,
15704 fold_at: &FoldAtLevel,
15705 window: &mut Window,
15706 cx: &mut Context<Self>,
15707 ) {
15708 if !self.buffer.read(cx).is_singleton() {
15709 return;
15710 }
15711
15712 let fold_at_level = fold_at.0;
15713 let snapshot = self.buffer.read(cx).snapshot(cx);
15714 let mut to_fold = Vec::new();
15715 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15716
15717 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15718 while start_row < end_row {
15719 match self
15720 .snapshot(window, cx)
15721 .crease_for_buffer_row(MultiBufferRow(start_row))
15722 {
15723 Some(crease) => {
15724 let nested_start_row = crease.range().start.row + 1;
15725 let nested_end_row = crease.range().end.row;
15726
15727 if current_level < fold_at_level {
15728 stack.push((nested_start_row, nested_end_row, current_level + 1));
15729 } else if current_level == fold_at_level {
15730 to_fold.push(crease);
15731 }
15732
15733 start_row = nested_end_row + 1;
15734 }
15735 None => start_row += 1,
15736 }
15737 }
15738 }
15739
15740 self.fold_creases(to_fold, true, window, cx);
15741 }
15742
15743 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15744 if self.buffer.read(cx).is_singleton() {
15745 let mut fold_ranges = Vec::new();
15746 let snapshot = self.buffer.read(cx).snapshot(cx);
15747
15748 for row in 0..snapshot.max_row().0 {
15749 if let Some(foldable_range) = self
15750 .snapshot(window, cx)
15751 .crease_for_buffer_row(MultiBufferRow(row))
15752 {
15753 fold_ranges.push(foldable_range);
15754 }
15755 }
15756
15757 self.fold_creases(fold_ranges, true, window, cx);
15758 } else {
15759 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15760 editor
15761 .update_in(cx, |editor, _, cx| {
15762 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15763 editor.fold_buffer(buffer_id, cx);
15764 }
15765 })
15766 .ok();
15767 });
15768 }
15769 }
15770
15771 pub fn fold_function_bodies(
15772 &mut self,
15773 _: &actions::FoldFunctionBodies,
15774 window: &mut Window,
15775 cx: &mut Context<Self>,
15776 ) {
15777 let snapshot = self.buffer.read(cx).snapshot(cx);
15778
15779 let ranges = snapshot
15780 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15781 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15782 .collect::<Vec<_>>();
15783
15784 let creases = ranges
15785 .into_iter()
15786 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15787 .collect();
15788
15789 self.fold_creases(creases, true, window, cx);
15790 }
15791
15792 pub fn fold_recursive(
15793 &mut self,
15794 _: &actions::FoldRecursive,
15795 window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 let mut to_fold = Vec::new();
15799 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15800 let selections = self.selections.all_adjusted(cx);
15801
15802 for selection in selections {
15803 let range = selection.range().sorted();
15804 let buffer_start_row = range.start.row;
15805
15806 if range.start.row != range.end.row {
15807 let mut found = false;
15808 for row in range.start.row..=range.end.row {
15809 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15810 found = true;
15811 to_fold.push(crease);
15812 }
15813 }
15814 if found {
15815 continue;
15816 }
15817 }
15818
15819 for row in (0..=range.start.row).rev() {
15820 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15821 if crease.range().end.row >= buffer_start_row {
15822 to_fold.push(crease);
15823 } else {
15824 break;
15825 }
15826 }
15827 }
15828 }
15829
15830 self.fold_creases(to_fold, true, window, cx);
15831 }
15832
15833 pub fn fold_at(
15834 &mut self,
15835 buffer_row: MultiBufferRow,
15836 window: &mut Window,
15837 cx: &mut Context<Self>,
15838 ) {
15839 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15840
15841 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15842 let autoscroll = self
15843 .selections
15844 .all::<Point>(cx)
15845 .iter()
15846 .any(|selection| crease.range().overlaps(&selection.range()));
15847
15848 self.fold_creases(vec![crease], autoscroll, window, cx);
15849 }
15850 }
15851
15852 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15853 if self.is_singleton(cx) {
15854 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15855 let buffer = &display_map.buffer_snapshot;
15856 let selections = self.selections.all::<Point>(cx);
15857 let ranges = selections
15858 .iter()
15859 .map(|s| {
15860 let range = s.display_range(&display_map).sorted();
15861 let mut start = range.start.to_point(&display_map);
15862 let mut end = range.end.to_point(&display_map);
15863 start.column = 0;
15864 end.column = buffer.line_len(MultiBufferRow(end.row));
15865 start..end
15866 })
15867 .collect::<Vec<_>>();
15868
15869 self.unfold_ranges(&ranges, true, true, cx);
15870 } else {
15871 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15872 let buffer_ids = self
15873 .selections
15874 .disjoint_anchor_ranges()
15875 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15876 .collect::<HashSet<_>>();
15877 for buffer_id in buffer_ids {
15878 self.unfold_buffer(buffer_id, cx);
15879 }
15880 }
15881 }
15882
15883 pub fn unfold_recursive(
15884 &mut self,
15885 _: &UnfoldRecursive,
15886 _window: &mut Window,
15887 cx: &mut Context<Self>,
15888 ) {
15889 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15890 let selections = self.selections.all::<Point>(cx);
15891 let ranges = selections
15892 .iter()
15893 .map(|s| {
15894 let mut range = s.display_range(&display_map).sorted();
15895 *range.start.column_mut() = 0;
15896 *range.end.column_mut() = display_map.line_len(range.end.row());
15897 let start = range.start.to_point(&display_map);
15898 let end = range.end.to_point(&display_map);
15899 start..end
15900 })
15901 .collect::<Vec<_>>();
15902
15903 self.unfold_ranges(&ranges, true, true, cx);
15904 }
15905
15906 pub fn unfold_at(
15907 &mut self,
15908 buffer_row: MultiBufferRow,
15909 _window: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) {
15912 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15913
15914 let intersection_range = Point::new(buffer_row.0, 0)
15915 ..Point::new(
15916 buffer_row.0,
15917 display_map.buffer_snapshot.line_len(buffer_row),
15918 );
15919
15920 let autoscroll = self
15921 .selections
15922 .all::<Point>(cx)
15923 .iter()
15924 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15925
15926 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15927 }
15928
15929 pub fn unfold_all(
15930 &mut self,
15931 _: &actions::UnfoldAll,
15932 _window: &mut Window,
15933 cx: &mut Context<Self>,
15934 ) {
15935 if self.buffer.read(cx).is_singleton() {
15936 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15937 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15938 } else {
15939 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15940 editor
15941 .update(cx, |editor, cx| {
15942 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15943 editor.unfold_buffer(buffer_id, cx);
15944 }
15945 })
15946 .ok();
15947 });
15948 }
15949 }
15950
15951 pub fn fold_selected_ranges(
15952 &mut self,
15953 _: &FoldSelectedRanges,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 let selections = self.selections.all_adjusted(cx);
15958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15959 let ranges = selections
15960 .into_iter()
15961 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15962 .collect::<Vec<_>>();
15963 self.fold_creases(ranges, true, window, cx);
15964 }
15965
15966 pub fn fold_ranges<T: ToOffset + Clone>(
15967 &mut self,
15968 ranges: Vec<Range<T>>,
15969 auto_scroll: bool,
15970 window: &mut Window,
15971 cx: &mut Context<Self>,
15972 ) {
15973 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15974 let ranges = ranges
15975 .into_iter()
15976 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15977 .collect::<Vec<_>>();
15978 self.fold_creases(ranges, auto_scroll, window, cx);
15979 }
15980
15981 pub fn fold_creases<T: ToOffset + Clone>(
15982 &mut self,
15983 creases: Vec<Crease<T>>,
15984 auto_scroll: bool,
15985 _window: &mut Window,
15986 cx: &mut Context<Self>,
15987 ) {
15988 if creases.is_empty() {
15989 return;
15990 }
15991
15992 let mut buffers_affected = HashSet::default();
15993 let multi_buffer = self.buffer().read(cx);
15994 for crease in &creases {
15995 if let Some((_, buffer, _)) =
15996 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15997 {
15998 buffers_affected.insert(buffer.read(cx).remote_id());
15999 };
16000 }
16001
16002 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16003
16004 if auto_scroll {
16005 self.request_autoscroll(Autoscroll::fit(), cx);
16006 }
16007
16008 cx.notify();
16009
16010 self.scrollbar_marker_state.dirty = true;
16011 self.folds_did_change(cx);
16012 }
16013
16014 /// Removes any folds whose ranges intersect any of the given ranges.
16015 pub fn unfold_ranges<T: ToOffset + Clone>(
16016 &mut self,
16017 ranges: &[Range<T>],
16018 inclusive: bool,
16019 auto_scroll: bool,
16020 cx: &mut Context<Self>,
16021 ) {
16022 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16023 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16024 });
16025 self.folds_did_change(cx);
16026 }
16027
16028 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16029 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16030 return;
16031 }
16032 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16033 self.display_map.update(cx, |display_map, cx| {
16034 display_map.fold_buffers([buffer_id], cx)
16035 });
16036 cx.emit(EditorEvent::BufferFoldToggled {
16037 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16038 folded: true,
16039 });
16040 cx.notify();
16041 }
16042
16043 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16044 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16045 return;
16046 }
16047 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16048 self.display_map.update(cx, |display_map, cx| {
16049 display_map.unfold_buffers([buffer_id], cx);
16050 });
16051 cx.emit(EditorEvent::BufferFoldToggled {
16052 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16053 folded: false,
16054 });
16055 cx.notify();
16056 }
16057
16058 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16059 self.display_map.read(cx).is_buffer_folded(buffer)
16060 }
16061
16062 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16063 self.display_map.read(cx).folded_buffers()
16064 }
16065
16066 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16067 self.display_map.update(cx, |display_map, cx| {
16068 display_map.disable_header_for_buffer(buffer_id, cx);
16069 });
16070 cx.notify();
16071 }
16072
16073 /// Removes any folds with the given ranges.
16074 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16075 &mut self,
16076 ranges: &[Range<T>],
16077 type_id: TypeId,
16078 auto_scroll: bool,
16079 cx: &mut Context<Self>,
16080 ) {
16081 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16082 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16083 });
16084 self.folds_did_change(cx);
16085 }
16086
16087 fn remove_folds_with<T: ToOffset + Clone>(
16088 &mut self,
16089 ranges: &[Range<T>],
16090 auto_scroll: bool,
16091 cx: &mut Context<Self>,
16092 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16093 ) {
16094 if ranges.is_empty() {
16095 return;
16096 }
16097
16098 let mut buffers_affected = HashSet::default();
16099 let multi_buffer = self.buffer().read(cx);
16100 for range in ranges {
16101 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16102 buffers_affected.insert(buffer.read(cx).remote_id());
16103 };
16104 }
16105
16106 self.display_map.update(cx, update);
16107
16108 if auto_scroll {
16109 self.request_autoscroll(Autoscroll::fit(), cx);
16110 }
16111
16112 cx.notify();
16113 self.scrollbar_marker_state.dirty = true;
16114 self.active_indent_guides_state.dirty = true;
16115 }
16116
16117 pub fn update_fold_widths(
16118 &mut self,
16119 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16120 cx: &mut Context<Self>,
16121 ) -> bool {
16122 self.display_map
16123 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16124 }
16125
16126 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16127 self.display_map.read(cx).fold_placeholder.clone()
16128 }
16129
16130 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16131 self.buffer.update(cx, |buffer, cx| {
16132 buffer.set_all_diff_hunks_expanded(cx);
16133 });
16134 }
16135
16136 pub fn expand_all_diff_hunks(
16137 &mut self,
16138 _: &ExpandAllDiffHunks,
16139 _window: &mut Window,
16140 cx: &mut Context<Self>,
16141 ) {
16142 self.buffer.update(cx, |buffer, cx| {
16143 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16144 });
16145 }
16146
16147 pub fn toggle_selected_diff_hunks(
16148 &mut self,
16149 _: &ToggleSelectedDiffHunks,
16150 _window: &mut Window,
16151 cx: &mut Context<Self>,
16152 ) {
16153 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16154 self.toggle_diff_hunks_in_ranges(ranges, cx);
16155 }
16156
16157 pub fn diff_hunks_in_ranges<'a>(
16158 &'a self,
16159 ranges: &'a [Range<Anchor>],
16160 buffer: &'a MultiBufferSnapshot,
16161 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16162 ranges.iter().flat_map(move |range| {
16163 let end_excerpt_id = range.end.excerpt_id;
16164 let range = range.to_point(buffer);
16165 let mut peek_end = range.end;
16166 if range.end.row < buffer.max_row().0 {
16167 peek_end = Point::new(range.end.row + 1, 0);
16168 }
16169 buffer
16170 .diff_hunks_in_range(range.start..peek_end)
16171 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16172 })
16173 }
16174
16175 pub fn has_stageable_diff_hunks_in_ranges(
16176 &self,
16177 ranges: &[Range<Anchor>],
16178 snapshot: &MultiBufferSnapshot,
16179 ) -> bool {
16180 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16181 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16182 }
16183
16184 pub fn toggle_staged_selected_diff_hunks(
16185 &mut self,
16186 _: &::git::ToggleStaged,
16187 _: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) {
16190 let snapshot = self.buffer.read(cx).snapshot(cx);
16191 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16192 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16193 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16194 }
16195
16196 pub fn set_render_diff_hunk_controls(
16197 &mut self,
16198 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16199 cx: &mut Context<Self>,
16200 ) {
16201 self.render_diff_hunk_controls = render_diff_hunk_controls;
16202 cx.notify();
16203 }
16204
16205 pub fn stage_and_next(
16206 &mut self,
16207 _: &::git::StageAndNext,
16208 window: &mut Window,
16209 cx: &mut Context<Self>,
16210 ) {
16211 self.do_stage_or_unstage_and_next(true, window, cx);
16212 }
16213
16214 pub fn unstage_and_next(
16215 &mut self,
16216 _: &::git::UnstageAndNext,
16217 window: &mut Window,
16218 cx: &mut Context<Self>,
16219 ) {
16220 self.do_stage_or_unstage_and_next(false, window, cx);
16221 }
16222
16223 pub fn stage_or_unstage_diff_hunks(
16224 &mut self,
16225 stage: bool,
16226 ranges: Vec<Range<Anchor>>,
16227 cx: &mut Context<Self>,
16228 ) {
16229 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16230 cx.spawn(async move |this, cx| {
16231 task.await?;
16232 this.update(cx, |this, cx| {
16233 let snapshot = this.buffer.read(cx).snapshot(cx);
16234 let chunk_by = this
16235 .diff_hunks_in_ranges(&ranges, &snapshot)
16236 .chunk_by(|hunk| hunk.buffer_id);
16237 for (buffer_id, hunks) in &chunk_by {
16238 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16239 }
16240 })
16241 })
16242 .detach_and_log_err(cx);
16243 }
16244
16245 fn save_buffers_for_ranges_if_needed(
16246 &mut self,
16247 ranges: &[Range<Anchor>],
16248 cx: &mut Context<Editor>,
16249 ) -> Task<Result<()>> {
16250 let multibuffer = self.buffer.read(cx);
16251 let snapshot = multibuffer.read(cx);
16252 let buffer_ids: HashSet<_> = ranges
16253 .iter()
16254 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16255 .collect();
16256 drop(snapshot);
16257
16258 let mut buffers = HashSet::default();
16259 for buffer_id in buffer_ids {
16260 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16261 let buffer = buffer_entity.read(cx);
16262 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16263 {
16264 buffers.insert(buffer_entity);
16265 }
16266 }
16267 }
16268
16269 if let Some(project) = &self.project {
16270 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16271 } else {
16272 Task::ready(Ok(()))
16273 }
16274 }
16275
16276 fn do_stage_or_unstage_and_next(
16277 &mut self,
16278 stage: bool,
16279 window: &mut Window,
16280 cx: &mut Context<Self>,
16281 ) {
16282 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16283
16284 if ranges.iter().any(|range| range.start != range.end) {
16285 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16286 return;
16287 }
16288
16289 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16290 let snapshot = self.snapshot(window, cx);
16291 let position = self.selections.newest::<Point>(cx).head();
16292 let mut row = snapshot
16293 .buffer_snapshot
16294 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16295 .find(|hunk| hunk.row_range.start.0 > position.row)
16296 .map(|hunk| hunk.row_range.start);
16297
16298 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16299 // Outside of the project diff editor, wrap around to the beginning.
16300 if !all_diff_hunks_expanded {
16301 row = row.or_else(|| {
16302 snapshot
16303 .buffer_snapshot
16304 .diff_hunks_in_range(Point::zero()..position)
16305 .find(|hunk| hunk.row_range.end.0 < position.row)
16306 .map(|hunk| hunk.row_range.start)
16307 });
16308 }
16309
16310 if let Some(row) = row {
16311 let destination = Point::new(row.0, 0);
16312 let autoscroll = Autoscroll::center();
16313
16314 self.unfold_ranges(&[destination..destination], false, false, cx);
16315 self.change_selections(Some(autoscroll), window, cx, |s| {
16316 s.select_ranges([destination..destination]);
16317 });
16318 }
16319 }
16320
16321 fn do_stage_or_unstage(
16322 &self,
16323 stage: bool,
16324 buffer_id: BufferId,
16325 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16326 cx: &mut App,
16327 ) -> Option<()> {
16328 let project = self.project.as_ref()?;
16329 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16330 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16331 let buffer_snapshot = buffer.read(cx).snapshot();
16332 let file_exists = buffer_snapshot
16333 .file()
16334 .is_some_and(|file| file.disk_state().exists());
16335 diff.update(cx, |diff, cx| {
16336 diff.stage_or_unstage_hunks(
16337 stage,
16338 &hunks
16339 .map(|hunk| buffer_diff::DiffHunk {
16340 buffer_range: hunk.buffer_range,
16341 diff_base_byte_range: hunk.diff_base_byte_range,
16342 secondary_status: hunk.secondary_status,
16343 range: Point::zero()..Point::zero(), // unused
16344 })
16345 .collect::<Vec<_>>(),
16346 &buffer_snapshot,
16347 file_exists,
16348 cx,
16349 )
16350 });
16351 None
16352 }
16353
16354 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16355 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16356 self.buffer
16357 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16358 }
16359
16360 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16361 self.buffer.update(cx, |buffer, cx| {
16362 let ranges = vec![Anchor::min()..Anchor::max()];
16363 if !buffer.all_diff_hunks_expanded()
16364 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16365 {
16366 buffer.collapse_diff_hunks(ranges, cx);
16367 true
16368 } else {
16369 false
16370 }
16371 })
16372 }
16373
16374 fn toggle_diff_hunks_in_ranges(
16375 &mut self,
16376 ranges: Vec<Range<Anchor>>,
16377 cx: &mut Context<Editor>,
16378 ) {
16379 self.buffer.update(cx, |buffer, cx| {
16380 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16381 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16382 })
16383 }
16384
16385 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16386 self.buffer.update(cx, |buffer, cx| {
16387 let snapshot = buffer.snapshot(cx);
16388 let excerpt_id = range.end.excerpt_id;
16389 let point_range = range.to_point(&snapshot);
16390 let expand = !buffer.single_hunk_is_expanded(range, cx);
16391 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16392 })
16393 }
16394
16395 pub(crate) fn apply_all_diff_hunks(
16396 &mut self,
16397 _: &ApplyAllDiffHunks,
16398 window: &mut Window,
16399 cx: &mut Context<Self>,
16400 ) {
16401 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16402
16403 let buffers = self.buffer.read(cx).all_buffers();
16404 for branch_buffer in buffers {
16405 branch_buffer.update(cx, |branch_buffer, cx| {
16406 branch_buffer.merge_into_base(Vec::new(), cx);
16407 });
16408 }
16409
16410 if let Some(project) = self.project.clone() {
16411 self.save(true, project, window, cx).detach_and_log_err(cx);
16412 }
16413 }
16414
16415 pub(crate) fn apply_selected_diff_hunks(
16416 &mut self,
16417 _: &ApplyDiffHunk,
16418 window: &mut Window,
16419 cx: &mut Context<Self>,
16420 ) {
16421 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16422 let snapshot = self.snapshot(window, cx);
16423 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16424 let mut ranges_by_buffer = HashMap::default();
16425 self.transact(window, cx, |editor, _window, cx| {
16426 for hunk in hunks {
16427 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16428 ranges_by_buffer
16429 .entry(buffer.clone())
16430 .or_insert_with(Vec::new)
16431 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16432 }
16433 }
16434
16435 for (buffer, ranges) in ranges_by_buffer {
16436 buffer.update(cx, |buffer, cx| {
16437 buffer.merge_into_base(ranges, cx);
16438 });
16439 }
16440 });
16441
16442 if let Some(project) = self.project.clone() {
16443 self.save(true, project, window, cx).detach_and_log_err(cx);
16444 }
16445 }
16446
16447 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16448 if hovered != self.gutter_hovered {
16449 self.gutter_hovered = hovered;
16450 cx.notify();
16451 }
16452 }
16453
16454 pub fn insert_blocks(
16455 &mut self,
16456 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16457 autoscroll: Option<Autoscroll>,
16458 cx: &mut Context<Self>,
16459 ) -> Vec<CustomBlockId> {
16460 let blocks = self
16461 .display_map
16462 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16463 if let Some(autoscroll) = autoscroll {
16464 self.request_autoscroll(autoscroll, cx);
16465 }
16466 cx.notify();
16467 blocks
16468 }
16469
16470 pub fn resize_blocks(
16471 &mut self,
16472 heights: HashMap<CustomBlockId, u32>,
16473 autoscroll: Option<Autoscroll>,
16474 cx: &mut Context<Self>,
16475 ) {
16476 self.display_map
16477 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16478 if let Some(autoscroll) = autoscroll {
16479 self.request_autoscroll(autoscroll, cx);
16480 }
16481 cx.notify();
16482 }
16483
16484 pub fn replace_blocks(
16485 &mut self,
16486 renderers: HashMap<CustomBlockId, RenderBlock>,
16487 autoscroll: Option<Autoscroll>,
16488 cx: &mut Context<Self>,
16489 ) {
16490 self.display_map
16491 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16492 if let Some(autoscroll) = autoscroll {
16493 self.request_autoscroll(autoscroll, cx);
16494 }
16495 cx.notify();
16496 }
16497
16498 pub fn remove_blocks(
16499 &mut self,
16500 block_ids: HashSet<CustomBlockId>,
16501 autoscroll: Option<Autoscroll>,
16502 cx: &mut Context<Self>,
16503 ) {
16504 self.display_map.update(cx, |display_map, cx| {
16505 display_map.remove_blocks(block_ids, cx)
16506 });
16507 if let Some(autoscroll) = autoscroll {
16508 self.request_autoscroll(autoscroll, cx);
16509 }
16510 cx.notify();
16511 }
16512
16513 pub fn row_for_block(
16514 &self,
16515 block_id: CustomBlockId,
16516 cx: &mut Context<Self>,
16517 ) -> Option<DisplayRow> {
16518 self.display_map
16519 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16520 }
16521
16522 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16523 self.focused_block = Some(focused_block);
16524 }
16525
16526 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16527 self.focused_block.take()
16528 }
16529
16530 pub fn insert_creases(
16531 &mut self,
16532 creases: impl IntoIterator<Item = Crease<Anchor>>,
16533 cx: &mut Context<Self>,
16534 ) -> Vec<CreaseId> {
16535 self.display_map
16536 .update(cx, |map, cx| map.insert_creases(creases, cx))
16537 }
16538
16539 pub fn remove_creases(
16540 &mut self,
16541 ids: impl IntoIterator<Item = CreaseId>,
16542 cx: &mut Context<Self>,
16543 ) -> Vec<(CreaseId, Range<Anchor>)> {
16544 self.display_map
16545 .update(cx, |map, cx| map.remove_creases(ids, cx))
16546 }
16547
16548 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16549 self.display_map
16550 .update(cx, |map, cx| map.snapshot(cx))
16551 .longest_row()
16552 }
16553
16554 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16555 self.display_map
16556 .update(cx, |map, cx| map.snapshot(cx))
16557 .max_point()
16558 }
16559
16560 pub fn text(&self, cx: &App) -> String {
16561 self.buffer.read(cx).read(cx).text()
16562 }
16563
16564 pub fn is_empty(&self, cx: &App) -> bool {
16565 self.buffer.read(cx).read(cx).is_empty()
16566 }
16567
16568 pub fn text_option(&self, cx: &App) -> Option<String> {
16569 let text = self.text(cx);
16570 let text = text.trim();
16571
16572 if text.is_empty() {
16573 return None;
16574 }
16575
16576 Some(text.to_string())
16577 }
16578
16579 pub fn set_text(
16580 &mut self,
16581 text: impl Into<Arc<str>>,
16582 window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 self.transact(window, cx, |this, _, cx| {
16586 this.buffer
16587 .read(cx)
16588 .as_singleton()
16589 .expect("you can only call set_text on editors for singleton buffers")
16590 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16591 });
16592 }
16593
16594 pub fn display_text(&self, cx: &mut App) -> String {
16595 self.display_map
16596 .update(cx, |map, cx| map.snapshot(cx))
16597 .text()
16598 }
16599
16600 fn create_minimap(
16601 &self,
16602 minimap_settings: MinimapSettings,
16603 window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) -> Option<Entity<Self>> {
16606 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16607 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16608 }
16609
16610 fn initialize_new_minimap(
16611 &self,
16612 minimap_settings: MinimapSettings,
16613 window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) -> Entity<Self> {
16616 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16617
16618 let mut minimap = Editor::new_internal(
16619 EditorMode::Minimap {
16620 parent: cx.weak_entity(),
16621 },
16622 self.buffer.clone(),
16623 self.project.clone(),
16624 Some(self.display_map.clone()),
16625 window,
16626 cx,
16627 );
16628 minimap.scroll_manager.clone_state(&self.scroll_manager);
16629 minimap.set_text_style_refinement(TextStyleRefinement {
16630 font_size: Some(MINIMAP_FONT_SIZE),
16631 font_weight: Some(MINIMAP_FONT_WEIGHT),
16632 ..Default::default()
16633 });
16634 minimap.update_minimap_configuration(minimap_settings, cx);
16635 cx.new(|_| minimap)
16636 }
16637
16638 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16639 let current_line_highlight = minimap_settings
16640 .current_line_highlight
16641 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16642 self.set_current_line_highlight(Some(current_line_highlight));
16643 }
16644
16645 pub fn minimap(&self) -> Option<&Entity<Self>> {
16646 self.minimap
16647 .as_ref()
16648 .filter(|_| self.minimap_visibility.visible())
16649 }
16650
16651 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16652 let mut wrap_guides = smallvec::smallvec![];
16653
16654 if self.show_wrap_guides == Some(false) {
16655 return wrap_guides;
16656 }
16657
16658 let settings = self.buffer.read(cx).language_settings(cx);
16659 if settings.show_wrap_guides {
16660 match self.soft_wrap_mode(cx) {
16661 SoftWrap::Column(soft_wrap) => {
16662 wrap_guides.push((soft_wrap as usize, true));
16663 }
16664 SoftWrap::Bounded(soft_wrap) => {
16665 wrap_guides.push((soft_wrap as usize, true));
16666 }
16667 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16668 }
16669 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16670 }
16671
16672 wrap_guides
16673 }
16674
16675 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16676 let settings = self.buffer.read(cx).language_settings(cx);
16677 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16678 match mode {
16679 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16680 SoftWrap::None
16681 }
16682 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16683 language_settings::SoftWrap::PreferredLineLength => {
16684 SoftWrap::Column(settings.preferred_line_length)
16685 }
16686 language_settings::SoftWrap::Bounded => {
16687 SoftWrap::Bounded(settings.preferred_line_length)
16688 }
16689 }
16690 }
16691
16692 pub fn set_soft_wrap_mode(
16693 &mut self,
16694 mode: language_settings::SoftWrap,
16695
16696 cx: &mut Context<Self>,
16697 ) {
16698 self.soft_wrap_mode_override = Some(mode);
16699 cx.notify();
16700 }
16701
16702 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16703 self.hard_wrap = hard_wrap;
16704 cx.notify();
16705 }
16706
16707 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16708 self.text_style_refinement = Some(style);
16709 }
16710
16711 /// called by the Element so we know what style we were most recently rendered with.
16712 pub(crate) fn set_style(
16713 &mut self,
16714 style: EditorStyle,
16715 window: &mut Window,
16716 cx: &mut Context<Self>,
16717 ) {
16718 // We intentionally do not inform the display map about the minimap style
16719 // so that wrapping is not recalculated and stays consistent for the editor
16720 // and its linked minimap.
16721 if !self.mode.is_minimap() {
16722 let rem_size = window.rem_size();
16723 self.display_map.update(cx, |map, cx| {
16724 map.set_font(
16725 style.text.font(),
16726 style.text.font_size.to_pixels(rem_size),
16727 cx,
16728 )
16729 });
16730 }
16731 self.style = Some(style);
16732 }
16733
16734 pub fn style(&self) -> Option<&EditorStyle> {
16735 self.style.as_ref()
16736 }
16737
16738 // Called by the element. This method is not designed to be called outside of the editor
16739 // element's layout code because it does not notify when rewrapping is computed synchronously.
16740 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16741 self.display_map
16742 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16743 }
16744
16745 pub fn set_soft_wrap(&mut self) {
16746 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16747 }
16748
16749 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16750 if self.soft_wrap_mode_override.is_some() {
16751 self.soft_wrap_mode_override.take();
16752 } else {
16753 let soft_wrap = match self.soft_wrap_mode(cx) {
16754 SoftWrap::GitDiff => return,
16755 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16756 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16757 language_settings::SoftWrap::None
16758 }
16759 };
16760 self.soft_wrap_mode_override = Some(soft_wrap);
16761 }
16762 cx.notify();
16763 }
16764
16765 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16766 let Some(workspace) = self.workspace() else {
16767 return;
16768 };
16769 let fs = workspace.read(cx).app_state().fs.clone();
16770 let current_show = TabBarSettings::get_global(cx).show;
16771 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16772 setting.show = Some(!current_show);
16773 });
16774 }
16775
16776 pub fn toggle_indent_guides(
16777 &mut self,
16778 _: &ToggleIndentGuides,
16779 _: &mut Window,
16780 cx: &mut Context<Self>,
16781 ) {
16782 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16783 self.buffer
16784 .read(cx)
16785 .language_settings(cx)
16786 .indent_guides
16787 .enabled
16788 });
16789 self.show_indent_guides = Some(!currently_enabled);
16790 cx.notify();
16791 }
16792
16793 fn should_show_indent_guides(&self) -> Option<bool> {
16794 self.show_indent_guides
16795 }
16796
16797 pub fn toggle_line_numbers(
16798 &mut self,
16799 _: &ToggleLineNumbers,
16800 _: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 let mut editor_settings = EditorSettings::get_global(cx).clone();
16804 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16805 EditorSettings::override_global(editor_settings, cx);
16806 }
16807
16808 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16809 if let Some(show_line_numbers) = self.show_line_numbers {
16810 return show_line_numbers;
16811 }
16812 EditorSettings::get_global(cx).gutter.line_numbers
16813 }
16814
16815 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16816 self.use_relative_line_numbers
16817 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16818 }
16819
16820 pub fn toggle_relative_line_numbers(
16821 &mut self,
16822 _: &ToggleRelativeLineNumbers,
16823 _: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) {
16826 let is_relative = self.should_use_relative_line_numbers(cx);
16827 self.set_relative_line_number(Some(!is_relative), cx)
16828 }
16829
16830 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16831 self.use_relative_line_numbers = is_relative;
16832 cx.notify();
16833 }
16834
16835 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16836 self.show_gutter = show_gutter;
16837 cx.notify();
16838 }
16839
16840 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16841 self.show_scrollbars = show_scrollbars;
16842 cx.notify();
16843 }
16844
16845 pub fn set_minimap_visibility(
16846 &mut self,
16847 minimap_visibility: MinimapVisibility,
16848 window: &mut Window,
16849 cx: &mut Context<Self>,
16850 ) {
16851 if self.minimap_visibility != minimap_visibility {
16852 if minimap_visibility.visible() && self.minimap.is_none() {
16853 let minimap_settings = EditorSettings::get_global(cx).minimap;
16854 self.minimap =
16855 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16856 }
16857 self.minimap_visibility = minimap_visibility;
16858 cx.notify();
16859 }
16860 }
16861
16862 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16863 self.set_show_scrollbars(false, cx);
16864 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16865 }
16866
16867 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16868 self.show_line_numbers = Some(show_line_numbers);
16869 cx.notify();
16870 }
16871
16872 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16873 self.disable_expand_excerpt_buttons = true;
16874 cx.notify();
16875 }
16876
16877 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16878 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16879 cx.notify();
16880 }
16881
16882 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16883 self.show_code_actions = Some(show_code_actions);
16884 cx.notify();
16885 }
16886
16887 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16888 self.show_runnables = Some(show_runnables);
16889 cx.notify();
16890 }
16891
16892 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16893 self.show_breakpoints = Some(show_breakpoints);
16894 cx.notify();
16895 }
16896
16897 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16898 if self.display_map.read(cx).masked != masked {
16899 self.display_map.update(cx, |map, _| map.masked = masked);
16900 }
16901 cx.notify()
16902 }
16903
16904 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16905 self.show_wrap_guides = Some(show_wrap_guides);
16906 cx.notify();
16907 }
16908
16909 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16910 self.show_indent_guides = Some(show_indent_guides);
16911 cx.notify();
16912 }
16913
16914 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16915 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16916 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16917 if let Some(dir) = file.abs_path(cx).parent() {
16918 return Some(dir.to_owned());
16919 }
16920 }
16921
16922 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16923 return Some(project_path.path.to_path_buf());
16924 }
16925 }
16926
16927 None
16928 }
16929
16930 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16931 self.active_excerpt(cx)?
16932 .1
16933 .read(cx)
16934 .file()
16935 .and_then(|f| f.as_local())
16936 }
16937
16938 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16939 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16940 let buffer = buffer.read(cx);
16941 if let Some(project_path) = buffer.project_path(cx) {
16942 let project = self.project.as_ref()?.read(cx);
16943 project.absolute_path(&project_path, cx)
16944 } else {
16945 buffer
16946 .file()
16947 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16948 }
16949 })
16950 }
16951
16952 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16953 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16954 let project_path = buffer.read(cx).project_path(cx)?;
16955 let project = self.project.as_ref()?.read(cx);
16956 let entry = project.entry_for_path(&project_path, cx)?;
16957 let path = entry.path.to_path_buf();
16958 Some(path)
16959 })
16960 }
16961
16962 pub fn reveal_in_finder(
16963 &mut self,
16964 _: &RevealInFileManager,
16965 _window: &mut Window,
16966 cx: &mut Context<Self>,
16967 ) {
16968 if let Some(target) = self.target_file(cx) {
16969 cx.reveal_path(&target.abs_path(cx));
16970 }
16971 }
16972
16973 pub fn copy_path(
16974 &mut self,
16975 _: &zed_actions::workspace::CopyPath,
16976 _window: &mut Window,
16977 cx: &mut Context<Self>,
16978 ) {
16979 if let Some(path) = self.target_file_abs_path(cx) {
16980 if let Some(path) = path.to_str() {
16981 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16982 }
16983 }
16984 }
16985
16986 pub fn copy_relative_path(
16987 &mut self,
16988 _: &zed_actions::workspace::CopyRelativePath,
16989 _window: &mut Window,
16990 cx: &mut Context<Self>,
16991 ) {
16992 if let Some(path) = self.target_file_path(cx) {
16993 if let Some(path) = path.to_str() {
16994 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16995 }
16996 }
16997 }
16998
16999 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17000 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17001 buffer.read(cx).project_path(cx)
17002 } else {
17003 None
17004 }
17005 }
17006
17007 // Returns true if the editor handled a go-to-line request
17008 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17009 maybe!({
17010 let breakpoint_store = self.breakpoint_store.as_ref()?;
17011
17012 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17013 else {
17014 self.clear_row_highlights::<ActiveDebugLine>();
17015 return None;
17016 };
17017
17018 let position = active_stack_frame.position;
17019 let buffer_id = position.buffer_id?;
17020 let snapshot = self
17021 .project
17022 .as_ref()?
17023 .read(cx)
17024 .buffer_for_id(buffer_id, cx)?
17025 .read(cx)
17026 .snapshot();
17027
17028 let mut handled = false;
17029 for (id, ExcerptRange { context, .. }) in
17030 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17031 {
17032 if context.start.cmp(&position, &snapshot).is_ge()
17033 || context.end.cmp(&position, &snapshot).is_lt()
17034 {
17035 continue;
17036 }
17037 let snapshot = self.buffer.read(cx).snapshot(cx);
17038 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17039
17040 handled = true;
17041 self.clear_row_highlights::<ActiveDebugLine>();
17042
17043 self.go_to_line::<ActiveDebugLine>(
17044 multibuffer_anchor,
17045 Some(cx.theme().colors().editor_debugger_active_line_background),
17046 window,
17047 cx,
17048 );
17049
17050 cx.notify();
17051 }
17052
17053 handled.then_some(())
17054 })
17055 .is_some()
17056 }
17057
17058 pub fn copy_file_name_without_extension(
17059 &mut self,
17060 _: &CopyFileNameWithoutExtension,
17061 _: &mut Window,
17062 cx: &mut Context<Self>,
17063 ) {
17064 if let Some(file) = self.target_file(cx) {
17065 if let Some(file_stem) = file.path().file_stem() {
17066 if let Some(name) = file_stem.to_str() {
17067 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17068 }
17069 }
17070 }
17071 }
17072
17073 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17074 if let Some(file) = self.target_file(cx) {
17075 if let Some(file_name) = file.path().file_name() {
17076 if let Some(name) = file_name.to_str() {
17077 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17078 }
17079 }
17080 }
17081 }
17082
17083 pub fn toggle_git_blame(
17084 &mut self,
17085 _: &::git::Blame,
17086 window: &mut Window,
17087 cx: &mut Context<Self>,
17088 ) {
17089 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17090
17091 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17092 self.start_git_blame(true, window, cx);
17093 }
17094
17095 cx.notify();
17096 }
17097
17098 pub fn toggle_git_blame_inline(
17099 &mut self,
17100 _: &ToggleGitBlameInline,
17101 window: &mut Window,
17102 cx: &mut Context<Self>,
17103 ) {
17104 self.toggle_git_blame_inline_internal(true, window, cx);
17105 cx.notify();
17106 }
17107
17108 pub fn open_git_blame_commit(
17109 &mut self,
17110 _: &OpenGitBlameCommit,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) {
17114 self.open_git_blame_commit_internal(window, cx);
17115 }
17116
17117 fn open_git_blame_commit_internal(
17118 &mut self,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) -> Option<()> {
17122 let blame = self.blame.as_ref()?;
17123 let snapshot = self.snapshot(window, cx);
17124 let cursor = self.selections.newest::<Point>(cx).head();
17125 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17126 let blame_entry = blame
17127 .update(cx, |blame, cx| {
17128 blame
17129 .blame_for_rows(
17130 &[RowInfo {
17131 buffer_id: Some(buffer.remote_id()),
17132 buffer_row: Some(point.row),
17133 ..Default::default()
17134 }],
17135 cx,
17136 )
17137 .next()
17138 })
17139 .flatten()?;
17140 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17141 let repo = blame.read(cx).repository(cx)?;
17142 let workspace = self.workspace()?.downgrade();
17143 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17144 None
17145 }
17146
17147 pub fn git_blame_inline_enabled(&self) -> bool {
17148 self.git_blame_inline_enabled
17149 }
17150
17151 pub fn toggle_selection_menu(
17152 &mut self,
17153 _: &ToggleSelectionMenu,
17154 _: &mut Window,
17155 cx: &mut Context<Self>,
17156 ) {
17157 self.show_selection_menu = self
17158 .show_selection_menu
17159 .map(|show_selections_menu| !show_selections_menu)
17160 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17161
17162 cx.notify();
17163 }
17164
17165 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17166 self.show_selection_menu
17167 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17168 }
17169
17170 fn start_git_blame(
17171 &mut self,
17172 user_triggered: bool,
17173 window: &mut Window,
17174 cx: &mut Context<Self>,
17175 ) {
17176 if let Some(project) = self.project.as_ref() {
17177 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17178 return;
17179 };
17180
17181 if buffer.read(cx).file().is_none() {
17182 return;
17183 }
17184
17185 let focused = self.focus_handle(cx).contains_focused(window, cx);
17186
17187 let project = project.clone();
17188 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17189 self.blame_subscription =
17190 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17191 self.blame = Some(blame);
17192 }
17193 }
17194
17195 fn toggle_git_blame_inline_internal(
17196 &mut self,
17197 user_triggered: bool,
17198 window: &mut Window,
17199 cx: &mut Context<Self>,
17200 ) {
17201 if self.git_blame_inline_enabled {
17202 self.git_blame_inline_enabled = false;
17203 self.show_git_blame_inline = false;
17204 self.show_git_blame_inline_delay_task.take();
17205 } else {
17206 self.git_blame_inline_enabled = true;
17207 self.start_git_blame_inline(user_triggered, window, cx);
17208 }
17209
17210 cx.notify();
17211 }
17212
17213 fn start_git_blame_inline(
17214 &mut self,
17215 user_triggered: bool,
17216 window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) {
17219 self.start_git_blame(user_triggered, window, cx);
17220
17221 if ProjectSettings::get_global(cx)
17222 .git
17223 .inline_blame_delay()
17224 .is_some()
17225 {
17226 self.start_inline_blame_timer(window, cx);
17227 } else {
17228 self.show_git_blame_inline = true
17229 }
17230 }
17231
17232 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17233 self.blame.as_ref()
17234 }
17235
17236 pub fn show_git_blame_gutter(&self) -> bool {
17237 self.show_git_blame_gutter
17238 }
17239
17240 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17241 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17242 }
17243
17244 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17245 self.show_git_blame_inline
17246 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17247 && !self.newest_selection_head_on_empty_line(cx)
17248 && self.has_blame_entries(cx)
17249 }
17250
17251 fn has_blame_entries(&self, cx: &App) -> bool {
17252 self.blame()
17253 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17254 }
17255
17256 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17257 let cursor_anchor = self.selections.newest_anchor().head();
17258
17259 let snapshot = self.buffer.read(cx).snapshot(cx);
17260 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17261
17262 snapshot.line_len(buffer_row) == 0
17263 }
17264
17265 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17266 let buffer_and_selection = maybe!({
17267 let selection = self.selections.newest::<Point>(cx);
17268 let selection_range = selection.range();
17269
17270 let multi_buffer = self.buffer().read(cx);
17271 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17272 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17273
17274 let (buffer, range, _) = if selection.reversed {
17275 buffer_ranges.first()
17276 } else {
17277 buffer_ranges.last()
17278 }?;
17279
17280 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17281 ..text::ToPoint::to_point(&range.end, &buffer).row;
17282 Some((
17283 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17284 selection,
17285 ))
17286 });
17287
17288 let Some((buffer, selection)) = buffer_and_selection else {
17289 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17290 };
17291
17292 let Some(project) = self.project.as_ref() else {
17293 return Task::ready(Err(anyhow!("editor does not have project")));
17294 };
17295
17296 project.update(cx, |project, cx| {
17297 project.get_permalink_to_line(&buffer, selection, cx)
17298 })
17299 }
17300
17301 pub fn copy_permalink_to_line(
17302 &mut self,
17303 _: &CopyPermalinkToLine,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 let permalink_task = self.get_permalink_to_line(cx);
17308 let workspace = self.workspace();
17309
17310 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17311 Ok(permalink) => {
17312 cx.update(|_, cx| {
17313 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17314 })
17315 .ok();
17316 }
17317 Err(err) => {
17318 let message = format!("Failed to copy permalink: {err}");
17319
17320 Err::<(), anyhow::Error>(err).log_err();
17321
17322 if let Some(workspace) = workspace {
17323 workspace
17324 .update_in(cx, |workspace, _, cx| {
17325 struct CopyPermalinkToLine;
17326
17327 workspace.show_toast(
17328 Toast::new(
17329 NotificationId::unique::<CopyPermalinkToLine>(),
17330 message,
17331 ),
17332 cx,
17333 )
17334 })
17335 .ok();
17336 }
17337 }
17338 })
17339 .detach();
17340 }
17341
17342 pub fn copy_file_location(
17343 &mut self,
17344 _: &CopyFileLocation,
17345 _: &mut Window,
17346 cx: &mut Context<Self>,
17347 ) {
17348 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17349 if let Some(file) = self.target_file(cx) {
17350 if let Some(path) = file.path().to_str() {
17351 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17352 }
17353 }
17354 }
17355
17356 pub fn open_permalink_to_line(
17357 &mut self,
17358 _: &OpenPermalinkToLine,
17359 window: &mut Window,
17360 cx: &mut Context<Self>,
17361 ) {
17362 let permalink_task = self.get_permalink_to_line(cx);
17363 let workspace = self.workspace();
17364
17365 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17366 Ok(permalink) => {
17367 cx.update(|_, cx| {
17368 cx.open_url(permalink.as_ref());
17369 })
17370 .ok();
17371 }
17372 Err(err) => {
17373 let message = format!("Failed to open permalink: {err}");
17374
17375 Err::<(), anyhow::Error>(err).log_err();
17376
17377 if let Some(workspace) = workspace {
17378 workspace
17379 .update(cx, |workspace, cx| {
17380 struct OpenPermalinkToLine;
17381
17382 workspace.show_toast(
17383 Toast::new(
17384 NotificationId::unique::<OpenPermalinkToLine>(),
17385 message,
17386 ),
17387 cx,
17388 )
17389 })
17390 .ok();
17391 }
17392 }
17393 })
17394 .detach();
17395 }
17396
17397 pub fn insert_uuid_v4(
17398 &mut self,
17399 _: &InsertUuidV4,
17400 window: &mut Window,
17401 cx: &mut Context<Self>,
17402 ) {
17403 self.insert_uuid(UuidVersion::V4, window, cx);
17404 }
17405
17406 pub fn insert_uuid_v7(
17407 &mut self,
17408 _: &InsertUuidV7,
17409 window: &mut Window,
17410 cx: &mut Context<Self>,
17411 ) {
17412 self.insert_uuid(UuidVersion::V7, window, cx);
17413 }
17414
17415 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17416 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17417 self.transact(window, cx, |this, window, cx| {
17418 let edits = this
17419 .selections
17420 .all::<Point>(cx)
17421 .into_iter()
17422 .map(|selection| {
17423 let uuid = match version {
17424 UuidVersion::V4 => uuid::Uuid::new_v4(),
17425 UuidVersion::V7 => uuid::Uuid::now_v7(),
17426 };
17427
17428 (selection.range(), uuid.to_string())
17429 });
17430 this.edit(edits, cx);
17431 this.refresh_inline_completion(true, false, window, cx);
17432 });
17433 }
17434
17435 pub fn open_selections_in_multibuffer(
17436 &mut self,
17437 _: &OpenSelectionsInMultibuffer,
17438 window: &mut Window,
17439 cx: &mut Context<Self>,
17440 ) {
17441 let multibuffer = self.buffer.read(cx);
17442
17443 let Some(buffer) = multibuffer.as_singleton() else {
17444 return;
17445 };
17446
17447 let Some(workspace) = self.workspace() else {
17448 return;
17449 };
17450
17451 let locations = self
17452 .selections
17453 .disjoint_anchors()
17454 .iter()
17455 .map(|range| Location {
17456 buffer: buffer.clone(),
17457 range: range.start.text_anchor..range.end.text_anchor,
17458 })
17459 .collect::<Vec<_>>();
17460
17461 let title = multibuffer.title(cx).to_string();
17462
17463 cx.spawn_in(window, async move |_, cx| {
17464 workspace.update_in(cx, |workspace, window, cx| {
17465 Self::open_locations_in_multibuffer(
17466 workspace,
17467 locations,
17468 format!("Selections for '{title}'"),
17469 false,
17470 MultibufferSelectionMode::All,
17471 window,
17472 cx,
17473 );
17474 })
17475 })
17476 .detach();
17477 }
17478
17479 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17480 /// last highlight added will be used.
17481 ///
17482 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17483 pub fn highlight_rows<T: 'static>(
17484 &mut self,
17485 range: Range<Anchor>,
17486 color: Hsla,
17487 options: RowHighlightOptions,
17488 cx: &mut Context<Self>,
17489 ) {
17490 let snapshot = self.buffer().read(cx).snapshot(cx);
17491 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17492 let ix = row_highlights.binary_search_by(|highlight| {
17493 Ordering::Equal
17494 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17495 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17496 });
17497
17498 if let Err(mut ix) = ix {
17499 let index = post_inc(&mut self.highlight_order);
17500
17501 // If this range intersects with the preceding highlight, then merge it with
17502 // the preceding highlight. Otherwise insert a new highlight.
17503 let mut merged = false;
17504 if ix > 0 {
17505 let prev_highlight = &mut row_highlights[ix - 1];
17506 if prev_highlight
17507 .range
17508 .end
17509 .cmp(&range.start, &snapshot)
17510 .is_ge()
17511 {
17512 ix -= 1;
17513 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17514 prev_highlight.range.end = range.end;
17515 }
17516 merged = true;
17517 prev_highlight.index = index;
17518 prev_highlight.color = color;
17519 prev_highlight.options = options;
17520 }
17521 }
17522
17523 if !merged {
17524 row_highlights.insert(
17525 ix,
17526 RowHighlight {
17527 range: range.clone(),
17528 index,
17529 color,
17530 options,
17531 type_id: TypeId::of::<T>(),
17532 },
17533 );
17534 }
17535
17536 // If any of the following highlights intersect with this one, merge them.
17537 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17538 let highlight = &row_highlights[ix];
17539 if next_highlight
17540 .range
17541 .start
17542 .cmp(&highlight.range.end, &snapshot)
17543 .is_le()
17544 {
17545 if next_highlight
17546 .range
17547 .end
17548 .cmp(&highlight.range.end, &snapshot)
17549 .is_gt()
17550 {
17551 row_highlights[ix].range.end = next_highlight.range.end;
17552 }
17553 row_highlights.remove(ix + 1);
17554 } else {
17555 break;
17556 }
17557 }
17558 }
17559 }
17560
17561 /// Remove any highlighted row ranges of the given type that intersect the
17562 /// given ranges.
17563 pub fn remove_highlighted_rows<T: 'static>(
17564 &mut self,
17565 ranges_to_remove: Vec<Range<Anchor>>,
17566 cx: &mut Context<Self>,
17567 ) {
17568 let snapshot = self.buffer().read(cx).snapshot(cx);
17569 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17570 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17571 row_highlights.retain(|highlight| {
17572 while let Some(range_to_remove) = ranges_to_remove.peek() {
17573 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17574 Ordering::Less | Ordering::Equal => {
17575 ranges_to_remove.next();
17576 }
17577 Ordering::Greater => {
17578 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17579 Ordering::Less | Ordering::Equal => {
17580 return false;
17581 }
17582 Ordering::Greater => break,
17583 }
17584 }
17585 }
17586 }
17587
17588 true
17589 })
17590 }
17591
17592 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17593 pub fn clear_row_highlights<T: 'static>(&mut self) {
17594 self.highlighted_rows.remove(&TypeId::of::<T>());
17595 }
17596
17597 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17598 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17599 self.highlighted_rows
17600 .get(&TypeId::of::<T>())
17601 .map_or(&[] as &[_], |vec| vec.as_slice())
17602 .iter()
17603 .map(|highlight| (highlight.range.clone(), highlight.color))
17604 }
17605
17606 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17607 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17608 /// Allows to ignore certain kinds of highlights.
17609 pub fn highlighted_display_rows(
17610 &self,
17611 window: &mut Window,
17612 cx: &mut App,
17613 ) -> BTreeMap<DisplayRow, LineHighlight> {
17614 let snapshot = self.snapshot(window, cx);
17615 let mut used_highlight_orders = HashMap::default();
17616 self.highlighted_rows
17617 .iter()
17618 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17619 .fold(
17620 BTreeMap::<DisplayRow, LineHighlight>::new(),
17621 |mut unique_rows, highlight| {
17622 let start = highlight.range.start.to_display_point(&snapshot);
17623 let end = highlight.range.end.to_display_point(&snapshot);
17624 let start_row = start.row().0;
17625 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17626 && end.column() == 0
17627 {
17628 end.row().0.saturating_sub(1)
17629 } else {
17630 end.row().0
17631 };
17632 for row in start_row..=end_row {
17633 let used_index =
17634 used_highlight_orders.entry(row).or_insert(highlight.index);
17635 if highlight.index >= *used_index {
17636 *used_index = highlight.index;
17637 unique_rows.insert(
17638 DisplayRow(row),
17639 LineHighlight {
17640 include_gutter: highlight.options.include_gutter,
17641 border: None,
17642 background: highlight.color.into(),
17643 type_id: Some(highlight.type_id),
17644 },
17645 );
17646 }
17647 }
17648 unique_rows
17649 },
17650 )
17651 }
17652
17653 pub fn highlighted_display_row_for_autoscroll(
17654 &self,
17655 snapshot: &DisplaySnapshot,
17656 ) -> Option<DisplayRow> {
17657 self.highlighted_rows
17658 .values()
17659 .flat_map(|highlighted_rows| highlighted_rows.iter())
17660 .filter_map(|highlight| {
17661 if highlight.options.autoscroll {
17662 Some(highlight.range.start.to_display_point(snapshot).row())
17663 } else {
17664 None
17665 }
17666 })
17667 .min()
17668 }
17669
17670 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17671 self.highlight_background::<SearchWithinRange>(
17672 ranges,
17673 |colors| colors.editor_document_highlight_read_background,
17674 cx,
17675 )
17676 }
17677
17678 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17679 self.breadcrumb_header = Some(new_header);
17680 }
17681
17682 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17683 self.clear_background_highlights::<SearchWithinRange>(cx);
17684 }
17685
17686 pub fn highlight_background<T: 'static>(
17687 &mut self,
17688 ranges: &[Range<Anchor>],
17689 color_fetcher: fn(&ThemeColors) -> Hsla,
17690 cx: &mut Context<Self>,
17691 ) {
17692 self.background_highlights
17693 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17694 self.scrollbar_marker_state.dirty = true;
17695 cx.notify();
17696 }
17697
17698 pub fn clear_background_highlights<T: 'static>(
17699 &mut self,
17700 cx: &mut Context<Self>,
17701 ) -> Option<BackgroundHighlight> {
17702 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17703 if !text_highlights.1.is_empty() {
17704 self.scrollbar_marker_state.dirty = true;
17705 cx.notify();
17706 }
17707 Some(text_highlights)
17708 }
17709
17710 pub fn highlight_gutter<T: 'static>(
17711 &mut self,
17712 ranges: &[Range<Anchor>],
17713 color_fetcher: fn(&App) -> Hsla,
17714 cx: &mut Context<Self>,
17715 ) {
17716 self.gutter_highlights
17717 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17718 cx.notify();
17719 }
17720
17721 pub fn clear_gutter_highlights<T: 'static>(
17722 &mut self,
17723 cx: &mut Context<Self>,
17724 ) -> Option<GutterHighlight> {
17725 cx.notify();
17726 self.gutter_highlights.remove(&TypeId::of::<T>())
17727 }
17728
17729 #[cfg(feature = "test-support")]
17730 pub fn all_text_background_highlights(
17731 &self,
17732 window: &mut Window,
17733 cx: &mut Context<Self>,
17734 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17735 let snapshot = self.snapshot(window, cx);
17736 let buffer = &snapshot.buffer_snapshot;
17737 let start = buffer.anchor_before(0);
17738 let end = buffer.anchor_after(buffer.len());
17739 let theme = cx.theme().colors();
17740 self.background_highlights_in_range(start..end, &snapshot, theme)
17741 }
17742
17743 #[cfg(feature = "test-support")]
17744 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17745 let snapshot = self.buffer().read(cx).snapshot(cx);
17746
17747 let highlights = self
17748 .background_highlights
17749 .get(&TypeId::of::<items::BufferSearchHighlights>());
17750
17751 if let Some((_color, ranges)) = highlights {
17752 ranges
17753 .iter()
17754 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17755 .collect_vec()
17756 } else {
17757 vec![]
17758 }
17759 }
17760
17761 fn document_highlights_for_position<'a>(
17762 &'a self,
17763 position: Anchor,
17764 buffer: &'a MultiBufferSnapshot,
17765 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17766 let read_highlights = self
17767 .background_highlights
17768 .get(&TypeId::of::<DocumentHighlightRead>())
17769 .map(|h| &h.1);
17770 let write_highlights = self
17771 .background_highlights
17772 .get(&TypeId::of::<DocumentHighlightWrite>())
17773 .map(|h| &h.1);
17774 let left_position = position.bias_left(buffer);
17775 let right_position = position.bias_right(buffer);
17776 read_highlights
17777 .into_iter()
17778 .chain(write_highlights)
17779 .flat_map(move |ranges| {
17780 let start_ix = match ranges.binary_search_by(|probe| {
17781 let cmp = probe.end.cmp(&left_position, buffer);
17782 if cmp.is_ge() {
17783 Ordering::Greater
17784 } else {
17785 Ordering::Less
17786 }
17787 }) {
17788 Ok(i) | Err(i) => i,
17789 };
17790
17791 ranges[start_ix..]
17792 .iter()
17793 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17794 })
17795 }
17796
17797 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17798 self.background_highlights
17799 .get(&TypeId::of::<T>())
17800 .map_or(false, |(_, highlights)| !highlights.is_empty())
17801 }
17802
17803 pub fn background_highlights_in_range(
17804 &self,
17805 search_range: Range<Anchor>,
17806 display_snapshot: &DisplaySnapshot,
17807 theme: &ThemeColors,
17808 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17809 let mut results = Vec::new();
17810 for (color_fetcher, ranges) in self.background_highlights.values() {
17811 let color = color_fetcher(theme);
17812 let start_ix = match ranges.binary_search_by(|probe| {
17813 let cmp = probe
17814 .end
17815 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17816 if cmp.is_gt() {
17817 Ordering::Greater
17818 } else {
17819 Ordering::Less
17820 }
17821 }) {
17822 Ok(i) | Err(i) => i,
17823 };
17824 for range in &ranges[start_ix..] {
17825 if range
17826 .start
17827 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17828 .is_ge()
17829 {
17830 break;
17831 }
17832
17833 let start = range.start.to_display_point(display_snapshot);
17834 let end = range.end.to_display_point(display_snapshot);
17835 results.push((start..end, color))
17836 }
17837 }
17838 results
17839 }
17840
17841 pub fn background_highlight_row_ranges<T: 'static>(
17842 &self,
17843 search_range: Range<Anchor>,
17844 display_snapshot: &DisplaySnapshot,
17845 count: usize,
17846 ) -> Vec<RangeInclusive<DisplayPoint>> {
17847 let mut results = Vec::new();
17848 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17849 return vec![];
17850 };
17851
17852 let start_ix = match ranges.binary_search_by(|probe| {
17853 let cmp = probe
17854 .end
17855 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17856 if cmp.is_gt() {
17857 Ordering::Greater
17858 } else {
17859 Ordering::Less
17860 }
17861 }) {
17862 Ok(i) | Err(i) => i,
17863 };
17864 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17865 if let (Some(start_display), Some(end_display)) = (start, end) {
17866 results.push(
17867 start_display.to_display_point(display_snapshot)
17868 ..=end_display.to_display_point(display_snapshot),
17869 );
17870 }
17871 };
17872 let mut start_row: Option<Point> = None;
17873 let mut end_row: Option<Point> = None;
17874 if ranges.len() > count {
17875 return Vec::new();
17876 }
17877 for range in &ranges[start_ix..] {
17878 if range
17879 .start
17880 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17881 .is_ge()
17882 {
17883 break;
17884 }
17885 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17886 if let Some(current_row) = &end_row {
17887 if end.row == current_row.row {
17888 continue;
17889 }
17890 }
17891 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17892 if start_row.is_none() {
17893 assert_eq!(end_row, None);
17894 start_row = Some(start);
17895 end_row = Some(end);
17896 continue;
17897 }
17898 if let Some(current_end) = end_row.as_mut() {
17899 if start.row > current_end.row + 1 {
17900 push_region(start_row, end_row);
17901 start_row = Some(start);
17902 end_row = Some(end);
17903 } else {
17904 // Merge two hunks.
17905 *current_end = end;
17906 }
17907 } else {
17908 unreachable!();
17909 }
17910 }
17911 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17912 push_region(start_row, end_row);
17913 results
17914 }
17915
17916 pub fn gutter_highlights_in_range(
17917 &self,
17918 search_range: Range<Anchor>,
17919 display_snapshot: &DisplaySnapshot,
17920 cx: &App,
17921 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17922 let mut results = Vec::new();
17923 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17924 let color = color_fetcher(cx);
17925 let start_ix = match ranges.binary_search_by(|probe| {
17926 let cmp = probe
17927 .end
17928 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17929 if cmp.is_gt() {
17930 Ordering::Greater
17931 } else {
17932 Ordering::Less
17933 }
17934 }) {
17935 Ok(i) | Err(i) => i,
17936 };
17937 for range in &ranges[start_ix..] {
17938 if range
17939 .start
17940 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17941 .is_ge()
17942 {
17943 break;
17944 }
17945
17946 let start = range.start.to_display_point(display_snapshot);
17947 let end = range.end.to_display_point(display_snapshot);
17948 results.push((start..end, color))
17949 }
17950 }
17951 results
17952 }
17953
17954 /// Get the text ranges corresponding to the redaction query
17955 pub fn redacted_ranges(
17956 &self,
17957 search_range: Range<Anchor>,
17958 display_snapshot: &DisplaySnapshot,
17959 cx: &App,
17960 ) -> Vec<Range<DisplayPoint>> {
17961 display_snapshot
17962 .buffer_snapshot
17963 .redacted_ranges(search_range, |file| {
17964 if let Some(file) = file {
17965 file.is_private()
17966 && EditorSettings::get(
17967 Some(SettingsLocation {
17968 worktree_id: file.worktree_id(cx),
17969 path: file.path().as_ref(),
17970 }),
17971 cx,
17972 )
17973 .redact_private_values
17974 } else {
17975 false
17976 }
17977 })
17978 .map(|range| {
17979 range.start.to_display_point(display_snapshot)
17980 ..range.end.to_display_point(display_snapshot)
17981 })
17982 .collect()
17983 }
17984
17985 pub fn highlight_text<T: 'static>(
17986 &mut self,
17987 ranges: Vec<Range<Anchor>>,
17988 style: HighlightStyle,
17989 cx: &mut Context<Self>,
17990 ) {
17991 self.display_map.update(cx, |map, _| {
17992 map.highlight_text(TypeId::of::<T>(), ranges, style)
17993 });
17994 cx.notify();
17995 }
17996
17997 pub(crate) fn highlight_inlays<T: 'static>(
17998 &mut self,
17999 highlights: Vec<InlayHighlight>,
18000 style: HighlightStyle,
18001 cx: &mut Context<Self>,
18002 ) {
18003 self.display_map.update(cx, |map, _| {
18004 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18005 });
18006 cx.notify();
18007 }
18008
18009 pub fn text_highlights<'a, T: 'static>(
18010 &'a self,
18011 cx: &'a App,
18012 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18013 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18014 }
18015
18016 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18017 let cleared = self
18018 .display_map
18019 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18020 if cleared {
18021 cx.notify();
18022 }
18023 }
18024
18025 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18026 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18027 && self.focus_handle.is_focused(window)
18028 }
18029
18030 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18031 self.show_cursor_when_unfocused = is_enabled;
18032 cx.notify();
18033 }
18034
18035 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18036 cx.notify();
18037 }
18038
18039 fn on_debug_session_event(
18040 &mut self,
18041 _session: Entity<Session>,
18042 event: &SessionEvent,
18043 cx: &mut Context<Self>,
18044 ) {
18045 match event {
18046 SessionEvent::InvalidateInlineValue => {
18047 self.refresh_inline_values(cx);
18048 }
18049 _ => {}
18050 }
18051 }
18052
18053 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18054 let Some(project) = self.project.clone() else {
18055 return;
18056 };
18057
18058 if !self.inline_value_cache.enabled {
18059 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18060 self.splice_inlays(&inlays, Vec::new(), cx);
18061 return;
18062 }
18063
18064 let current_execution_position = self
18065 .highlighted_rows
18066 .get(&TypeId::of::<ActiveDebugLine>())
18067 .and_then(|lines| lines.last().map(|line| line.range.start));
18068
18069 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18070 let inline_values = editor
18071 .update(cx, |editor, cx| {
18072 let Some(current_execution_position) = current_execution_position else {
18073 return Some(Task::ready(Ok(Vec::new())));
18074 };
18075
18076 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18077 let snapshot = buffer.snapshot(cx);
18078
18079 let excerpt = snapshot.excerpt_containing(
18080 current_execution_position..current_execution_position,
18081 )?;
18082
18083 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18084 })?;
18085
18086 let range =
18087 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18088
18089 project.inline_values(buffer, range, cx)
18090 })
18091 .ok()
18092 .flatten()?
18093 .await
18094 .context("refreshing debugger inlays")
18095 .log_err()?;
18096
18097 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18098
18099 for (buffer_id, inline_value) in inline_values
18100 .into_iter()
18101 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18102 {
18103 buffer_inline_values
18104 .entry(buffer_id)
18105 .or_default()
18106 .push(inline_value);
18107 }
18108
18109 editor
18110 .update(cx, |editor, cx| {
18111 let snapshot = editor.buffer.read(cx).snapshot(cx);
18112 let mut new_inlays = Vec::default();
18113
18114 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18115 let buffer_id = buffer_snapshot.remote_id();
18116 buffer_inline_values
18117 .get(&buffer_id)
18118 .into_iter()
18119 .flatten()
18120 .for_each(|hint| {
18121 let inlay = Inlay::debugger_hint(
18122 post_inc(&mut editor.next_inlay_id),
18123 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18124 hint.text(),
18125 );
18126
18127 new_inlays.push(inlay);
18128 });
18129 }
18130
18131 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18132 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18133
18134 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18135 })
18136 .ok()?;
18137 Some(())
18138 });
18139 }
18140
18141 fn on_buffer_event(
18142 &mut self,
18143 multibuffer: &Entity<MultiBuffer>,
18144 event: &multi_buffer::Event,
18145 window: &mut Window,
18146 cx: &mut Context<Self>,
18147 ) {
18148 match event {
18149 multi_buffer::Event::Edited {
18150 singleton_buffer_edited,
18151 edited_buffer: buffer_edited,
18152 } => {
18153 self.scrollbar_marker_state.dirty = true;
18154 self.active_indent_guides_state.dirty = true;
18155 self.refresh_active_diagnostics(cx);
18156 self.refresh_code_actions(window, cx);
18157 self.refresh_selected_text_highlights(true, window, cx);
18158 refresh_matching_bracket_highlights(self, window, cx);
18159 if self.has_active_inline_completion() {
18160 self.update_visible_inline_completion(window, cx);
18161 }
18162 if let Some(buffer) = buffer_edited {
18163 let buffer_id = buffer.read(cx).remote_id();
18164 if !self.registered_buffers.contains_key(&buffer_id) {
18165 if let Some(project) = self.project.as_ref() {
18166 project.update(cx, |project, cx| {
18167 self.registered_buffers.insert(
18168 buffer_id,
18169 project.register_buffer_with_language_servers(&buffer, cx),
18170 );
18171 })
18172 }
18173 }
18174 }
18175 cx.emit(EditorEvent::BufferEdited);
18176 cx.emit(SearchEvent::MatchesInvalidated);
18177 if *singleton_buffer_edited {
18178 if let Some(project) = &self.project {
18179 #[allow(clippy::mutable_key_type)]
18180 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18181 multibuffer
18182 .all_buffers()
18183 .into_iter()
18184 .filter_map(|buffer| {
18185 buffer.update(cx, |buffer, cx| {
18186 let language = buffer.language()?;
18187 let should_discard = project.update(cx, |project, cx| {
18188 project.is_local()
18189 && !project.has_language_servers_for(buffer, cx)
18190 });
18191 should_discard.not().then_some(language.clone())
18192 })
18193 })
18194 .collect::<HashSet<_>>()
18195 });
18196 if !languages_affected.is_empty() {
18197 self.refresh_inlay_hints(
18198 InlayHintRefreshReason::BufferEdited(languages_affected),
18199 cx,
18200 );
18201 }
18202 }
18203 }
18204
18205 let Some(project) = &self.project else { return };
18206 let (telemetry, is_via_ssh) = {
18207 let project = project.read(cx);
18208 let telemetry = project.client().telemetry().clone();
18209 let is_via_ssh = project.is_via_ssh();
18210 (telemetry, is_via_ssh)
18211 };
18212 refresh_linked_ranges(self, window, cx);
18213 telemetry.log_edit_event("editor", is_via_ssh);
18214 }
18215 multi_buffer::Event::ExcerptsAdded {
18216 buffer,
18217 predecessor,
18218 excerpts,
18219 } => {
18220 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18221 let buffer_id = buffer.read(cx).remote_id();
18222 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18223 if let Some(project) = &self.project {
18224 update_uncommitted_diff_for_buffer(
18225 cx.entity(),
18226 project,
18227 [buffer.clone()],
18228 self.buffer.clone(),
18229 cx,
18230 )
18231 .detach();
18232 }
18233 }
18234 cx.emit(EditorEvent::ExcerptsAdded {
18235 buffer: buffer.clone(),
18236 predecessor: *predecessor,
18237 excerpts: excerpts.clone(),
18238 });
18239 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18240 }
18241 multi_buffer::Event::ExcerptsRemoved {
18242 ids,
18243 removed_buffer_ids,
18244 } => {
18245 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18246 let buffer = self.buffer.read(cx);
18247 self.registered_buffers
18248 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18249 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18250 cx.emit(EditorEvent::ExcerptsRemoved {
18251 ids: ids.clone(),
18252 removed_buffer_ids: removed_buffer_ids.clone(),
18253 })
18254 }
18255 multi_buffer::Event::ExcerptsEdited {
18256 excerpt_ids,
18257 buffer_ids,
18258 } => {
18259 self.display_map.update(cx, |map, cx| {
18260 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18261 });
18262 cx.emit(EditorEvent::ExcerptsEdited {
18263 ids: excerpt_ids.clone(),
18264 })
18265 }
18266 multi_buffer::Event::ExcerptsExpanded { ids } => {
18267 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18268 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18269 }
18270 multi_buffer::Event::Reparsed(buffer_id) => {
18271 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18272 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18273
18274 cx.emit(EditorEvent::Reparsed(*buffer_id));
18275 }
18276 multi_buffer::Event::DiffHunksToggled => {
18277 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18278 }
18279 multi_buffer::Event::LanguageChanged(buffer_id) => {
18280 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18281 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18282 cx.emit(EditorEvent::Reparsed(*buffer_id));
18283 cx.notify();
18284 }
18285 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18286 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18287 multi_buffer::Event::FileHandleChanged
18288 | multi_buffer::Event::Reloaded
18289 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18290 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18291 multi_buffer::Event::DiagnosticsUpdated => {
18292 self.refresh_active_diagnostics(cx);
18293 self.refresh_inline_diagnostics(true, window, cx);
18294 self.scrollbar_marker_state.dirty = true;
18295 cx.notify();
18296 }
18297 _ => {}
18298 };
18299 }
18300
18301 pub fn start_temporary_diff_override(&mut self) {
18302 self.load_diff_task.take();
18303 self.temporary_diff_override = true;
18304 }
18305
18306 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18307 self.temporary_diff_override = false;
18308 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18309 self.buffer.update(cx, |buffer, cx| {
18310 buffer.set_all_diff_hunks_collapsed(cx);
18311 });
18312
18313 if let Some(project) = self.project.clone() {
18314 self.load_diff_task = Some(
18315 update_uncommitted_diff_for_buffer(
18316 cx.entity(),
18317 &project,
18318 self.buffer.read(cx).all_buffers(),
18319 self.buffer.clone(),
18320 cx,
18321 )
18322 .shared(),
18323 );
18324 }
18325 }
18326
18327 fn on_display_map_changed(
18328 &mut self,
18329 _: Entity<DisplayMap>,
18330 _: &mut Window,
18331 cx: &mut Context<Self>,
18332 ) {
18333 cx.notify();
18334 }
18335
18336 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18337 let new_severity = if self.diagnostics_enabled() {
18338 EditorSettings::get_global(cx)
18339 .diagnostics_max_severity
18340 .unwrap_or(DiagnosticSeverity::Hint)
18341 } else {
18342 DiagnosticSeverity::Off
18343 };
18344 self.set_max_diagnostics_severity(new_severity, cx);
18345 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18346 self.update_edit_prediction_settings(cx);
18347 self.refresh_inline_completion(true, false, window, cx);
18348 self.refresh_inlay_hints(
18349 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18350 self.selections.newest_anchor().head(),
18351 &self.buffer.read(cx).snapshot(cx),
18352 cx,
18353 )),
18354 cx,
18355 );
18356
18357 let old_cursor_shape = self.cursor_shape;
18358
18359 {
18360 let editor_settings = EditorSettings::get_global(cx);
18361 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18362 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18363 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18364 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18365 }
18366
18367 if old_cursor_shape != self.cursor_shape {
18368 cx.emit(EditorEvent::CursorShapeChanged);
18369 }
18370
18371 let project_settings = ProjectSettings::get_global(cx);
18372 self.serialize_dirty_buffers =
18373 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18374
18375 if self.mode.is_full() {
18376 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18377 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18378 if self.show_inline_diagnostics != show_inline_diagnostics {
18379 self.show_inline_diagnostics = show_inline_diagnostics;
18380 self.refresh_inline_diagnostics(false, window, cx);
18381 }
18382
18383 if self.git_blame_inline_enabled != inline_blame_enabled {
18384 self.toggle_git_blame_inline_internal(false, window, cx);
18385 }
18386
18387 let minimap_settings = EditorSettings::get_global(cx).minimap;
18388 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18389 self.set_minimap_visibility(
18390 self.minimap_visibility.toggle_visibility(),
18391 window,
18392 cx,
18393 );
18394 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18395 minimap_entity.update(cx, |minimap_editor, cx| {
18396 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18397 })
18398 }
18399 }
18400
18401 cx.notify();
18402 }
18403
18404 pub fn set_searchable(&mut self, searchable: bool) {
18405 self.searchable = searchable;
18406 }
18407
18408 pub fn searchable(&self) -> bool {
18409 self.searchable
18410 }
18411
18412 fn open_proposed_changes_editor(
18413 &mut self,
18414 _: &OpenProposedChangesEditor,
18415 window: &mut Window,
18416 cx: &mut Context<Self>,
18417 ) {
18418 let Some(workspace) = self.workspace() else {
18419 cx.propagate();
18420 return;
18421 };
18422
18423 let selections = self.selections.all::<usize>(cx);
18424 let multi_buffer = self.buffer.read(cx);
18425 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18426 let mut new_selections_by_buffer = HashMap::default();
18427 for selection in selections {
18428 for (buffer, range, _) in
18429 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18430 {
18431 let mut range = range.to_point(buffer);
18432 range.start.column = 0;
18433 range.end.column = buffer.line_len(range.end.row);
18434 new_selections_by_buffer
18435 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18436 .or_insert(Vec::new())
18437 .push(range)
18438 }
18439 }
18440
18441 let proposed_changes_buffers = new_selections_by_buffer
18442 .into_iter()
18443 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18444 .collect::<Vec<_>>();
18445 let proposed_changes_editor = cx.new(|cx| {
18446 ProposedChangesEditor::new(
18447 "Proposed changes",
18448 proposed_changes_buffers,
18449 self.project.clone(),
18450 window,
18451 cx,
18452 )
18453 });
18454
18455 window.defer(cx, move |window, cx| {
18456 workspace.update(cx, |workspace, cx| {
18457 workspace.active_pane().update(cx, |pane, cx| {
18458 pane.add_item(
18459 Box::new(proposed_changes_editor),
18460 true,
18461 true,
18462 None,
18463 window,
18464 cx,
18465 );
18466 });
18467 });
18468 });
18469 }
18470
18471 pub fn open_excerpts_in_split(
18472 &mut self,
18473 _: &OpenExcerptsSplit,
18474 window: &mut Window,
18475 cx: &mut Context<Self>,
18476 ) {
18477 self.open_excerpts_common(None, true, window, cx)
18478 }
18479
18480 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18481 self.open_excerpts_common(None, false, window, cx)
18482 }
18483
18484 fn open_excerpts_common(
18485 &mut self,
18486 jump_data: Option<JumpData>,
18487 split: bool,
18488 window: &mut Window,
18489 cx: &mut Context<Self>,
18490 ) {
18491 let Some(workspace) = self.workspace() else {
18492 cx.propagate();
18493 return;
18494 };
18495
18496 if self.buffer.read(cx).is_singleton() {
18497 cx.propagate();
18498 return;
18499 }
18500
18501 let mut new_selections_by_buffer = HashMap::default();
18502 match &jump_data {
18503 Some(JumpData::MultiBufferPoint {
18504 excerpt_id,
18505 position,
18506 anchor,
18507 line_offset_from_top,
18508 }) => {
18509 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18510 if let Some(buffer) = multi_buffer_snapshot
18511 .buffer_id_for_excerpt(*excerpt_id)
18512 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18513 {
18514 let buffer_snapshot = buffer.read(cx).snapshot();
18515 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18516 language::ToPoint::to_point(anchor, &buffer_snapshot)
18517 } else {
18518 buffer_snapshot.clip_point(*position, Bias::Left)
18519 };
18520 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18521 new_selections_by_buffer.insert(
18522 buffer,
18523 (
18524 vec![jump_to_offset..jump_to_offset],
18525 Some(*line_offset_from_top),
18526 ),
18527 );
18528 }
18529 }
18530 Some(JumpData::MultiBufferRow {
18531 row,
18532 line_offset_from_top,
18533 }) => {
18534 let point = MultiBufferPoint::new(row.0, 0);
18535 if let Some((buffer, buffer_point, _)) =
18536 self.buffer.read(cx).point_to_buffer_point(point, cx)
18537 {
18538 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18539 new_selections_by_buffer
18540 .entry(buffer)
18541 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18542 .0
18543 .push(buffer_offset..buffer_offset)
18544 }
18545 }
18546 None => {
18547 let selections = self.selections.all::<usize>(cx);
18548 let multi_buffer = self.buffer.read(cx);
18549 for selection in selections {
18550 for (snapshot, range, _, anchor) in multi_buffer
18551 .snapshot(cx)
18552 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18553 {
18554 if let Some(anchor) = anchor {
18555 // selection is in a deleted hunk
18556 let Some(buffer_id) = anchor.buffer_id else {
18557 continue;
18558 };
18559 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18560 continue;
18561 };
18562 let offset = text::ToOffset::to_offset(
18563 &anchor.text_anchor,
18564 &buffer_handle.read(cx).snapshot(),
18565 );
18566 let range = offset..offset;
18567 new_selections_by_buffer
18568 .entry(buffer_handle)
18569 .or_insert((Vec::new(), None))
18570 .0
18571 .push(range)
18572 } else {
18573 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18574 else {
18575 continue;
18576 };
18577 new_selections_by_buffer
18578 .entry(buffer_handle)
18579 .or_insert((Vec::new(), None))
18580 .0
18581 .push(range)
18582 }
18583 }
18584 }
18585 }
18586 }
18587
18588 new_selections_by_buffer
18589 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18590
18591 if new_selections_by_buffer.is_empty() {
18592 return;
18593 }
18594
18595 // We defer the pane interaction because we ourselves are a workspace item
18596 // and activating a new item causes the pane to call a method on us reentrantly,
18597 // which panics if we're on the stack.
18598 window.defer(cx, move |window, cx| {
18599 workspace.update(cx, |workspace, cx| {
18600 let pane = if split {
18601 workspace.adjacent_pane(window, cx)
18602 } else {
18603 workspace.active_pane().clone()
18604 };
18605
18606 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18607 let editor = buffer
18608 .read(cx)
18609 .file()
18610 .is_none()
18611 .then(|| {
18612 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18613 // so `workspace.open_project_item` will never find them, always opening a new editor.
18614 // Instead, we try to activate the existing editor in the pane first.
18615 let (editor, pane_item_index) =
18616 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18617 let editor = item.downcast::<Editor>()?;
18618 let singleton_buffer =
18619 editor.read(cx).buffer().read(cx).as_singleton()?;
18620 if singleton_buffer == buffer {
18621 Some((editor, i))
18622 } else {
18623 None
18624 }
18625 })?;
18626 pane.update(cx, |pane, cx| {
18627 pane.activate_item(pane_item_index, true, true, window, cx)
18628 });
18629 Some(editor)
18630 })
18631 .flatten()
18632 .unwrap_or_else(|| {
18633 workspace.open_project_item::<Self>(
18634 pane.clone(),
18635 buffer,
18636 true,
18637 true,
18638 window,
18639 cx,
18640 )
18641 });
18642
18643 editor.update(cx, |editor, cx| {
18644 let autoscroll = match scroll_offset {
18645 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18646 None => Autoscroll::newest(),
18647 };
18648 let nav_history = editor.nav_history.take();
18649 editor.change_selections(Some(autoscroll), window, cx, |s| {
18650 s.select_ranges(ranges);
18651 });
18652 editor.nav_history = nav_history;
18653 });
18654 }
18655 })
18656 });
18657 }
18658
18659 // For now, don't allow opening excerpts in buffers that aren't backed by
18660 // regular project files.
18661 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18662 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18663 }
18664
18665 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18666 let snapshot = self.buffer.read(cx).read(cx);
18667 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18668 Some(
18669 ranges
18670 .iter()
18671 .map(move |range| {
18672 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18673 })
18674 .collect(),
18675 )
18676 }
18677
18678 fn selection_replacement_ranges(
18679 &self,
18680 range: Range<OffsetUtf16>,
18681 cx: &mut App,
18682 ) -> Vec<Range<OffsetUtf16>> {
18683 let selections = self.selections.all::<OffsetUtf16>(cx);
18684 let newest_selection = selections
18685 .iter()
18686 .max_by_key(|selection| selection.id)
18687 .unwrap();
18688 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18689 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18690 let snapshot = self.buffer.read(cx).read(cx);
18691 selections
18692 .into_iter()
18693 .map(|mut selection| {
18694 selection.start.0 =
18695 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18696 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18697 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18698 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18699 })
18700 .collect()
18701 }
18702
18703 fn report_editor_event(
18704 &self,
18705 event_type: &'static str,
18706 file_extension: Option<String>,
18707 cx: &App,
18708 ) {
18709 if cfg!(any(test, feature = "test-support")) {
18710 return;
18711 }
18712
18713 let Some(project) = &self.project else { return };
18714
18715 // If None, we are in a file without an extension
18716 let file = self
18717 .buffer
18718 .read(cx)
18719 .as_singleton()
18720 .and_then(|b| b.read(cx).file());
18721 let file_extension = file_extension.or(file
18722 .as_ref()
18723 .and_then(|file| Path::new(file.file_name(cx)).extension())
18724 .and_then(|e| e.to_str())
18725 .map(|a| a.to_string()));
18726
18727 let vim_mode = vim_enabled(cx);
18728
18729 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18730 let copilot_enabled = edit_predictions_provider
18731 == language::language_settings::EditPredictionProvider::Copilot;
18732 let copilot_enabled_for_language = self
18733 .buffer
18734 .read(cx)
18735 .language_settings(cx)
18736 .show_edit_predictions;
18737
18738 let project = project.read(cx);
18739 telemetry::event!(
18740 event_type,
18741 file_extension,
18742 vim_mode,
18743 copilot_enabled,
18744 copilot_enabled_for_language,
18745 edit_predictions_provider,
18746 is_via_ssh = project.is_via_ssh(),
18747 );
18748 }
18749
18750 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18751 /// with each line being an array of {text, highlight} objects.
18752 fn copy_highlight_json(
18753 &mut self,
18754 _: &CopyHighlightJson,
18755 window: &mut Window,
18756 cx: &mut Context<Self>,
18757 ) {
18758 #[derive(Serialize)]
18759 struct Chunk<'a> {
18760 text: String,
18761 highlight: Option<&'a str>,
18762 }
18763
18764 let snapshot = self.buffer.read(cx).snapshot(cx);
18765 let range = self
18766 .selected_text_range(false, window, cx)
18767 .and_then(|selection| {
18768 if selection.range.is_empty() {
18769 None
18770 } else {
18771 Some(selection.range)
18772 }
18773 })
18774 .unwrap_or_else(|| 0..snapshot.len());
18775
18776 let chunks = snapshot.chunks(range, true);
18777 let mut lines = Vec::new();
18778 let mut line: VecDeque<Chunk> = VecDeque::new();
18779
18780 let Some(style) = self.style.as_ref() else {
18781 return;
18782 };
18783
18784 for chunk in chunks {
18785 let highlight = chunk
18786 .syntax_highlight_id
18787 .and_then(|id| id.name(&style.syntax));
18788 let mut chunk_lines = chunk.text.split('\n').peekable();
18789 while let Some(text) = chunk_lines.next() {
18790 let mut merged_with_last_token = false;
18791 if let Some(last_token) = line.back_mut() {
18792 if last_token.highlight == highlight {
18793 last_token.text.push_str(text);
18794 merged_with_last_token = true;
18795 }
18796 }
18797
18798 if !merged_with_last_token {
18799 line.push_back(Chunk {
18800 text: text.into(),
18801 highlight,
18802 });
18803 }
18804
18805 if chunk_lines.peek().is_some() {
18806 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18807 line.pop_front();
18808 }
18809 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18810 line.pop_back();
18811 }
18812
18813 lines.push(mem::take(&mut line));
18814 }
18815 }
18816 }
18817
18818 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18819 return;
18820 };
18821 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18822 }
18823
18824 pub fn open_context_menu(
18825 &mut self,
18826 _: &OpenContextMenu,
18827 window: &mut Window,
18828 cx: &mut Context<Self>,
18829 ) {
18830 self.request_autoscroll(Autoscroll::newest(), cx);
18831 let position = self.selections.newest_display(cx).start;
18832 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18833 }
18834
18835 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18836 &self.inlay_hint_cache
18837 }
18838
18839 pub fn replay_insert_event(
18840 &mut self,
18841 text: &str,
18842 relative_utf16_range: Option<Range<isize>>,
18843 window: &mut Window,
18844 cx: &mut Context<Self>,
18845 ) {
18846 if !self.input_enabled {
18847 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18848 return;
18849 }
18850 if let Some(relative_utf16_range) = relative_utf16_range {
18851 let selections = self.selections.all::<OffsetUtf16>(cx);
18852 self.change_selections(None, window, cx, |s| {
18853 let new_ranges = selections.into_iter().map(|range| {
18854 let start = OffsetUtf16(
18855 range
18856 .head()
18857 .0
18858 .saturating_add_signed(relative_utf16_range.start),
18859 );
18860 let end = OffsetUtf16(
18861 range
18862 .head()
18863 .0
18864 .saturating_add_signed(relative_utf16_range.end),
18865 );
18866 start..end
18867 });
18868 s.select_ranges(new_ranges);
18869 });
18870 }
18871
18872 self.handle_input(text, window, cx);
18873 }
18874
18875 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18876 let Some(provider) = self.semantics_provider.as_ref() else {
18877 return false;
18878 };
18879
18880 let mut supports = false;
18881 self.buffer().update(cx, |this, cx| {
18882 this.for_each_buffer(|buffer| {
18883 supports |= provider.supports_inlay_hints(buffer, cx);
18884 });
18885 });
18886
18887 supports
18888 }
18889
18890 pub fn is_focused(&self, window: &Window) -> bool {
18891 self.focus_handle.is_focused(window)
18892 }
18893
18894 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18895 cx.emit(EditorEvent::Focused);
18896
18897 if let Some(descendant) = self
18898 .last_focused_descendant
18899 .take()
18900 .and_then(|descendant| descendant.upgrade())
18901 {
18902 window.focus(&descendant);
18903 } else {
18904 if let Some(blame) = self.blame.as_ref() {
18905 blame.update(cx, GitBlame::focus)
18906 }
18907
18908 self.blink_manager.update(cx, BlinkManager::enable);
18909 self.show_cursor_names(window, cx);
18910 self.buffer.update(cx, |buffer, cx| {
18911 buffer.finalize_last_transaction(cx);
18912 if self.leader_id.is_none() {
18913 buffer.set_active_selections(
18914 &self.selections.disjoint_anchors(),
18915 self.selections.line_mode,
18916 self.cursor_shape,
18917 cx,
18918 );
18919 }
18920 });
18921 }
18922 }
18923
18924 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18925 cx.emit(EditorEvent::FocusedIn)
18926 }
18927
18928 fn handle_focus_out(
18929 &mut self,
18930 event: FocusOutEvent,
18931 _window: &mut Window,
18932 cx: &mut Context<Self>,
18933 ) {
18934 if event.blurred != self.focus_handle {
18935 self.last_focused_descendant = Some(event.blurred);
18936 }
18937 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18938 }
18939
18940 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18941 self.blink_manager.update(cx, BlinkManager::disable);
18942 self.buffer
18943 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18944
18945 if let Some(blame) = self.blame.as_ref() {
18946 blame.update(cx, GitBlame::blur)
18947 }
18948 if !self.hover_state.focused(window, cx) {
18949 hide_hover(self, cx);
18950 }
18951 if !self
18952 .context_menu
18953 .borrow()
18954 .as_ref()
18955 .is_some_and(|context_menu| context_menu.focused(window, cx))
18956 {
18957 self.hide_context_menu(window, cx);
18958 }
18959 self.discard_inline_completion(false, cx);
18960 cx.emit(EditorEvent::Blurred);
18961 cx.notify();
18962 }
18963
18964 pub fn register_action<A: Action>(
18965 &mut self,
18966 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18967 ) -> Subscription {
18968 let id = self.next_editor_action_id.post_inc();
18969 let listener = Arc::new(listener);
18970 self.editor_actions.borrow_mut().insert(
18971 id,
18972 Box::new(move |window, _| {
18973 let listener = listener.clone();
18974 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18975 let action = action.downcast_ref().unwrap();
18976 if phase == DispatchPhase::Bubble {
18977 listener(action, window, cx)
18978 }
18979 })
18980 }),
18981 );
18982
18983 let editor_actions = self.editor_actions.clone();
18984 Subscription::new(move || {
18985 editor_actions.borrow_mut().remove(&id);
18986 })
18987 }
18988
18989 pub fn file_header_size(&self) -> u32 {
18990 FILE_HEADER_HEIGHT
18991 }
18992
18993 pub fn restore(
18994 &mut self,
18995 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18996 window: &mut Window,
18997 cx: &mut Context<Self>,
18998 ) {
18999 let workspace = self.workspace();
19000 let project = self.project.as_ref();
19001 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19002 let mut tasks = Vec::new();
19003 for (buffer_id, changes) in revert_changes {
19004 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19005 buffer.update(cx, |buffer, cx| {
19006 buffer.edit(
19007 changes
19008 .into_iter()
19009 .map(|(range, text)| (range, text.to_string())),
19010 None,
19011 cx,
19012 );
19013 });
19014
19015 if let Some(project) =
19016 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19017 {
19018 project.update(cx, |project, cx| {
19019 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19020 })
19021 }
19022 }
19023 }
19024 tasks
19025 });
19026 cx.spawn_in(window, async move |_, cx| {
19027 for (buffer, task) in save_tasks {
19028 let result = task.await;
19029 if result.is_err() {
19030 let Some(path) = buffer
19031 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19032 .ok()
19033 else {
19034 continue;
19035 };
19036 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19037 let Some(task) = cx
19038 .update_window_entity(&workspace, |workspace, window, cx| {
19039 workspace
19040 .open_path_preview(path, None, false, false, false, window, cx)
19041 })
19042 .ok()
19043 else {
19044 continue;
19045 };
19046 task.await.log_err();
19047 }
19048 }
19049 }
19050 })
19051 .detach();
19052 self.change_selections(None, window, cx, |selections| selections.refresh());
19053 }
19054
19055 pub fn to_pixel_point(
19056 &self,
19057 source: multi_buffer::Anchor,
19058 editor_snapshot: &EditorSnapshot,
19059 window: &mut Window,
19060 ) -> Option<gpui::Point<Pixels>> {
19061 let source_point = source.to_display_point(editor_snapshot);
19062 self.display_to_pixel_point(source_point, editor_snapshot, window)
19063 }
19064
19065 pub fn display_to_pixel_point(
19066 &self,
19067 source: DisplayPoint,
19068 editor_snapshot: &EditorSnapshot,
19069 window: &mut Window,
19070 ) -> Option<gpui::Point<Pixels>> {
19071 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19072 let text_layout_details = self.text_layout_details(window);
19073 let scroll_top = text_layout_details
19074 .scroll_anchor
19075 .scroll_position(editor_snapshot)
19076 .y;
19077
19078 if source.row().as_f32() < scroll_top.floor() {
19079 return None;
19080 }
19081 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19082 let source_y = line_height * (source.row().as_f32() - scroll_top);
19083 Some(gpui::Point::new(source_x, source_y))
19084 }
19085
19086 pub fn has_visible_completions_menu(&self) -> bool {
19087 !self.edit_prediction_preview_is_active()
19088 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19089 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19090 })
19091 }
19092
19093 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19094 if self.mode.is_minimap() {
19095 return;
19096 }
19097 self.addons
19098 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19099 }
19100
19101 pub fn unregister_addon<T: Addon>(&mut self) {
19102 self.addons.remove(&std::any::TypeId::of::<T>());
19103 }
19104
19105 pub fn addon<T: Addon>(&self) -> Option<&T> {
19106 let type_id = std::any::TypeId::of::<T>();
19107 self.addons
19108 .get(&type_id)
19109 .and_then(|item| item.to_any().downcast_ref::<T>())
19110 }
19111
19112 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19113 let type_id = std::any::TypeId::of::<T>();
19114 self.addons
19115 .get_mut(&type_id)
19116 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19117 }
19118
19119 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19120 let text_layout_details = self.text_layout_details(window);
19121 let style = &text_layout_details.editor_style;
19122 let font_id = window.text_system().resolve_font(&style.text.font());
19123 let font_size = style.text.font_size.to_pixels(window.rem_size());
19124 let line_height = style.text.line_height_in_pixels(window.rem_size());
19125 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19126
19127 gpui::Size::new(em_width, line_height)
19128 }
19129
19130 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19131 self.load_diff_task.clone()
19132 }
19133
19134 fn read_metadata_from_db(
19135 &mut self,
19136 item_id: u64,
19137 workspace_id: WorkspaceId,
19138 window: &mut Window,
19139 cx: &mut Context<Editor>,
19140 ) {
19141 if self.is_singleton(cx)
19142 && !self.mode.is_minimap()
19143 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19144 {
19145 let buffer_snapshot = OnceCell::new();
19146
19147 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19148 if !folds.is_empty() {
19149 let snapshot =
19150 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19151 self.fold_ranges(
19152 folds
19153 .into_iter()
19154 .map(|(start, end)| {
19155 snapshot.clip_offset(start, Bias::Left)
19156 ..snapshot.clip_offset(end, Bias::Right)
19157 })
19158 .collect(),
19159 false,
19160 window,
19161 cx,
19162 );
19163 }
19164 }
19165
19166 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19167 if !selections.is_empty() {
19168 let snapshot =
19169 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19170 self.change_selections(None, window, cx, |s| {
19171 s.select_ranges(selections.into_iter().map(|(start, end)| {
19172 snapshot.clip_offset(start, Bias::Left)
19173 ..snapshot.clip_offset(end, Bias::Right)
19174 }));
19175 });
19176 }
19177 };
19178 }
19179
19180 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19181 }
19182}
19183
19184fn vim_enabled(cx: &App) -> bool {
19185 cx.global::<SettingsStore>()
19186 .raw_user_settings()
19187 .get("vim_mode")
19188 == Some(&serde_json::Value::Bool(true))
19189}
19190
19191// Consider user intent and default settings
19192fn choose_completion_range(
19193 completion: &Completion,
19194 intent: CompletionIntent,
19195 buffer: &Entity<Buffer>,
19196 cx: &mut Context<Editor>,
19197) -> Range<usize> {
19198 fn should_replace(
19199 completion: &Completion,
19200 insert_range: &Range<text::Anchor>,
19201 intent: CompletionIntent,
19202 completion_mode_setting: LspInsertMode,
19203 buffer: &Buffer,
19204 ) -> bool {
19205 // specific actions take precedence over settings
19206 match intent {
19207 CompletionIntent::CompleteWithInsert => return false,
19208 CompletionIntent::CompleteWithReplace => return true,
19209 CompletionIntent::Complete | CompletionIntent::Compose => {}
19210 }
19211
19212 match completion_mode_setting {
19213 LspInsertMode::Insert => false,
19214 LspInsertMode::Replace => true,
19215 LspInsertMode::ReplaceSubsequence => {
19216 let mut text_to_replace = buffer.chars_for_range(
19217 buffer.anchor_before(completion.replace_range.start)
19218 ..buffer.anchor_after(completion.replace_range.end),
19219 );
19220 let mut completion_text = completion.new_text.chars();
19221
19222 // is `text_to_replace` a subsequence of `completion_text`
19223 text_to_replace
19224 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19225 }
19226 LspInsertMode::ReplaceSuffix => {
19227 let range_after_cursor = insert_range.end..completion.replace_range.end;
19228
19229 let text_after_cursor = buffer
19230 .text_for_range(
19231 buffer.anchor_before(range_after_cursor.start)
19232 ..buffer.anchor_after(range_after_cursor.end),
19233 )
19234 .collect::<String>();
19235 completion.new_text.ends_with(&text_after_cursor)
19236 }
19237 }
19238 }
19239
19240 let buffer = buffer.read(cx);
19241
19242 if let CompletionSource::Lsp {
19243 insert_range: Some(insert_range),
19244 ..
19245 } = &completion.source
19246 {
19247 let completion_mode_setting =
19248 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19249 .completions
19250 .lsp_insert_mode;
19251
19252 if !should_replace(
19253 completion,
19254 &insert_range,
19255 intent,
19256 completion_mode_setting,
19257 buffer,
19258 ) {
19259 return insert_range.to_offset(buffer);
19260 }
19261 }
19262
19263 completion.replace_range.to_offset(buffer)
19264}
19265
19266fn insert_extra_newline_brackets(
19267 buffer: &MultiBufferSnapshot,
19268 range: Range<usize>,
19269 language: &language::LanguageScope,
19270) -> bool {
19271 let leading_whitespace_len = buffer
19272 .reversed_chars_at(range.start)
19273 .take_while(|c| c.is_whitespace() && *c != '\n')
19274 .map(|c| c.len_utf8())
19275 .sum::<usize>();
19276 let trailing_whitespace_len = buffer
19277 .chars_at(range.end)
19278 .take_while(|c| c.is_whitespace() && *c != '\n')
19279 .map(|c| c.len_utf8())
19280 .sum::<usize>();
19281 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19282
19283 language.brackets().any(|(pair, enabled)| {
19284 let pair_start = pair.start.trim_end();
19285 let pair_end = pair.end.trim_start();
19286
19287 enabled
19288 && pair.newline
19289 && buffer.contains_str_at(range.end, pair_end)
19290 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19291 })
19292}
19293
19294fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19295 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19296 [(buffer, range, _)] => (*buffer, range.clone()),
19297 _ => return false,
19298 };
19299 let pair = {
19300 let mut result: Option<BracketMatch> = None;
19301
19302 for pair in buffer
19303 .all_bracket_ranges(range.clone())
19304 .filter(move |pair| {
19305 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19306 })
19307 {
19308 let len = pair.close_range.end - pair.open_range.start;
19309
19310 if let Some(existing) = &result {
19311 let existing_len = existing.close_range.end - existing.open_range.start;
19312 if len > existing_len {
19313 continue;
19314 }
19315 }
19316
19317 result = Some(pair);
19318 }
19319
19320 result
19321 };
19322 let Some(pair) = pair else {
19323 return false;
19324 };
19325 pair.newline_only
19326 && buffer
19327 .chars_for_range(pair.open_range.end..range.start)
19328 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19329 .all(|c| c.is_whitespace() && c != '\n')
19330}
19331
19332fn update_uncommitted_diff_for_buffer(
19333 editor: Entity<Editor>,
19334 project: &Entity<Project>,
19335 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19336 buffer: Entity<MultiBuffer>,
19337 cx: &mut App,
19338) -> Task<()> {
19339 let mut tasks = Vec::new();
19340 project.update(cx, |project, cx| {
19341 for buffer in buffers {
19342 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19343 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19344 }
19345 }
19346 });
19347 cx.spawn(async move |cx| {
19348 let diffs = future::join_all(tasks).await;
19349 if editor
19350 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19351 .unwrap_or(false)
19352 {
19353 return;
19354 }
19355
19356 buffer
19357 .update(cx, |buffer, cx| {
19358 for diff in diffs.into_iter().flatten() {
19359 buffer.add_diff(diff, cx);
19360 }
19361 })
19362 .ok();
19363 })
19364}
19365
19366fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
19367 let tab_size = tab_size.get() as usize;
19368 let mut width = offset;
19369
19370 for ch in text.chars() {
19371 width += if ch == '\t' {
19372 tab_size - (width % tab_size)
19373 } else {
19374 1
19375 };
19376 }
19377
19378 width - offset
19379}
19380
19381#[cfg(test)]
19382mod tests {
19383 use super::*;
19384
19385 #[test]
19386 fn test_string_size_with_expanded_tabs() {
19387 let nz = |val| NonZeroU32::new(val).unwrap();
19388 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
19389 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
19390 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
19391 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
19392 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
19393 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
19394 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
19395 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
19396 }
19397}
19398
19399/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
19400struct WordBreakingTokenizer<'a> {
19401 input: &'a str,
19402}
19403
19404impl<'a> WordBreakingTokenizer<'a> {
19405 fn new(input: &'a str) -> Self {
19406 Self { input }
19407 }
19408}
19409
19410fn is_char_ideographic(ch: char) -> bool {
19411 use unicode_script::Script::*;
19412 use unicode_script::UnicodeScript;
19413 matches!(ch.script(), Han | Tangut | Yi)
19414}
19415
19416fn is_grapheme_ideographic(text: &str) -> bool {
19417 text.chars().any(is_char_ideographic)
19418}
19419
19420fn is_grapheme_whitespace(text: &str) -> bool {
19421 text.chars().any(|x| x.is_whitespace())
19422}
19423
19424fn should_stay_with_preceding_ideograph(text: &str) -> bool {
19425 text.chars().next().map_or(false, |ch| {
19426 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
19427 })
19428}
19429
19430#[derive(PartialEq, Eq, Debug, Clone, Copy)]
19431enum WordBreakToken<'a> {
19432 Word { token: &'a str, grapheme_len: usize },
19433 InlineWhitespace { token: &'a str, grapheme_len: usize },
19434 Newline,
19435}
19436
19437impl<'a> Iterator for WordBreakingTokenizer<'a> {
19438 /// Yields a span, the count of graphemes in the token, and whether it was
19439 /// whitespace. Note that it also breaks at word boundaries.
19440 type Item = WordBreakToken<'a>;
19441
19442 fn next(&mut self) -> Option<Self::Item> {
19443 use unicode_segmentation::UnicodeSegmentation;
19444 if self.input.is_empty() {
19445 return None;
19446 }
19447
19448 let mut iter = self.input.graphemes(true).peekable();
19449 let mut offset = 0;
19450 let mut grapheme_len = 0;
19451 if let Some(first_grapheme) = iter.next() {
19452 let is_newline = first_grapheme == "\n";
19453 let is_whitespace = is_grapheme_whitespace(first_grapheme);
19454 offset += first_grapheme.len();
19455 grapheme_len += 1;
19456 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
19457 if let Some(grapheme) = iter.peek().copied() {
19458 if should_stay_with_preceding_ideograph(grapheme) {
19459 offset += grapheme.len();
19460 grapheme_len += 1;
19461 }
19462 }
19463 } else {
19464 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19465 let mut next_word_bound = words.peek().copied();
19466 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19467 next_word_bound = words.next();
19468 }
19469 while let Some(grapheme) = iter.peek().copied() {
19470 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19471 break;
19472 };
19473 if is_grapheme_whitespace(grapheme) != is_whitespace
19474 || (grapheme == "\n") != is_newline
19475 {
19476 break;
19477 };
19478 offset += grapheme.len();
19479 grapheme_len += 1;
19480 iter.next();
19481 }
19482 }
19483 let token = &self.input[..offset];
19484 self.input = &self.input[offset..];
19485 if token == "\n" {
19486 Some(WordBreakToken::Newline)
19487 } else if is_whitespace {
19488 Some(WordBreakToken::InlineWhitespace {
19489 token,
19490 grapheme_len,
19491 })
19492 } else {
19493 Some(WordBreakToken::Word {
19494 token,
19495 grapheme_len,
19496 })
19497 }
19498 } else {
19499 None
19500 }
19501 }
19502}
19503
19504#[test]
19505fn test_word_breaking_tokenizer() {
19506 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19507 ("", &[]),
19508 (" ", &[whitespace(" ", 2)]),
19509 ("Ʒ", &[word("Ʒ", 1)]),
19510 ("Ǽ", &[word("Ǽ", 1)]),
19511 ("⋑", &[word("⋑", 1)]),
19512 ("⋑⋑", &[word("⋑⋑", 2)]),
19513 (
19514 "原理,进而",
19515 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19516 ),
19517 (
19518 "hello world",
19519 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19520 ),
19521 (
19522 "hello, world",
19523 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19524 ),
19525 (
19526 " hello world",
19527 &[
19528 whitespace(" ", 2),
19529 word("hello", 5),
19530 whitespace(" ", 1),
19531 word("world", 5),
19532 ],
19533 ),
19534 (
19535 "这是什么 \n 钢笔",
19536 &[
19537 word("这", 1),
19538 word("是", 1),
19539 word("什", 1),
19540 word("么", 1),
19541 whitespace(" ", 1),
19542 newline(),
19543 whitespace(" ", 1),
19544 word("钢", 1),
19545 word("笔", 1),
19546 ],
19547 ),
19548 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19549 ];
19550
19551 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19552 WordBreakToken::Word {
19553 token,
19554 grapheme_len,
19555 }
19556 }
19557
19558 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19559 WordBreakToken::InlineWhitespace {
19560 token,
19561 grapheme_len,
19562 }
19563 }
19564
19565 fn newline() -> WordBreakToken<'static> {
19566 WordBreakToken::Newline
19567 }
19568
19569 for (input, result) in tests {
19570 assert_eq!(
19571 WordBreakingTokenizer::new(input)
19572 .collect::<Vec<_>>()
19573 .as_slice(),
19574 *result,
19575 );
19576 }
19577}
19578
19579fn wrap_with_prefix(
19580 line_prefix: String,
19581 unwrapped_text: String,
19582 wrap_column: usize,
19583 tab_size: NonZeroU32,
19584 preserve_existing_whitespace: bool,
19585) -> String {
19586 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19587 let mut wrapped_text = String::new();
19588 let mut current_line = line_prefix.clone();
19589
19590 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19591 let mut current_line_len = line_prefix_len;
19592 let mut in_whitespace = false;
19593 for token in tokenizer {
19594 let have_preceding_whitespace = in_whitespace;
19595 match token {
19596 WordBreakToken::Word {
19597 token,
19598 grapheme_len,
19599 } => {
19600 in_whitespace = false;
19601 if current_line_len + grapheme_len > wrap_column
19602 && current_line_len != line_prefix_len
19603 {
19604 wrapped_text.push_str(current_line.trim_end());
19605 wrapped_text.push('\n');
19606 current_line.truncate(line_prefix.len());
19607 current_line_len = line_prefix_len;
19608 }
19609 current_line.push_str(token);
19610 current_line_len += grapheme_len;
19611 }
19612 WordBreakToken::InlineWhitespace {
19613 mut token,
19614 mut grapheme_len,
19615 } => {
19616 in_whitespace = true;
19617 if have_preceding_whitespace && !preserve_existing_whitespace {
19618 continue;
19619 }
19620 if !preserve_existing_whitespace {
19621 token = " ";
19622 grapheme_len = 1;
19623 }
19624 if current_line_len + grapheme_len > wrap_column {
19625 wrapped_text.push_str(current_line.trim_end());
19626 wrapped_text.push('\n');
19627 current_line.truncate(line_prefix.len());
19628 current_line_len = line_prefix_len;
19629 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19630 current_line.push_str(token);
19631 current_line_len += grapheme_len;
19632 }
19633 }
19634 WordBreakToken::Newline => {
19635 in_whitespace = true;
19636 if preserve_existing_whitespace {
19637 wrapped_text.push_str(current_line.trim_end());
19638 wrapped_text.push('\n');
19639 current_line.truncate(line_prefix.len());
19640 current_line_len = line_prefix_len;
19641 } else if have_preceding_whitespace {
19642 continue;
19643 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19644 {
19645 wrapped_text.push_str(current_line.trim_end());
19646 wrapped_text.push('\n');
19647 current_line.truncate(line_prefix.len());
19648 current_line_len = line_prefix_len;
19649 } else if current_line_len != line_prefix_len {
19650 current_line.push(' ');
19651 current_line_len += 1;
19652 }
19653 }
19654 }
19655 }
19656
19657 if !current_line.is_empty() {
19658 wrapped_text.push_str(¤t_line);
19659 }
19660 wrapped_text
19661}
19662
19663#[test]
19664fn test_wrap_with_prefix() {
19665 assert_eq!(
19666 wrap_with_prefix(
19667 "# ".to_string(),
19668 "abcdefg".to_string(),
19669 4,
19670 NonZeroU32::new(4).unwrap(),
19671 false,
19672 ),
19673 "# abcdefg"
19674 );
19675 assert_eq!(
19676 wrap_with_prefix(
19677 "".to_string(),
19678 "\thello world".to_string(),
19679 8,
19680 NonZeroU32::new(4).unwrap(),
19681 false,
19682 ),
19683 "hello\nworld"
19684 );
19685 assert_eq!(
19686 wrap_with_prefix(
19687 "// ".to_string(),
19688 "xx \nyy zz aa bb cc".to_string(),
19689 12,
19690 NonZeroU32::new(4).unwrap(),
19691 false,
19692 ),
19693 "// xx yy zz\n// aa bb cc"
19694 );
19695 assert_eq!(
19696 wrap_with_prefix(
19697 String::new(),
19698 "这是什么 \n 钢笔".to_string(),
19699 3,
19700 NonZeroU32::new(4).unwrap(),
19701 false,
19702 ),
19703 "这是什\n么 钢\n笔"
19704 );
19705}
19706
19707pub trait CollaborationHub {
19708 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19709 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19710 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19711}
19712
19713impl CollaborationHub for Entity<Project> {
19714 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19715 self.read(cx).collaborators()
19716 }
19717
19718 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19719 self.read(cx).user_store().read(cx).participant_indices()
19720 }
19721
19722 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19723 let this = self.read(cx);
19724 let user_ids = this.collaborators().values().map(|c| c.user_id);
19725 this.user_store().read_with(cx, |user_store, cx| {
19726 user_store.participant_names(user_ids, cx)
19727 })
19728 }
19729}
19730
19731pub trait SemanticsProvider {
19732 fn hover(
19733 &self,
19734 buffer: &Entity<Buffer>,
19735 position: text::Anchor,
19736 cx: &mut App,
19737 ) -> Option<Task<Vec<project::Hover>>>;
19738
19739 fn inline_values(
19740 &self,
19741 buffer_handle: Entity<Buffer>,
19742 range: Range<text::Anchor>,
19743 cx: &mut App,
19744 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19745
19746 fn inlay_hints(
19747 &self,
19748 buffer_handle: Entity<Buffer>,
19749 range: Range<text::Anchor>,
19750 cx: &mut App,
19751 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19752
19753 fn resolve_inlay_hint(
19754 &self,
19755 hint: InlayHint,
19756 buffer_handle: Entity<Buffer>,
19757 server_id: LanguageServerId,
19758 cx: &mut App,
19759 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19760
19761 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19762
19763 fn document_highlights(
19764 &self,
19765 buffer: &Entity<Buffer>,
19766 position: text::Anchor,
19767 cx: &mut App,
19768 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19769
19770 fn definitions(
19771 &self,
19772 buffer: &Entity<Buffer>,
19773 position: text::Anchor,
19774 kind: GotoDefinitionKind,
19775 cx: &mut App,
19776 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19777
19778 fn range_for_rename(
19779 &self,
19780 buffer: &Entity<Buffer>,
19781 position: text::Anchor,
19782 cx: &mut App,
19783 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19784
19785 fn perform_rename(
19786 &self,
19787 buffer: &Entity<Buffer>,
19788 position: text::Anchor,
19789 new_name: String,
19790 cx: &mut App,
19791 ) -> Option<Task<Result<ProjectTransaction>>>;
19792}
19793
19794pub trait CompletionProvider {
19795 fn completions(
19796 &self,
19797 excerpt_id: ExcerptId,
19798 buffer: &Entity<Buffer>,
19799 buffer_position: text::Anchor,
19800 trigger: CompletionContext,
19801 window: &mut Window,
19802 cx: &mut Context<Editor>,
19803 ) -> Task<Result<Option<Vec<Completion>>>>;
19804
19805 fn resolve_completions(
19806 &self,
19807 buffer: Entity<Buffer>,
19808 completion_indices: Vec<usize>,
19809 completions: Rc<RefCell<Box<[Completion]>>>,
19810 cx: &mut Context<Editor>,
19811 ) -> Task<Result<bool>>;
19812
19813 fn apply_additional_edits_for_completion(
19814 &self,
19815 _buffer: Entity<Buffer>,
19816 _completions: Rc<RefCell<Box<[Completion]>>>,
19817 _completion_index: usize,
19818 _push_to_history: bool,
19819 _cx: &mut Context<Editor>,
19820 ) -> Task<Result<Option<language::Transaction>>> {
19821 Task::ready(Ok(None))
19822 }
19823
19824 fn is_completion_trigger(
19825 &self,
19826 buffer: &Entity<Buffer>,
19827 position: language::Anchor,
19828 text: &str,
19829 trigger_in_words: bool,
19830 cx: &mut Context<Editor>,
19831 ) -> bool;
19832
19833 fn sort_completions(&self) -> bool {
19834 true
19835 }
19836
19837 fn filter_completions(&self) -> bool {
19838 true
19839 }
19840}
19841
19842pub trait CodeActionProvider {
19843 fn id(&self) -> Arc<str>;
19844
19845 fn code_actions(
19846 &self,
19847 buffer: &Entity<Buffer>,
19848 range: Range<text::Anchor>,
19849 window: &mut Window,
19850 cx: &mut App,
19851 ) -> Task<Result<Vec<CodeAction>>>;
19852
19853 fn apply_code_action(
19854 &self,
19855 buffer_handle: Entity<Buffer>,
19856 action: CodeAction,
19857 excerpt_id: ExcerptId,
19858 push_to_history: bool,
19859 window: &mut Window,
19860 cx: &mut App,
19861 ) -> Task<Result<ProjectTransaction>>;
19862}
19863
19864impl CodeActionProvider for Entity<Project> {
19865 fn id(&self) -> Arc<str> {
19866 "project".into()
19867 }
19868
19869 fn code_actions(
19870 &self,
19871 buffer: &Entity<Buffer>,
19872 range: Range<text::Anchor>,
19873 _window: &mut Window,
19874 cx: &mut App,
19875 ) -> Task<Result<Vec<CodeAction>>> {
19876 self.update(cx, |project, cx| {
19877 let code_lens = project.code_lens(buffer, range.clone(), cx);
19878 let code_actions = project.code_actions(buffer, range, None, cx);
19879 cx.background_spawn(async move {
19880 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19881 Ok(code_lens
19882 .context("code lens fetch")?
19883 .into_iter()
19884 .chain(code_actions.context("code action fetch")?)
19885 .collect())
19886 })
19887 })
19888 }
19889
19890 fn apply_code_action(
19891 &self,
19892 buffer_handle: Entity<Buffer>,
19893 action: CodeAction,
19894 _excerpt_id: ExcerptId,
19895 push_to_history: bool,
19896 _window: &mut Window,
19897 cx: &mut App,
19898 ) -> Task<Result<ProjectTransaction>> {
19899 self.update(cx, |project, cx| {
19900 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19901 })
19902 }
19903}
19904
19905fn snippet_completions(
19906 project: &Project,
19907 buffer: &Entity<Buffer>,
19908 buffer_position: text::Anchor,
19909 cx: &mut App,
19910) -> Task<Result<Vec<Completion>>> {
19911 let languages = buffer.read(cx).languages_at(buffer_position);
19912 let snippet_store = project.snippets().read(cx);
19913
19914 let scopes: Vec<_> = languages
19915 .iter()
19916 .filter_map(|language| {
19917 let language_name = language.lsp_id();
19918 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19919
19920 if snippets.is_empty() {
19921 None
19922 } else {
19923 Some((language.default_scope(), snippets))
19924 }
19925 })
19926 .collect();
19927
19928 if scopes.is_empty() {
19929 return Task::ready(Ok(vec![]));
19930 }
19931
19932 let snapshot = buffer.read(cx).text_snapshot();
19933 let chars: String = snapshot
19934 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19935 .collect();
19936 let executor = cx.background_executor().clone();
19937
19938 cx.background_spawn(async move {
19939 let mut all_results: Vec<Completion> = Vec::new();
19940 for (scope, snippets) in scopes.into_iter() {
19941 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19942 let mut last_word = chars
19943 .chars()
19944 .take_while(|c| classifier.is_word(*c))
19945 .collect::<String>();
19946 last_word = last_word.chars().rev().collect();
19947
19948 if last_word.is_empty() {
19949 return Ok(vec![]);
19950 }
19951
19952 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19953 let to_lsp = |point: &text::Anchor| {
19954 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19955 point_to_lsp(end)
19956 };
19957 let lsp_end = to_lsp(&buffer_position);
19958
19959 let candidates = snippets
19960 .iter()
19961 .enumerate()
19962 .flat_map(|(ix, snippet)| {
19963 snippet
19964 .prefix
19965 .iter()
19966 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19967 })
19968 .collect::<Vec<StringMatchCandidate>>();
19969
19970 let mut matches = fuzzy::match_strings(
19971 &candidates,
19972 &last_word,
19973 last_word.chars().any(|c| c.is_uppercase()),
19974 100,
19975 &Default::default(),
19976 executor.clone(),
19977 )
19978 .await;
19979
19980 // Remove all candidates where the query's start does not match the start of any word in the candidate
19981 if let Some(query_start) = last_word.chars().next() {
19982 matches.retain(|string_match| {
19983 split_words(&string_match.string).any(|word| {
19984 // Check that the first codepoint of the word as lowercase matches the first
19985 // codepoint of the query as lowercase
19986 word.chars()
19987 .flat_map(|codepoint| codepoint.to_lowercase())
19988 .zip(query_start.to_lowercase())
19989 .all(|(word_cp, query_cp)| word_cp == query_cp)
19990 })
19991 });
19992 }
19993
19994 let matched_strings = matches
19995 .into_iter()
19996 .map(|m| m.string)
19997 .collect::<HashSet<_>>();
19998
19999 let mut result: Vec<Completion> = snippets
20000 .iter()
20001 .filter_map(|snippet| {
20002 let matching_prefix = snippet
20003 .prefix
20004 .iter()
20005 .find(|prefix| matched_strings.contains(*prefix))?;
20006 let start = as_offset - last_word.len();
20007 let start = snapshot.anchor_before(start);
20008 let range = start..buffer_position;
20009 let lsp_start = to_lsp(&start);
20010 let lsp_range = lsp::Range {
20011 start: lsp_start,
20012 end: lsp_end,
20013 };
20014 Some(Completion {
20015 replace_range: range,
20016 new_text: snippet.body.clone(),
20017 source: CompletionSource::Lsp {
20018 insert_range: None,
20019 server_id: LanguageServerId(usize::MAX),
20020 resolved: true,
20021 lsp_completion: Box::new(lsp::CompletionItem {
20022 label: snippet.prefix.first().unwrap().clone(),
20023 kind: Some(CompletionItemKind::SNIPPET),
20024 label_details: snippet.description.as_ref().map(|description| {
20025 lsp::CompletionItemLabelDetails {
20026 detail: Some(description.clone()),
20027 description: None,
20028 }
20029 }),
20030 insert_text_format: Some(InsertTextFormat::SNIPPET),
20031 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20032 lsp::InsertReplaceEdit {
20033 new_text: snippet.body.clone(),
20034 insert: lsp_range,
20035 replace: lsp_range,
20036 },
20037 )),
20038 filter_text: Some(snippet.body.clone()),
20039 sort_text: Some(char::MAX.to_string()),
20040 ..lsp::CompletionItem::default()
20041 }),
20042 lsp_defaults: None,
20043 },
20044 label: CodeLabel {
20045 text: matching_prefix.clone(),
20046 runs: Vec::new(),
20047 filter_range: 0..matching_prefix.len(),
20048 },
20049 icon_path: None,
20050 documentation: Some(
20051 CompletionDocumentation::SingleLineAndMultiLinePlainText {
20052 single_line: snippet.name.clone().into(),
20053 plain_text: snippet
20054 .description
20055 .clone()
20056 .map(|description| description.into()),
20057 },
20058 ),
20059 insert_text_mode: None,
20060 confirm: None,
20061 })
20062 })
20063 .collect();
20064
20065 all_results.append(&mut result);
20066 }
20067
20068 Ok(all_results)
20069 })
20070}
20071
20072impl CompletionProvider for Entity<Project> {
20073 fn completions(
20074 &self,
20075 _excerpt_id: ExcerptId,
20076 buffer: &Entity<Buffer>,
20077 buffer_position: text::Anchor,
20078 options: CompletionContext,
20079 _window: &mut Window,
20080 cx: &mut Context<Editor>,
20081 ) -> Task<Result<Option<Vec<Completion>>>> {
20082 self.update(cx, |project, cx| {
20083 let snippets = snippet_completions(project, buffer, buffer_position, cx);
20084 let project_completions = project.completions(buffer, buffer_position, options, cx);
20085 cx.background_spawn(async move {
20086 let snippets_completions = snippets.await?;
20087 match project_completions.await? {
20088 Some(mut completions) => {
20089 completions.extend(snippets_completions);
20090 Ok(Some(completions))
20091 }
20092 None => {
20093 if snippets_completions.is_empty() {
20094 Ok(None)
20095 } else {
20096 Ok(Some(snippets_completions))
20097 }
20098 }
20099 }
20100 })
20101 })
20102 }
20103
20104 fn resolve_completions(
20105 &self,
20106 buffer: Entity<Buffer>,
20107 completion_indices: Vec<usize>,
20108 completions: Rc<RefCell<Box<[Completion]>>>,
20109 cx: &mut Context<Editor>,
20110 ) -> Task<Result<bool>> {
20111 self.update(cx, |project, cx| {
20112 project.lsp_store().update(cx, |lsp_store, cx| {
20113 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
20114 })
20115 })
20116 }
20117
20118 fn apply_additional_edits_for_completion(
20119 &self,
20120 buffer: Entity<Buffer>,
20121 completions: Rc<RefCell<Box<[Completion]>>>,
20122 completion_index: usize,
20123 push_to_history: bool,
20124 cx: &mut Context<Editor>,
20125 ) -> Task<Result<Option<language::Transaction>>> {
20126 self.update(cx, |project, cx| {
20127 project.lsp_store().update(cx, |lsp_store, cx| {
20128 lsp_store.apply_additional_edits_for_completion(
20129 buffer,
20130 completions,
20131 completion_index,
20132 push_to_history,
20133 cx,
20134 )
20135 })
20136 })
20137 }
20138
20139 fn is_completion_trigger(
20140 &self,
20141 buffer: &Entity<Buffer>,
20142 position: language::Anchor,
20143 text: &str,
20144 trigger_in_words: bool,
20145 cx: &mut Context<Editor>,
20146 ) -> bool {
20147 let mut chars = text.chars();
20148 let char = if let Some(char) = chars.next() {
20149 char
20150 } else {
20151 return false;
20152 };
20153 if chars.next().is_some() {
20154 return false;
20155 }
20156
20157 let buffer = buffer.read(cx);
20158 let snapshot = buffer.snapshot();
20159 if !snapshot.settings_at(position, cx).show_completions_on_input {
20160 return false;
20161 }
20162 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20163 if trigger_in_words && classifier.is_word(char) {
20164 return true;
20165 }
20166
20167 buffer.completion_triggers().contains(text)
20168 }
20169}
20170
20171impl SemanticsProvider for Entity<Project> {
20172 fn hover(
20173 &self,
20174 buffer: &Entity<Buffer>,
20175 position: text::Anchor,
20176 cx: &mut App,
20177 ) -> Option<Task<Vec<project::Hover>>> {
20178 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20179 }
20180
20181 fn document_highlights(
20182 &self,
20183 buffer: &Entity<Buffer>,
20184 position: text::Anchor,
20185 cx: &mut App,
20186 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20187 Some(self.update(cx, |project, cx| {
20188 project.document_highlights(buffer, position, cx)
20189 }))
20190 }
20191
20192 fn definitions(
20193 &self,
20194 buffer: &Entity<Buffer>,
20195 position: text::Anchor,
20196 kind: GotoDefinitionKind,
20197 cx: &mut App,
20198 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20199 Some(self.update(cx, |project, cx| match kind {
20200 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20201 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20202 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20203 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20204 }))
20205 }
20206
20207 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20208 // TODO: make this work for remote projects
20209 self.update(cx, |project, cx| {
20210 if project
20211 .active_debug_session(cx)
20212 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20213 {
20214 return true;
20215 }
20216
20217 buffer.update(cx, |buffer, cx| {
20218 project.any_language_server_supports_inlay_hints(buffer, cx)
20219 })
20220 })
20221 }
20222
20223 fn inline_values(
20224 &self,
20225 buffer_handle: Entity<Buffer>,
20226 range: Range<text::Anchor>,
20227 cx: &mut App,
20228 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20229 self.update(cx, |project, cx| {
20230 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20231
20232 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20233 })
20234 }
20235
20236 fn inlay_hints(
20237 &self,
20238 buffer_handle: Entity<Buffer>,
20239 range: Range<text::Anchor>,
20240 cx: &mut App,
20241 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20242 Some(self.update(cx, |project, cx| {
20243 project.inlay_hints(buffer_handle, range, cx)
20244 }))
20245 }
20246
20247 fn resolve_inlay_hint(
20248 &self,
20249 hint: InlayHint,
20250 buffer_handle: Entity<Buffer>,
20251 server_id: LanguageServerId,
20252 cx: &mut App,
20253 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20254 Some(self.update(cx, |project, cx| {
20255 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20256 }))
20257 }
20258
20259 fn range_for_rename(
20260 &self,
20261 buffer: &Entity<Buffer>,
20262 position: text::Anchor,
20263 cx: &mut App,
20264 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20265 Some(self.update(cx, |project, cx| {
20266 let buffer = buffer.clone();
20267 let task = project.prepare_rename(buffer.clone(), position, cx);
20268 cx.spawn(async move |_, cx| {
20269 Ok(match task.await? {
20270 PrepareRenameResponse::Success(range) => Some(range),
20271 PrepareRenameResponse::InvalidPosition => None,
20272 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20273 // Fallback on using TreeSitter info to determine identifier range
20274 buffer.update(cx, |buffer, _| {
20275 let snapshot = buffer.snapshot();
20276 let (range, kind) = snapshot.surrounding_word(position);
20277 if kind != Some(CharKind::Word) {
20278 return None;
20279 }
20280 Some(
20281 snapshot.anchor_before(range.start)
20282 ..snapshot.anchor_after(range.end),
20283 )
20284 })?
20285 }
20286 })
20287 })
20288 }))
20289 }
20290
20291 fn perform_rename(
20292 &self,
20293 buffer: &Entity<Buffer>,
20294 position: text::Anchor,
20295 new_name: String,
20296 cx: &mut App,
20297 ) -> Option<Task<Result<ProjectTransaction>>> {
20298 Some(self.update(cx, |project, cx| {
20299 project.perform_rename(buffer.clone(), position, new_name, cx)
20300 }))
20301 }
20302}
20303
20304fn inlay_hint_settings(
20305 location: Anchor,
20306 snapshot: &MultiBufferSnapshot,
20307 cx: &mut Context<Editor>,
20308) -> InlayHintSettings {
20309 let file = snapshot.file_at(location);
20310 let language = snapshot.language_at(location).map(|l| l.name());
20311 language_settings(language, file, cx).inlay_hints
20312}
20313
20314fn consume_contiguous_rows(
20315 contiguous_row_selections: &mut Vec<Selection<Point>>,
20316 selection: &Selection<Point>,
20317 display_map: &DisplaySnapshot,
20318 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20319) -> (MultiBufferRow, MultiBufferRow) {
20320 contiguous_row_selections.push(selection.clone());
20321 let start_row = MultiBufferRow(selection.start.row);
20322 let mut end_row = ending_row(selection, display_map);
20323
20324 while let Some(next_selection) = selections.peek() {
20325 if next_selection.start.row <= end_row.0 {
20326 end_row = ending_row(next_selection, display_map);
20327 contiguous_row_selections.push(selections.next().unwrap().clone());
20328 } else {
20329 break;
20330 }
20331 }
20332 (start_row, end_row)
20333}
20334
20335fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20336 if next_selection.end.column > 0 || next_selection.is_empty() {
20337 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20338 } else {
20339 MultiBufferRow(next_selection.end.row)
20340 }
20341}
20342
20343impl EditorSnapshot {
20344 pub fn remote_selections_in_range<'a>(
20345 &'a self,
20346 range: &'a Range<Anchor>,
20347 collaboration_hub: &dyn CollaborationHub,
20348 cx: &'a App,
20349 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20350 let participant_names = collaboration_hub.user_names(cx);
20351 let participant_indices = collaboration_hub.user_participant_indices(cx);
20352 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20353 let collaborators_by_replica_id = collaborators_by_peer_id
20354 .values()
20355 .map(|collaborator| (collaborator.replica_id, collaborator))
20356 .collect::<HashMap<_, _>>();
20357 self.buffer_snapshot
20358 .selections_in_range(range, false)
20359 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20360 if replica_id == AGENT_REPLICA_ID {
20361 Some(RemoteSelection {
20362 replica_id,
20363 selection,
20364 cursor_shape,
20365 line_mode,
20366 collaborator_id: CollaboratorId::Agent,
20367 user_name: Some("Agent".into()),
20368 color: cx.theme().players().agent(),
20369 })
20370 } else {
20371 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20372 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20373 let user_name = participant_names.get(&collaborator.user_id).cloned();
20374 Some(RemoteSelection {
20375 replica_id,
20376 selection,
20377 cursor_shape,
20378 line_mode,
20379 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20380 user_name,
20381 color: if let Some(index) = participant_index {
20382 cx.theme().players().color_for_participant(index.0)
20383 } else {
20384 cx.theme().players().absent()
20385 },
20386 })
20387 }
20388 })
20389 }
20390
20391 pub fn hunks_for_ranges(
20392 &self,
20393 ranges: impl IntoIterator<Item = Range<Point>>,
20394 ) -> Vec<MultiBufferDiffHunk> {
20395 let mut hunks = Vec::new();
20396 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20397 HashMap::default();
20398 for query_range in ranges {
20399 let query_rows =
20400 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20401 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20402 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20403 ) {
20404 // Include deleted hunks that are adjacent to the query range, because
20405 // otherwise they would be missed.
20406 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20407 if hunk.status().is_deleted() {
20408 intersects_range |= hunk.row_range.start == query_rows.end;
20409 intersects_range |= hunk.row_range.end == query_rows.start;
20410 }
20411 if intersects_range {
20412 if !processed_buffer_rows
20413 .entry(hunk.buffer_id)
20414 .or_default()
20415 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20416 {
20417 continue;
20418 }
20419 hunks.push(hunk);
20420 }
20421 }
20422 }
20423
20424 hunks
20425 }
20426
20427 fn display_diff_hunks_for_rows<'a>(
20428 &'a self,
20429 display_rows: Range<DisplayRow>,
20430 folded_buffers: &'a HashSet<BufferId>,
20431 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20432 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20433 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20434
20435 self.buffer_snapshot
20436 .diff_hunks_in_range(buffer_start..buffer_end)
20437 .filter_map(|hunk| {
20438 if folded_buffers.contains(&hunk.buffer_id) {
20439 return None;
20440 }
20441
20442 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20443 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20444
20445 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20446 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20447
20448 let display_hunk = if hunk_display_start.column() != 0 {
20449 DisplayDiffHunk::Folded {
20450 display_row: hunk_display_start.row(),
20451 }
20452 } else {
20453 let mut end_row = hunk_display_end.row();
20454 if hunk_display_end.column() > 0 {
20455 end_row.0 += 1;
20456 }
20457 let is_created_file = hunk.is_created_file();
20458 DisplayDiffHunk::Unfolded {
20459 status: hunk.status(),
20460 diff_base_byte_range: hunk.diff_base_byte_range,
20461 display_row_range: hunk_display_start.row()..end_row,
20462 multi_buffer_range: Anchor::range_in_buffer(
20463 hunk.excerpt_id,
20464 hunk.buffer_id,
20465 hunk.buffer_range,
20466 ),
20467 is_created_file,
20468 }
20469 };
20470
20471 Some(display_hunk)
20472 })
20473 }
20474
20475 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20476 self.display_snapshot.buffer_snapshot.language_at(position)
20477 }
20478
20479 pub fn is_focused(&self) -> bool {
20480 self.is_focused
20481 }
20482
20483 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20484 self.placeholder_text.as_ref()
20485 }
20486
20487 pub fn scroll_position(&self) -> gpui::Point<f32> {
20488 self.scroll_anchor.scroll_position(&self.display_snapshot)
20489 }
20490
20491 fn gutter_dimensions(
20492 &self,
20493 font_id: FontId,
20494 font_size: Pixels,
20495 max_line_number_width: Pixels,
20496 cx: &App,
20497 ) -> Option<GutterDimensions> {
20498 if !self.show_gutter {
20499 return None;
20500 }
20501
20502 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20503 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20504
20505 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20506 matches!(
20507 ProjectSettings::get_global(cx).git.git_gutter,
20508 Some(GitGutterSetting::TrackedFiles)
20509 )
20510 });
20511 let gutter_settings = EditorSettings::get_global(cx).gutter;
20512 let show_line_numbers = self
20513 .show_line_numbers
20514 .unwrap_or(gutter_settings.line_numbers);
20515 let line_gutter_width = if show_line_numbers {
20516 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20517 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20518 max_line_number_width.max(min_width_for_number_on_gutter)
20519 } else {
20520 0.0.into()
20521 };
20522
20523 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20524 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20525
20526 let git_blame_entries_width =
20527 self.git_blame_gutter_max_author_length
20528 .map(|max_author_length| {
20529 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20530 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20531
20532 /// The number of characters to dedicate to gaps and margins.
20533 const SPACING_WIDTH: usize = 4;
20534
20535 let max_char_count = max_author_length.min(renderer.max_author_length())
20536 + ::git::SHORT_SHA_LENGTH
20537 + MAX_RELATIVE_TIMESTAMP.len()
20538 + SPACING_WIDTH;
20539
20540 em_advance * max_char_count
20541 });
20542
20543 let is_singleton = self.buffer_snapshot.is_singleton();
20544
20545 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20546 left_padding += if !is_singleton {
20547 em_width * 4.0
20548 } else if show_runnables || show_breakpoints {
20549 em_width * 3.0
20550 } else if show_git_gutter && show_line_numbers {
20551 em_width * 2.0
20552 } else if show_git_gutter || show_line_numbers {
20553 em_width
20554 } else {
20555 px(0.)
20556 };
20557
20558 let shows_folds = is_singleton && gutter_settings.folds;
20559
20560 let right_padding = if shows_folds && show_line_numbers {
20561 em_width * 4.0
20562 } else if shows_folds || (!is_singleton && show_line_numbers) {
20563 em_width * 3.0
20564 } else if show_line_numbers {
20565 em_width
20566 } else {
20567 px(0.)
20568 };
20569
20570 Some(GutterDimensions {
20571 left_padding,
20572 right_padding,
20573 width: line_gutter_width + left_padding + right_padding,
20574 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20575 git_blame_entries_width,
20576 })
20577 }
20578
20579 pub fn render_crease_toggle(
20580 &self,
20581 buffer_row: MultiBufferRow,
20582 row_contains_cursor: bool,
20583 editor: Entity<Editor>,
20584 window: &mut Window,
20585 cx: &mut App,
20586 ) -> Option<AnyElement> {
20587 let folded = self.is_line_folded(buffer_row);
20588 let mut is_foldable = false;
20589
20590 if let Some(crease) = self
20591 .crease_snapshot
20592 .query_row(buffer_row, &self.buffer_snapshot)
20593 {
20594 is_foldable = true;
20595 match crease {
20596 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20597 if let Some(render_toggle) = render_toggle {
20598 let toggle_callback =
20599 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20600 if folded {
20601 editor.update(cx, |editor, cx| {
20602 editor.fold_at(buffer_row, window, cx)
20603 });
20604 } else {
20605 editor.update(cx, |editor, cx| {
20606 editor.unfold_at(buffer_row, window, cx)
20607 });
20608 }
20609 });
20610 return Some((render_toggle)(
20611 buffer_row,
20612 folded,
20613 toggle_callback,
20614 window,
20615 cx,
20616 ));
20617 }
20618 }
20619 }
20620 }
20621
20622 is_foldable |= self.starts_indent(buffer_row);
20623
20624 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20625 Some(
20626 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20627 .toggle_state(folded)
20628 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20629 if folded {
20630 this.unfold_at(buffer_row, window, cx);
20631 } else {
20632 this.fold_at(buffer_row, window, cx);
20633 }
20634 }))
20635 .into_any_element(),
20636 )
20637 } else {
20638 None
20639 }
20640 }
20641
20642 pub fn render_crease_trailer(
20643 &self,
20644 buffer_row: MultiBufferRow,
20645 window: &mut Window,
20646 cx: &mut App,
20647 ) -> Option<AnyElement> {
20648 let folded = self.is_line_folded(buffer_row);
20649 if let Crease::Inline { render_trailer, .. } = self
20650 .crease_snapshot
20651 .query_row(buffer_row, &self.buffer_snapshot)?
20652 {
20653 let render_trailer = render_trailer.as_ref()?;
20654 Some(render_trailer(buffer_row, folded, window, cx))
20655 } else {
20656 None
20657 }
20658 }
20659}
20660
20661impl Deref for EditorSnapshot {
20662 type Target = DisplaySnapshot;
20663
20664 fn deref(&self) -> &Self::Target {
20665 &self.display_snapshot
20666 }
20667}
20668
20669#[derive(Clone, Debug, PartialEq, Eq)]
20670pub enum EditorEvent {
20671 InputIgnored {
20672 text: Arc<str>,
20673 },
20674 InputHandled {
20675 utf16_range_to_replace: Option<Range<isize>>,
20676 text: Arc<str>,
20677 },
20678 ExcerptsAdded {
20679 buffer: Entity<Buffer>,
20680 predecessor: ExcerptId,
20681 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20682 },
20683 ExcerptsRemoved {
20684 ids: Vec<ExcerptId>,
20685 removed_buffer_ids: Vec<BufferId>,
20686 },
20687 BufferFoldToggled {
20688 ids: Vec<ExcerptId>,
20689 folded: bool,
20690 },
20691 ExcerptsEdited {
20692 ids: Vec<ExcerptId>,
20693 },
20694 ExcerptsExpanded {
20695 ids: Vec<ExcerptId>,
20696 },
20697 BufferEdited,
20698 Edited {
20699 transaction_id: clock::Lamport,
20700 },
20701 Reparsed(BufferId),
20702 Focused,
20703 FocusedIn,
20704 Blurred,
20705 DirtyChanged,
20706 Saved,
20707 TitleChanged,
20708 DiffBaseChanged,
20709 SelectionsChanged {
20710 local: bool,
20711 },
20712 ScrollPositionChanged {
20713 local: bool,
20714 autoscroll: bool,
20715 },
20716 Closed,
20717 TransactionUndone {
20718 transaction_id: clock::Lamport,
20719 },
20720 TransactionBegun {
20721 transaction_id: clock::Lamport,
20722 },
20723 Reloaded,
20724 CursorShapeChanged,
20725 PushedToNavHistory {
20726 anchor: Anchor,
20727 is_deactivate: bool,
20728 },
20729}
20730
20731impl EventEmitter<EditorEvent> for Editor {}
20732
20733impl Focusable for Editor {
20734 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20735 self.focus_handle.clone()
20736 }
20737}
20738
20739impl Render for Editor {
20740 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20741 let settings = ThemeSettings::get_global(cx);
20742
20743 let mut text_style = match self.mode {
20744 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20745 color: cx.theme().colors().editor_foreground,
20746 font_family: settings.ui_font.family.clone(),
20747 font_features: settings.ui_font.features.clone(),
20748 font_fallbacks: settings.ui_font.fallbacks.clone(),
20749 font_size: rems(0.875).into(),
20750 font_weight: settings.ui_font.weight,
20751 line_height: relative(settings.buffer_line_height.value()),
20752 ..Default::default()
20753 },
20754 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20755 color: cx.theme().colors().editor_foreground,
20756 font_family: settings.buffer_font.family.clone(),
20757 font_features: settings.buffer_font.features.clone(),
20758 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20759 font_size: settings.buffer_font_size(cx).into(),
20760 font_weight: settings.buffer_font.weight,
20761 line_height: relative(settings.buffer_line_height.value()),
20762 ..Default::default()
20763 },
20764 };
20765 if let Some(text_style_refinement) = &self.text_style_refinement {
20766 text_style.refine(text_style_refinement)
20767 }
20768
20769 let background = match self.mode {
20770 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20771 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20772 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20773 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20774 };
20775
20776 EditorElement::new(
20777 &cx.entity(),
20778 EditorStyle {
20779 background,
20780 local_player: cx.theme().players().local(),
20781 text: text_style,
20782 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20783 syntax: cx.theme().syntax().clone(),
20784 status: cx.theme().status().clone(),
20785 inlay_hints_style: make_inlay_hints_style(cx),
20786 inline_completion_styles: make_suggestion_styles(cx),
20787 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20788 show_underlines: !self.mode.is_minimap(),
20789 },
20790 )
20791 }
20792}
20793
20794impl EntityInputHandler for Editor {
20795 fn text_for_range(
20796 &mut self,
20797 range_utf16: Range<usize>,
20798 adjusted_range: &mut Option<Range<usize>>,
20799 _: &mut Window,
20800 cx: &mut Context<Self>,
20801 ) -> Option<String> {
20802 let snapshot = self.buffer.read(cx).read(cx);
20803 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20804 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20805 if (start.0..end.0) != range_utf16 {
20806 adjusted_range.replace(start.0..end.0);
20807 }
20808 Some(snapshot.text_for_range(start..end).collect())
20809 }
20810
20811 fn selected_text_range(
20812 &mut self,
20813 ignore_disabled_input: bool,
20814 _: &mut Window,
20815 cx: &mut Context<Self>,
20816 ) -> Option<UTF16Selection> {
20817 // Prevent the IME menu from appearing when holding down an alphabetic key
20818 // while input is disabled.
20819 if !ignore_disabled_input && !self.input_enabled {
20820 return None;
20821 }
20822
20823 let selection = self.selections.newest::<OffsetUtf16>(cx);
20824 let range = selection.range();
20825
20826 Some(UTF16Selection {
20827 range: range.start.0..range.end.0,
20828 reversed: selection.reversed,
20829 })
20830 }
20831
20832 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20833 let snapshot = self.buffer.read(cx).read(cx);
20834 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20835 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20836 }
20837
20838 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20839 self.clear_highlights::<InputComposition>(cx);
20840 self.ime_transaction.take();
20841 }
20842
20843 fn replace_text_in_range(
20844 &mut self,
20845 range_utf16: Option<Range<usize>>,
20846 text: &str,
20847 window: &mut Window,
20848 cx: &mut Context<Self>,
20849 ) {
20850 if !self.input_enabled {
20851 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20852 return;
20853 }
20854
20855 self.transact(window, cx, |this, window, cx| {
20856 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20857 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20858 Some(this.selection_replacement_ranges(range_utf16, cx))
20859 } else {
20860 this.marked_text_ranges(cx)
20861 };
20862
20863 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20864 let newest_selection_id = this.selections.newest_anchor().id;
20865 this.selections
20866 .all::<OffsetUtf16>(cx)
20867 .iter()
20868 .zip(ranges_to_replace.iter())
20869 .find_map(|(selection, range)| {
20870 if selection.id == newest_selection_id {
20871 Some(
20872 (range.start.0 as isize - selection.head().0 as isize)
20873 ..(range.end.0 as isize - selection.head().0 as isize),
20874 )
20875 } else {
20876 None
20877 }
20878 })
20879 });
20880
20881 cx.emit(EditorEvent::InputHandled {
20882 utf16_range_to_replace: range_to_replace,
20883 text: text.into(),
20884 });
20885
20886 if let Some(new_selected_ranges) = new_selected_ranges {
20887 this.change_selections(None, window, cx, |selections| {
20888 selections.select_ranges(new_selected_ranges)
20889 });
20890 this.backspace(&Default::default(), window, cx);
20891 }
20892
20893 this.handle_input(text, window, cx);
20894 });
20895
20896 if let Some(transaction) = self.ime_transaction {
20897 self.buffer.update(cx, |buffer, cx| {
20898 buffer.group_until_transaction(transaction, cx);
20899 });
20900 }
20901
20902 self.unmark_text(window, cx);
20903 }
20904
20905 fn replace_and_mark_text_in_range(
20906 &mut self,
20907 range_utf16: Option<Range<usize>>,
20908 text: &str,
20909 new_selected_range_utf16: Option<Range<usize>>,
20910 window: &mut Window,
20911 cx: &mut Context<Self>,
20912 ) {
20913 if !self.input_enabled {
20914 return;
20915 }
20916
20917 let transaction = self.transact(window, cx, |this, window, cx| {
20918 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20919 let snapshot = this.buffer.read(cx).read(cx);
20920 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20921 for marked_range in &mut marked_ranges {
20922 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20923 marked_range.start.0 += relative_range_utf16.start;
20924 marked_range.start =
20925 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20926 marked_range.end =
20927 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20928 }
20929 }
20930 Some(marked_ranges)
20931 } else if let Some(range_utf16) = range_utf16 {
20932 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20933 Some(this.selection_replacement_ranges(range_utf16, cx))
20934 } else {
20935 None
20936 };
20937
20938 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20939 let newest_selection_id = this.selections.newest_anchor().id;
20940 this.selections
20941 .all::<OffsetUtf16>(cx)
20942 .iter()
20943 .zip(ranges_to_replace.iter())
20944 .find_map(|(selection, range)| {
20945 if selection.id == newest_selection_id {
20946 Some(
20947 (range.start.0 as isize - selection.head().0 as isize)
20948 ..(range.end.0 as isize - selection.head().0 as isize),
20949 )
20950 } else {
20951 None
20952 }
20953 })
20954 });
20955
20956 cx.emit(EditorEvent::InputHandled {
20957 utf16_range_to_replace: range_to_replace,
20958 text: text.into(),
20959 });
20960
20961 if let Some(ranges) = ranges_to_replace {
20962 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20963 }
20964
20965 let marked_ranges = {
20966 let snapshot = this.buffer.read(cx).read(cx);
20967 this.selections
20968 .disjoint_anchors()
20969 .iter()
20970 .map(|selection| {
20971 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20972 })
20973 .collect::<Vec<_>>()
20974 };
20975
20976 if text.is_empty() {
20977 this.unmark_text(window, cx);
20978 } else {
20979 this.highlight_text::<InputComposition>(
20980 marked_ranges.clone(),
20981 HighlightStyle {
20982 underline: Some(UnderlineStyle {
20983 thickness: px(1.),
20984 color: None,
20985 wavy: false,
20986 }),
20987 ..Default::default()
20988 },
20989 cx,
20990 );
20991 }
20992
20993 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20994 let use_autoclose = this.use_autoclose;
20995 let use_auto_surround = this.use_auto_surround;
20996 this.set_use_autoclose(false);
20997 this.set_use_auto_surround(false);
20998 this.handle_input(text, window, cx);
20999 this.set_use_autoclose(use_autoclose);
21000 this.set_use_auto_surround(use_auto_surround);
21001
21002 if let Some(new_selected_range) = new_selected_range_utf16 {
21003 let snapshot = this.buffer.read(cx).read(cx);
21004 let new_selected_ranges = marked_ranges
21005 .into_iter()
21006 .map(|marked_range| {
21007 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
21008 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
21009 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
21010 snapshot.clip_offset_utf16(new_start, Bias::Left)
21011 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
21012 })
21013 .collect::<Vec<_>>();
21014
21015 drop(snapshot);
21016 this.change_selections(None, window, cx, |selections| {
21017 selections.select_ranges(new_selected_ranges)
21018 });
21019 }
21020 });
21021
21022 self.ime_transaction = self.ime_transaction.or(transaction);
21023 if let Some(transaction) = self.ime_transaction {
21024 self.buffer.update(cx, |buffer, cx| {
21025 buffer.group_until_transaction(transaction, cx);
21026 });
21027 }
21028
21029 if self.text_highlights::<InputComposition>(cx).is_none() {
21030 self.ime_transaction.take();
21031 }
21032 }
21033
21034 fn bounds_for_range(
21035 &mut self,
21036 range_utf16: Range<usize>,
21037 element_bounds: gpui::Bounds<Pixels>,
21038 window: &mut Window,
21039 cx: &mut Context<Self>,
21040 ) -> Option<gpui::Bounds<Pixels>> {
21041 let text_layout_details = self.text_layout_details(window);
21042 let gpui::Size {
21043 width: em_width,
21044 height: line_height,
21045 } = self.character_size(window);
21046
21047 let snapshot = self.snapshot(window, cx);
21048 let scroll_position = snapshot.scroll_position();
21049 let scroll_left = scroll_position.x * em_width;
21050
21051 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
21052 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
21053 + self.gutter_dimensions.width
21054 + self.gutter_dimensions.margin;
21055 let y = line_height * (start.row().as_f32() - scroll_position.y);
21056
21057 Some(Bounds {
21058 origin: element_bounds.origin + point(x, y),
21059 size: size(em_width, line_height),
21060 })
21061 }
21062
21063 fn character_index_for_point(
21064 &mut self,
21065 point: gpui::Point<Pixels>,
21066 _window: &mut Window,
21067 _cx: &mut Context<Self>,
21068 ) -> Option<usize> {
21069 let position_map = self.last_position_map.as_ref()?;
21070 if !position_map.text_hitbox.contains(&point) {
21071 return None;
21072 }
21073 let display_point = position_map.point_for_position(point).previous_valid;
21074 let anchor = position_map
21075 .snapshot
21076 .display_point_to_anchor(display_point, Bias::Left);
21077 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
21078 Some(utf16_offset.0)
21079 }
21080}
21081
21082trait SelectionExt {
21083 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
21084 fn spanned_rows(
21085 &self,
21086 include_end_if_at_line_start: bool,
21087 map: &DisplaySnapshot,
21088 ) -> Range<MultiBufferRow>;
21089}
21090
21091impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
21092 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
21093 let start = self
21094 .start
21095 .to_point(&map.buffer_snapshot)
21096 .to_display_point(map);
21097 let end = self
21098 .end
21099 .to_point(&map.buffer_snapshot)
21100 .to_display_point(map);
21101 if self.reversed {
21102 end..start
21103 } else {
21104 start..end
21105 }
21106 }
21107
21108 fn spanned_rows(
21109 &self,
21110 include_end_if_at_line_start: bool,
21111 map: &DisplaySnapshot,
21112 ) -> Range<MultiBufferRow> {
21113 let start = self.start.to_point(&map.buffer_snapshot);
21114 let mut end = self.end.to_point(&map.buffer_snapshot);
21115 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21116 end.row -= 1;
21117 }
21118
21119 let buffer_start = map.prev_line_boundary(start).0;
21120 let buffer_end = map.next_line_boundary(end).0;
21121 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21122 }
21123}
21124
21125impl<T: InvalidationRegion> InvalidationStack<T> {
21126 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21127 where
21128 S: Clone + ToOffset,
21129 {
21130 while let Some(region) = self.last() {
21131 let all_selections_inside_invalidation_ranges =
21132 if selections.len() == region.ranges().len() {
21133 selections
21134 .iter()
21135 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21136 .all(|(selection, invalidation_range)| {
21137 let head = selection.head().to_offset(buffer);
21138 invalidation_range.start <= head && invalidation_range.end >= head
21139 })
21140 } else {
21141 false
21142 };
21143
21144 if all_selections_inside_invalidation_ranges {
21145 break;
21146 } else {
21147 self.pop();
21148 }
21149 }
21150 }
21151}
21152
21153impl<T> Default for InvalidationStack<T> {
21154 fn default() -> Self {
21155 Self(Default::default())
21156 }
21157}
21158
21159impl<T> Deref for InvalidationStack<T> {
21160 type Target = Vec<T>;
21161
21162 fn deref(&self) -> &Self::Target {
21163 &self.0
21164 }
21165}
21166
21167impl<T> DerefMut for InvalidationStack<T> {
21168 fn deref_mut(&mut self) -> &mut Self::Target {
21169 &mut self.0
21170 }
21171}
21172
21173impl InvalidationRegion for SnippetState {
21174 fn ranges(&self) -> &[Range<Anchor>] {
21175 &self.ranges[self.active_index]
21176 }
21177}
21178
21179fn inline_completion_edit_text(
21180 current_snapshot: &BufferSnapshot,
21181 edits: &[(Range<Anchor>, String)],
21182 edit_preview: &EditPreview,
21183 include_deletions: bool,
21184 cx: &App,
21185) -> HighlightedText {
21186 let edits = edits
21187 .iter()
21188 .map(|(anchor, text)| {
21189 (
21190 anchor.start.text_anchor..anchor.end.text_anchor,
21191 text.clone(),
21192 )
21193 })
21194 .collect::<Vec<_>>();
21195
21196 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21197}
21198
21199pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21200 match severity {
21201 lsp::DiagnosticSeverity::ERROR => colors.error,
21202 lsp::DiagnosticSeverity::WARNING => colors.warning,
21203 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21204 lsp::DiagnosticSeverity::HINT => colors.info,
21205 _ => colors.ignored,
21206 }
21207}
21208
21209pub fn styled_runs_for_code_label<'a>(
21210 label: &'a CodeLabel,
21211 syntax_theme: &'a theme::SyntaxTheme,
21212) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21213 let fade_out = HighlightStyle {
21214 fade_out: Some(0.35),
21215 ..Default::default()
21216 };
21217
21218 let mut prev_end = label.filter_range.end;
21219 label
21220 .runs
21221 .iter()
21222 .enumerate()
21223 .flat_map(move |(ix, (range, highlight_id))| {
21224 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21225 style
21226 } else {
21227 return Default::default();
21228 };
21229 let mut muted_style = style;
21230 muted_style.highlight(fade_out);
21231
21232 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21233 if range.start >= label.filter_range.end {
21234 if range.start > prev_end {
21235 runs.push((prev_end..range.start, fade_out));
21236 }
21237 runs.push((range.clone(), muted_style));
21238 } else if range.end <= label.filter_range.end {
21239 runs.push((range.clone(), style));
21240 } else {
21241 runs.push((range.start..label.filter_range.end, style));
21242 runs.push((label.filter_range.end..range.end, muted_style));
21243 }
21244 prev_end = cmp::max(prev_end, range.end);
21245
21246 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21247 runs.push((prev_end..label.text.len(), fade_out));
21248 }
21249
21250 runs
21251 })
21252}
21253
21254pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21255 let mut prev_index = 0;
21256 let mut prev_codepoint: Option<char> = None;
21257 text.char_indices()
21258 .chain([(text.len(), '\0')])
21259 .filter_map(move |(index, codepoint)| {
21260 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21261 let is_boundary = index == text.len()
21262 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21263 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21264 if is_boundary {
21265 let chunk = &text[prev_index..index];
21266 prev_index = index;
21267 Some(chunk)
21268 } else {
21269 None
21270 }
21271 })
21272}
21273
21274pub trait RangeToAnchorExt: Sized {
21275 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21276
21277 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21278 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21279 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21280 }
21281}
21282
21283impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21284 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21285 let start_offset = self.start.to_offset(snapshot);
21286 let end_offset = self.end.to_offset(snapshot);
21287 if start_offset == end_offset {
21288 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21289 } else {
21290 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21291 }
21292 }
21293}
21294
21295pub trait RowExt {
21296 fn as_f32(&self) -> f32;
21297
21298 fn next_row(&self) -> Self;
21299
21300 fn previous_row(&self) -> Self;
21301
21302 fn minus(&self, other: Self) -> u32;
21303}
21304
21305impl RowExt for DisplayRow {
21306 fn as_f32(&self) -> f32 {
21307 self.0 as f32
21308 }
21309
21310 fn next_row(&self) -> Self {
21311 Self(self.0 + 1)
21312 }
21313
21314 fn previous_row(&self) -> Self {
21315 Self(self.0.saturating_sub(1))
21316 }
21317
21318 fn minus(&self, other: Self) -> u32 {
21319 self.0 - other.0
21320 }
21321}
21322
21323impl RowExt for MultiBufferRow {
21324 fn as_f32(&self) -> f32 {
21325 self.0 as f32
21326 }
21327
21328 fn next_row(&self) -> Self {
21329 Self(self.0 + 1)
21330 }
21331
21332 fn previous_row(&self) -> Self {
21333 Self(self.0.saturating_sub(1))
21334 }
21335
21336 fn minus(&self, other: Self) -> u32 {
21337 self.0 - other.0
21338 }
21339}
21340
21341trait RowRangeExt {
21342 type Row;
21343
21344 fn len(&self) -> usize;
21345
21346 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21347}
21348
21349impl RowRangeExt for Range<MultiBufferRow> {
21350 type Row = MultiBufferRow;
21351
21352 fn len(&self) -> usize {
21353 (self.end.0 - self.start.0) as usize
21354 }
21355
21356 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21357 (self.start.0..self.end.0).map(MultiBufferRow)
21358 }
21359}
21360
21361impl RowRangeExt for Range<DisplayRow> {
21362 type Row = DisplayRow;
21363
21364 fn len(&self) -> usize {
21365 (self.end.0 - self.start.0) as usize
21366 }
21367
21368 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21369 (self.start.0..self.end.0).map(DisplayRow)
21370 }
21371}
21372
21373/// If select range has more than one line, we
21374/// just point the cursor to range.start.
21375fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21376 if range.start.row == range.end.row {
21377 range
21378 } else {
21379 range.start..range.start
21380 }
21381}
21382pub struct KillRing(ClipboardItem);
21383impl Global for KillRing {}
21384
21385const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21386
21387enum BreakpointPromptEditAction {
21388 Log,
21389 Condition,
21390 HitCondition,
21391}
21392
21393struct BreakpointPromptEditor {
21394 pub(crate) prompt: Entity<Editor>,
21395 editor: WeakEntity<Editor>,
21396 breakpoint_anchor: Anchor,
21397 breakpoint: Breakpoint,
21398 edit_action: BreakpointPromptEditAction,
21399 block_ids: HashSet<CustomBlockId>,
21400 editor_margins: Arc<Mutex<EditorMargins>>,
21401 _subscriptions: Vec<Subscription>,
21402}
21403
21404impl BreakpointPromptEditor {
21405 const MAX_LINES: u8 = 4;
21406
21407 fn new(
21408 editor: WeakEntity<Editor>,
21409 breakpoint_anchor: Anchor,
21410 breakpoint: Breakpoint,
21411 edit_action: BreakpointPromptEditAction,
21412 window: &mut Window,
21413 cx: &mut Context<Self>,
21414 ) -> Self {
21415 let base_text = match edit_action {
21416 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21417 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21418 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21419 }
21420 .map(|msg| msg.to_string())
21421 .unwrap_or_default();
21422
21423 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21424 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21425
21426 let prompt = cx.new(|cx| {
21427 let mut prompt = Editor::new(
21428 EditorMode::AutoHeight {
21429 max_lines: Self::MAX_LINES as usize,
21430 },
21431 buffer,
21432 None,
21433 window,
21434 cx,
21435 );
21436 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21437 prompt.set_show_cursor_when_unfocused(false, cx);
21438 prompt.set_placeholder_text(
21439 match edit_action {
21440 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21441 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21442 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21443 },
21444 cx,
21445 );
21446
21447 prompt
21448 });
21449
21450 Self {
21451 prompt,
21452 editor,
21453 breakpoint_anchor,
21454 breakpoint,
21455 edit_action,
21456 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21457 block_ids: Default::default(),
21458 _subscriptions: vec![],
21459 }
21460 }
21461
21462 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21463 self.block_ids.extend(block_ids)
21464 }
21465
21466 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21467 if let Some(editor) = self.editor.upgrade() {
21468 let message = self
21469 .prompt
21470 .read(cx)
21471 .buffer
21472 .read(cx)
21473 .as_singleton()
21474 .expect("A multi buffer in breakpoint prompt isn't possible")
21475 .read(cx)
21476 .as_rope()
21477 .to_string();
21478
21479 editor.update(cx, |editor, cx| {
21480 editor.edit_breakpoint_at_anchor(
21481 self.breakpoint_anchor,
21482 self.breakpoint.clone(),
21483 match self.edit_action {
21484 BreakpointPromptEditAction::Log => {
21485 BreakpointEditAction::EditLogMessage(message.into())
21486 }
21487 BreakpointPromptEditAction::Condition => {
21488 BreakpointEditAction::EditCondition(message.into())
21489 }
21490 BreakpointPromptEditAction::HitCondition => {
21491 BreakpointEditAction::EditHitCondition(message.into())
21492 }
21493 },
21494 cx,
21495 );
21496
21497 editor.remove_blocks(self.block_ids.clone(), None, cx);
21498 cx.focus_self(window);
21499 });
21500 }
21501 }
21502
21503 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21504 self.editor
21505 .update(cx, |editor, cx| {
21506 editor.remove_blocks(self.block_ids.clone(), None, cx);
21507 window.focus(&editor.focus_handle);
21508 })
21509 .log_err();
21510 }
21511
21512 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21513 let settings = ThemeSettings::get_global(cx);
21514 let text_style = TextStyle {
21515 color: if self.prompt.read(cx).read_only(cx) {
21516 cx.theme().colors().text_disabled
21517 } else {
21518 cx.theme().colors().text
21519 },
21520 font_family: settings.buffer_font.family.clone(),
21521 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21522 font_size: settings.buffer_font_size(cx).into(),
21523 font_weight: settings.buffer_font.weight,
21524 line_height: relative(settings.buffer_line_height.value()),
21525 ..Default::default()
21526 };
21527 EditorElement::new(
21528 &self.prompt,
21529 EditorStyle {
21530 background: cx.theme().colors().editor_background,
21531 local_player: cx.theme().players().local(),
21532 text: text_style,
21533 ..Default::default()
21534 },
21535 )
21536 }
21537}
21538
21539impl Render for BreakpointPromptEditor {
21540 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21541 let editor_margins = *self.editor_margins.lock();
21542 let gutter_dimensions = editor_margins.gutter;
21543 h_flex()
21544 .key_context("Editor")
21545 .bg(cx.theme().colors().editor_background)
21546 .border_y_1()
21547 .border_color(cx.theme().status().info_border)
21548 .size_full()
21549 .py(window.line_height() / 2.5)
21550 .on_action(cx.listener(Self::confirm))
21551 .on_action(cx.listener(Self::cancel))
21552 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21553 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21554 }
21555}
21556
21557impl Focusable for BreakpointPromptEditor {
21558 fn focus_handle(&self, cx: &App) -> FocusHandle {
21559 self.prompt.focus_handle(cx)
21560 }
21561}
21562
21563fn all_edits_insertions_or_deletions(
21564 edits: &Vec<(Range<Anchor>, String)>,
21565 snapshot: &MultiBufferSnapshot,
21566) -> bool {
21567 let mut all_insertions = true;
21568 let mut all_deletions = true;
21569
21570 for (range, new_text) in edits.iter() {
21571 let range_is_empty = range.to_offset(&snapshot).is_empty();
21572 let text_is_empty = new_text.is_empty();
21573
21574 if range_is_empty != text_is_empty {
21575 if range_is_empty {
21576 all_deletions = false;
21577 } else {
21578 all_insertions = false;
21579 }
21580 } else {
21581 return false;
21582 }
21583
21584 if !all_insertions && !all_deletions {
21585 return false;
21586 }
21587 }
21588 all_insertions || all_deletions
21589}
21590
21591struct MissingEditPredictionKeybindingTooltip;
21592
21593impl Render for MissingEditPredictionKeybindingTooltip {
21594 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21595 ui::tooltip_container(window, cx, |container, _, cx| {
21596 container
21597 .flex_shrink_0()
21598 .max_w_80()
21599 .min_h(rems_from_px(124.))
21600 .justify_between()
21601 .child(
21602 v_flex()
21603 .flex_1()
21604 .text_ui_sm(cx)
21605 .child(Label::new("Conflict with Accept Keybinding"))
21606 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21607 )
21608 .child(
21609 h_flex()
21610 .pb_1()
21611 .gap_1()
21612 .items_end()
21613 .w_full()
21614 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21615 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21616 }))
21617 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21618 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21619 })),
21620 )
21621 })
21622 }
21623}
21624
21625#[derive(Debug, Clone, Copy, PartialEq)]
21626pub struct LineHighlight {
21627 pub background: Background,
21628 pub border: Option<gpui::Hsla>,
21629 pub include_gutter: bool,
21630 pub type_id: Option<TypeId>,
21631}
21632
21633fn render_diff_hunk_controls(
21634 row: u32,
21635 status: &DiffHunkStatus,
21636 hunk_range: Range<Anchor>,
21637 is_created_file: bool,
21638 line_height: Pixels,
21639 editor: &Entity<Editor>,
21640 _window: &mut Window,
21641 cx: &mut App,
21642) -> AnyElement {
21643 h_flex()
21644 .h(line_height)
21645 .mr_1()
21646 .gap_1()
21647 .px_0p5()
21648 .pb_1()
21649 .border_x_1()
21650 .border_b_1()
21651 .border_color(cx.theme().colors().border_variant)
21652 .rounded_b_lg()
21653 .bg(cx.theme().colors().editor_background)
21654 .gap_1()
21655 .occlude()
21656 .shadow_md()
21657 .child(if status.has_secondary_hunk() {
21658 Button::new(("stage", row as u64), "Stage")
21659 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21660 .tooltip({
21661 let focus_handle = editor.focus_handle(cx);
21662 move |window, cx| {
21663 Tooltip::for_action_in(
21664 "Stage Hunk",
21665 &::git::ToggleStaged,
21666 &focus_handle,
21667 window,
21668 cx,
21669 )
21670 }
21671 })
21672 .on_click({
21673 let editor = editor.clone();
21674 move |_event, _window, cx| {
21675 editor.update(cx, |editor, cx| {
21676 editor.stage_or_unstage_diff_hunks(
21677 true,
21678 vec![hunk_range.start..hunk_range.start],
21679 cx,
21680 );
21681 });
21682 }
21683 })
21684 } else {
21685 Button::new(("unstage", row as u64), "Unstage")
21686 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21687 .tooltip({
21688 let focus_handle = editor.focus_handle(cx);
21689 move |window, cx| {
21690 Tooltip::for_action_in(
21691 "Unstage Hunk",
21692 &::git::ToggleStaged,
21693 &focus_handle,
21694 window,
21695 cx,
21696 )
21697 }
21698 })
21699 .on_click({
21700 let editor = editor.clone();
21701 move |_event, _window, cx| {
21702 editor.update(cx, |editor, cx| {
21703 editor.stage_or_unstage_diff_hunks(
21704 false,
21705 vec![hunk_range.start..hunk_range.start],
21706 cx,
21707 );
21708 });
21709 }
21710 })
21711 })
21712 .child(
21713 Button::new(("restore", row as u64), "Restore")
21714 .tooltip({
21715 let focus_handle = editor.focus_handle(cx);
21716 move |window, cx| {
21717 Tooltip::for_action_in(
21718 "Restore Hunk",
21719 &::git::Restore,
21720 &focus_handle,
21721 window,
21722 cx,
21723 )
21724 }
21725 })
21726 .on_click({
21727 let editor = editor.clone();
21728 move |_event, window, cx| {
21729 editor.update(cx, |editor, cx| {
21730 let snapshot = editor.snapshot(window, cx);
21731 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21732 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21733 });
21734 }
21735 })
21736 .disabled(is_created_file),
21737 )
21738 .when(
21739 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21740 |el| {
21741 el.child(
21742 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21743 .shape(IconButtonShape::Square)
21744 .icon_size(IconSize::Small)
21745 // .disabled(!has_multiple_hunks)
21746 .tooltip({
21747 let focus_handle = editor.focus_handle(cx);
21748 move |window, cx| {
21749 Tooltip::for_action_in(
21750 "Next Hunk",
21751 &GoToHunk,
21752 &focus_handle,
21753 window,
21754 cx,
21755 )
21756 }
21757 })
21758 .on_click({
21759 let editor = editor.clone();
21760 move |_event, window, cx| {
21761 editor.update(cx, |editor, cx| {
21762 let snapshot = editor.snapshot(window, cx);
21763 let position =
21764 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21765 editor.go_to_hunk_before_or_after_position(
21766 &snapshot,
21767 position,
21768 Direction::Next,
21769 window,
21770 cx,
21771 );
21772 editor.expand_selected_diff_hunks(cx);
21773 });
21774 }
21775 }),
21776 )
21777 .child(
21778 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21779 .shape(IconButtonShape::Square)
21780 .icon_size(IconSize::Small)
21781 // .disabled(!has_multiple_hunks)
21782 .tooltip({
21783 let focus_handle = editor.focus_handle(cx);
21784 move |window, cx| {
21785 Tooltip::for_action_in(
21786 "Previous Hunk",
21787 &GoToPreviousHunk,
21788 &focus_handle,
21789 window,
21790 cx,
21791 )
21792 }
21793 })
21794 .on_click({
21795 let editor = editor.clone();
21796 move |_event, window, cx| {
21797 editor.update(cx, |editor, cx| {
21798 let snapshot = editor.snapshot(window, cx);
21799 let point =
21800 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21801 editor.go_to_hunk_before_or_after_position(
21802 &snapshot,
21803 point,
21804 Direction::Prev,
21805 window,
21806 cx,
21807 );
21808 editor.expand_selected_diff_hunks(cx);
21809 });
21810 }
21811 }),
21812 )
21813 },
21814 )
21815 .into_any_element()
21816}