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 editor_tests;
44#[cfg(test)]
45mod inline_completion_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
52use aho_corasick::AhoCorasick;
53use anyhow::{Context as _, Result, anyhow};
54use blink_manager::BlinkManager;
55use buffer_diff::DiffHunkStatus;
56use client::{Collaborator, ParticipantIndex};
57use clock::ReplicaId;
58use collections::{BTreeMap, HashMap, HashSet, VecDeque};
59use convert_case::{Case, Casing};
60use display_map::*;
61pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
62use editor_settings::GoToDefinitionFallback;
63pub use editor_settings::{
64 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
65 ShowScrollbar,
66};
67pub use editor_settings_controls::*;
68use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
69pub use element::{
70 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
71};
72use feature_flags::{Debugger, FeatureFlagAppExt};
73use futures::{
74 FutureExt,
75 future::{self, Shared, join},
76};
77use fuzzy::StringMatchCandidate;
78
79use ::git::Restore;
80use code_context_menus::{
81 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
82 CompletionsMenu, ContextMenuOrigin,
83};
84use git::blame::{GitBlame, GlobalBlameRenderer};
85use gpui::{
86 Action, Animation, AnimationExt, AnyElement, AnyWeakEntity, App, AppContext,
87 AsyncWindowContext, AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry,
88 ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter,
89 FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla,
90 KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render,
91 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
92 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
93 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
94};
95use highlight_matching_bracket::refresh_matching_bracket_highlights;
96use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
97pub use hover_popover::hover_markdown_style;
98use hover_popover::{HoverState, hide_hover};
99use indent_guides::ActiveIndentGuidesState;
100use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
101pub use inline_completion::Direction;
102use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
103pub use items::MAX_TAB_TITLE_LEN;
104use itertools::Itertools;
105use language::{
106 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
107 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
108 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
109 TransactionId, TreeSitterOptions, WordsQuery,
110 language_settings::{
111 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
112 all_language_settings, language_settings,
113 },
114 point_from_lsp, text_diff_with_options,
115};
116use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
117use linked_editing_ranges::refresh_linked_ranges;
118use mouse_context_menu::MouseContextMenu;
119use persistence::DB;
120use project::{
121 ProjectPath,
122 debugger::breakpoint_store::{
123 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
124 },
125};
126
127pub use git::blame::BlameRenderer;
128pub use proposed_changes_editor::{
129 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
130};
131use smallvec::smallvec;
132use std::{cell::OnceCell, iter::Peekable};
133use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
134
135pub use lsp::CompletionContext;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
138 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
139};
140
141use language::BufferSnapshot;
142pub use lsp_ext::lsp_tasks;
143use movement::TextLayoutDetails;
144pub use multi_buffer::{
145 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
146 RowInfo, ToOffset, ToPoint,
147};
148use multi_buffer::{
149 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
150 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
151};
152use parking_lot::Mutex;
153use project::{
154 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
155 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
156 TaskSourceKind,
157 debugger::breakpoint_store::Breakpoint,
158 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
159 project_settings::{GitGutterSetting, ProjectSettings},
160};
161use rand::prelude::*;
162use rpc::{ErrorExt, proto::*};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
164use selections_collection::{
165 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
166};
167use serde::{Deserialize, Serialize};
168use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
169use smallvec::SmallVec;
170use snippet::Snippet;
171use std::sync::Arc;
172use std::{
173 any::TypeId,
174 borrow::Cow,
175 cell::RefCell,
176 cmp::{self, Ordering, Reverse},
177 mem,
178 num::NonZeroU32,
179 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
180 path::{Path, PathBuf},
181 rc::Rc,
182 time::{Duration, Instant},
183};
184pub use sum_tree::Bias;
185use sum_tree::TreeMap;
186use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
187use theme::{
188 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
189 observe_buffer_font_size_adjustment,
190};
191use ui::{
192 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
193 IconSize, Key, Tooltip, h_flex, prelude::*,
194};
195use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
196use workspace::{
197 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
198 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
199 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
200 item::{ItemHandle, PreviewTabsSettings},
201 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
202 searchable::SearchEvent,
203};
204
205use crate::hover_links::{find_url, find_url_from_range};
206use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
207
208pub const FILE_HEADER_HEIGHT: u32 = 2;
209pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
210pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
211const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
212const MAX_LINE_LEN: usize = 1024;
213const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
214const MAX_SELECTION_HISTORY_LEN: usize = 1024;
215pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
216#[doc(hidden)]
217pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
218
219pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
220pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
221pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
222
223pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
224pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
225pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
226
227pub type RenderDiffHunkControlsFn = Arc<
228 dyn Fn(
229 u32,
230 &DiffHunkStatus,
231 Range<Anchor>,
232 bool,
233 Pixels,
234 &Entity<Editor>,
235 &mut Window,
236 &mut App,
237 ) -> AnyElement,
238>;
239
240const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
241 alt: true,
242 shift: true,
243 control: false,
244 platform: false,
245 function: false,
246};
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
249pub enum InlayId {
250 InlineCompletion(usize),
251 Hint(usize),
252}
253
254impl InlayId {
255 fn id(&self) -> usize {
256 match self {
257 Self::InlineCompletion(id) => *id,
258 Self::Hint(id) => *id,
259 }
260 }
261}
262
263pub enum DebugCurrentRowHighlight {}
264enum DocumentHighlightRead {}
265enum DocumentHighlightWrite {}
266enum InputComposition {}
267enum SelectedTextHighlight {}
268
269#[derive(Debug, Copy, Clone, PartialEq, Eq)]
270pub enum Navigated {
271 Yes,
272 No,
273}
274
275impl Navigated {
276 pub fn from_bool(yes: bool) -> Navigated {
277 if yes { Navigated::Yes } else { Navigated::No }
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Eq)]
282enum DisplayDiffHunk {
283 Folded {
284 display_row: DisplayRow,
285 },
286 Unfolded {
287 is_created_file: bool,
288 diff_base_byte_range: Range<usize>,
289 display_row_range: Range<DisplayRow>,
290 multi_buffer_range: Range<Anchor>,
291 status: DiffHunkStatus,
292 },
293}
294
295pub enum HideMouseCursorOrigin {
296 TypingAction,
297 MovementAction,
298}
299
300pub fn init_settings(cx: &mut App) {
301 EditorSettings::register(cx);
302}
303
304pub fn init(cx: &mut App) {
305 init_settings(cx);
306
307 cx.set_global(GlobalBlameRenderer(Arc::new(())));
308
309 workspace::register_project_item::<Editor>(cx);
310 workspace::FollowableViewRegistry::register::<Editor>(cx);
311 workspace::register_serializable_item::<Editor>(cx);
312
313 cx.observe_new(
314 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
315 workspace.register_action(Editor::new_file);
316 workspace.register_action(Editor::new_file_vertical);
317 workspace.register_action(Editor::new_file_horizontal);
318 workspace.register_action(Editor::cancel_language_server_work);
319 },
320 )
321 .detach();
322
323 cx.on_action(move |_: &workspace::NewFile, cx| {
324 let app_state = workspace::AppState::global(cx);
325 if let Some(app_state) = app_state.upgrade() {
326 workspace::open_new(
327 Default::default(),
328 app_state,
329 cx,
330 |workspace, window, cx| {
331 Editor::new_file(workspace, &Default::default(), window, cx)
332 },
333 )
334 .detach();
335 }
336 });
337 cx.on_action(move |_: &workspace::NewWindow, cx| {
338 let app_state = workspace::AppState::global(cx);
339 if let Some(app_state) = app_state.upgrade() {
340 workspace::open_new(
341 Default::default(),
342 app_state,
343 cx,
344 |workspace, window, cx| {
345 cx.activate(true);
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352}
353
354pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
355 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
356}
357
358pub trait DiagnosticRenderer {
359 fn render_group(
360 &self,
361 diagnostic_group: Vec<DiagnosticEntry<Point>>,
362 buffer_id: BufferId,
363 snapshot: EditorSnapshot,
364 editor: WeakEntity<Editor>,
365 cx: &mut App,
366 ) -> Vec<BlockProperties<Anchor>>;
367}
368
369pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
370
371impl gpui::Global for GlobalDiagnosticRenderer {}
372pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
374}
375
376pub struct SearchWithinRange;
377
378trait InvalidationRegion {
379 fn ranges(&self) -> &[Range<Anchor>];
380}
381
382#[derive(Clone, Debug, PartialEq)]
383pub enum SelectPhase {
384 Begin {
385 position: DisplayPoint,
386 add: bool,
387 click_count: usize,
388 },
389 BeginColumnar {
390 position: DisplayPoint,
391 reset: bool,
392 goal_column: u32,
393 },
394 Extend {
395 position: DisplayPoint,
396 click_count: usize,
397 },
398 Update {
399 position: DisplayPoint,
400 goal_column: u32,
401 scroll_delta: gpui::Point<f32>,
402 },
403 End,
404}
405
406#[derive(Clone, Debug)]
407pub enum SelectMode {
408 Character,
409 Word(Range<Anchor>),
410 Line(Range<Anchor>),
411 All,
412}
413
414#[derive(Copy, Clone, PartialEq, Eq, Debug)]
415pub enum EditorMode {
416 SingleLine {
417 auto_width: bool,
418 },
419 AutoHeight {
420 max_lines: usize,
421 },
422 Full {
423 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
424 scale_ui_elements_with_buffer_font_size: bool,
425 /// When set to `true`, the editor will render a background for the active line.
426 show_active_line_background: bool,
427 },
428}
429
430impl EditorMode {
431 pub fn full() -> Self {
432 Self::Full {
433 scale_ui_elements_with_buffer_font_size: true,
434 show_active_line_background: true,
435 }
436 }
437
438 pub fn is_full(&self) -> bool {
439 matches!(self, Self::Full { .. })
440 }
441}
442
443#[derive(Copy, Clone, Debug)]
444pub enum SoftWrap {
445 /// Prefer not to wrap at all.
446 ///
447 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
448 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
449 GitDiff,
450 /// Prefer a single line generally, unless an overly long line is encountered.
451 None,
452 /// Soft wrap lines that exceed the editor width.
453 EditorWidth,
454 /// Soft wrap lines at the preferred line length.
455 Column(u32),
456 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
457 Bounded(u32),
458}
459
460#[derive(Clone)]
461pub struct EditorStyle {
462 pub background: Hsla,
463 pub local_player: PlayerColor,
464 pub text: TextStyle,
465 pub scrollbar_width: Pixels,
466 pub syntax: Arc<SyntaxTheme>,
467 pub status: StatusColors,
468 pub inlay_hints_style: HighlightStyle,
469 pub inline_completion_styles: InlineCompletionStyles,
470 pub unnecessary_code_fade: f32,
471}
472
473impl Default for EditorStyle {
474 fn default() -> Self {
475 Self {
476 background: Hsla::default(),
477 local_player: PlayerColor::default(),
478 text: TextStyle::default(),
479 scrollbar_width: Pixels::default(),
480 syntax: Default::default(),
481 // HACK: Status colors don't have a real default.
482 // We should look into removing the status colors from the editor
483 // style and retrieve them directly from the theme.
484 status: StatusColors::dark(),
485 inlay_hints_style: HighlightStyle::default(),
486 inline_completion_styles: InlineCompletionStyles {
487 insertion: HighlightStyle::default(),
488 whitespace: HighlightStyle::default(),
489 },
490 unnecessary_code_fade: Default::default(),
491 }
492 }
493}
494
495pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
496 let show_background = language_settings::language_settings(None, None, cx)
497 .inlay_hints
498 .show_background;
499
500 HighlightStyle {
501 color: Some(cx.theme().status().hint),
502 background_color: show_background.then(|| cx.theme().status().hint_background),
503 ..HighlightStyle::default()
504 }
505}
506
507pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
508 InlineCompletionStyles {
509 insertion: HighlightStyle {
510 color: Some(cx.theme().status().predictive),
511 ..HighlightStyle::default()
512 },
513 whitespace: HighlightStyle {
514 background_color: Some(cx.theme().status().created_background),
515 ..HighlightStyle::default()
516 },
517 }
518}
519
520type CompletionId = usize;
521
522pub(crate) enum EditDisplayMode {
523 TabAccept,
524 DiffPopover,
525 Inline,
526}
527
528enum InlineCompletion {
529 Edit {
530 edits: Vec<(Range<Anchor>, String)>,
531 edit_preview: Option<EditPreview>,
532 display_mode: EditDisplayMode,
533 snapshot: BufferSnapshot,
534 },
535 Move {
536 target: Anchor,
537 snapshot: BufferSnapshot,
538 },
539}
540
541struct InlineCompletionState {
542 inlay_ids: Vec<InlayId>,
543 completion: InlineCompletion,
544 completion_id: Option<SharedString>,
545 invalidation_range: Range<Anchor>,
546}
547
548enum EditPredictionSettings {
549 Disabled,
550 Enabled {
551 show_in_menu: bool,
552 preview_requires_modifier: bool,
553 },
554}
555
556enum InlineCompletionHighlight {}
557
558#[derive(Debug, Clone)]
559struct InlineDiagnostic {
560 message: SharedString,
561 group_id: usize,
562 is_primary: bool,
563 start: Point,
564 severity: DiagnosticSeverity,
565}
566
567pub enum MenuInlineCompletionsPolicy {
568 Never,
569 ByProvider,
570}
571
572pub enum EditPredictionPreview {
573 /// Modifier is not pressed
574 Inactive { released_too_fast: bool },
575 /// Modifier pressed
576 Active {
577 since: Instant,
578 previous_scroll_position: Option<ScrollAnchor>,
579 },
580}
581
582impl EditPredictionPreview {
583 pub fn released_too_fast(&self) -> bool {
584 match self {
585 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
586 EditPredictionPreview::Active { .. } => false,
587 }
588 }
589
590 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
591 if let EditPredictionPreview::Active {
592 previous_scroll_position,
593 ..
594 } = self
595 {
596 *previous_scroll_position = scroll_position;
597 }
598 }
599}
600
601pub struct ContextMenuOptions {
602 pub min_entries_visible: usize,
603 pub max_entries_visible: usize,
604 pub placement: Option<ContextMenuPlacement>,
605}
606
607#[derive(Debug, Clone, PartialEq, Eq)]
608pub enum ContextMenuPlacement {
609 Above,
610 Below,
611}
612
613#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
614struct EditorActionId(usize);
615
616impl EditorActionId {
617 pub fn post_inc(&mut self) -> Self {
618 let answer = self.0;
619
620 *self = Self(answer + 1);
621
622 Self(answer)
623 }
624}
625
626// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
627// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
628
629type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
630type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
631
632#[derive(Default)]
633struct ScrollbarMarkerState {
634 scrollbar_size: Size<Pixels>,
635 dirty: bool,
636 markers: Arc<[PaintQuad]>,
637 pending_refresh: Option<Task<Result<()>>>,
638}
639
640impl ScrollbarMarkerState {
641 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
642 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
643 }
644}
645
646#[derive(Clone, Debug)]
647struct RunnableTasks {
648 templates: Vec<(TaskSourceKind, TaskTemplate)>,
649 offset: multi_buffer::Anchor,
650 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
651 column: u32,
652 // Values of all named captures, including those starting with '_'
653 extra_variables: HashMap<String, String>,
654 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
655 context_range: Range<BufferOffset>,
656}
657
658impl RunnableTasks {
659 fn resolve<'a>(
660 &'a self,
661 cx: &'a task::TaskContext,
662 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
663 self.templates.iter().filter_map(|(kind, template)| {
664 template
665 .resolve_task(&kind.to_id_base(), cx)
666 .map(|task| (kind.clone(), task))
667 })
668 }
669}
670
671#[derive(Clone)]
672struct ResolvedTasks {
673 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
674 position: Anchor,
675}
676
677#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
678struct BufferOffset(usize);
679
680// Addons allow storing per-editor state in other crates (e.g. Vim)
681pub trait Addon: 'static {
682 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
683
684 fn render_buffer_header_controls(
685 &self,
686 _: &ExcerptInfo,
687 _: &Window,
688 _: &App,
689 ) -> Option<AnyElement> {
690 None
691 }
692
693 fn to_any(&self) -> &dyn std::any::Any;
694}
695
696/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
697///
698/// See the [module level documentation](self) for more information.
699pub struct Editor {
700 focus_handle: FocusHandle,
701 last_focused_descendant: Option<WeakFocusHandle>,
702 /// The text buffer being edited
703 buffer: Entity<MultiBuffer>,
704 /// Map of how text in the buffer should be displayed.
705 /// Handles soft wraps, folds, fake inlay text insertions, etc.
706 pub display_map: Entity<DisplayMap>,
707 pub selections: SelectionsCollection,
708 pub scroll_manager: ScrollManager,
709 /// When inline assist editors are linked, they all render cursors because
710 /// typing enters text into each of them, even the ones that aren't focused.
711 pub(crate) show_cursor_when_unfocused: bool,
712 columnar_selection_tail: Option<Anchor>,
713 add_selections_state: Option<AddSelectionsState>,
714 select_next_state: Option<SelectNextState>,
715 select_prev_state: Option<SelectNextState>,
716 selection_history: SelectionHistory,
717 autoclose_regions: Vec<AutocloseRegion>,
718 snippet_stack: InvalidationStack<SnippetState>,
719 select_syntax_node_history: SelectSyntaxNodeHistory,
720 ime_transaction: Option<TransactionId>,
721 active_diagnostics: ActiveDiagnostic,
722 show_inline_diagnostics: bool,
723 inline_diagnostics_update: Task<()>,
724 inline_diagnostics_enabled: bool,
725 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
726 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
727 hard_wrap: Option<usize>,
728
729 // TODO: make this a access method
730 pub project: Option<Entity<Project>>,
731 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
732 completion_provider: Option<Box<dyn CompletionProvider>>,
733 collaboration_hub: Option<Box<dyn CollaborationHub>>,
734 blink_manager: Entity<BlinkManager>,
735 show_cursor_names: bool,
736 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
737 pub show_local_selections: bool,
738 mode: EditorMode,
739 show_breadcrumbs: bool,
740 show_gutter: bool,
741 show_scrollbars: bool,
742 show_line_numbers: Option<bool>,
743 use_relative_line_numbers: Option<bool>,
744 show_git_diff_gutter: Option<bool>,
745 show_code_actions: Option<bool>,
746 show_runnables: Option<bool>,
747 show_breakpoints: Option<bool>,
748 show_wrap_guides: Option<bool>,
749 show_indent_guides: Option<bool>,
750 placeholder_text: Option<Arc<str>>,
751 highlight_order: usize,
752 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
753 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
754 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
755 scrollbar_marker_state: ScrollbarMarkerState,
756 active_indent_guides_state: ActiveIndentGuidesState,
757 nav_history: Option<ItemNavHistory>,
758 context_menu: RefCell<Option<CodeContextMenu>>,
759 context_menu_options: Option<ContextMenuOptions>,
760 mouse_context_menu: Option<MouseContextMenu>,
761 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
762 signature_help_state: SignatureHelpState,
763 auto_signature_help: Option<bool>,
764 find_all_references_task_sources: Vec<Anchor>,
765 next_completion_id: CompletionId,
766 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
767 code_actions_task: Option<Task<Result<()>>>,
768 selection_highlight_task: Option<Task<()>>,
769 document_highlights_task: Option<Task<()>>,
770 linked_editing_range_task: Option<Task<Option<()>>>,
771 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
772 pending_rename: Option<RenameState>,
773 searchable: bool,
774 cursor_shape: CursorShape,
775 current_line_highlight: Option<CurrentLineHighlight>,
776 collapse_matches: bool,
777 autoindent_mode: Option<AutoindentMode>,
778 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
779 input_enabled: bool,
780 use_modal_editing: bool,
781 read_only: bool,
782 leader_peer_id: Option<PeerId>,
783 remote_id: Option<ViewId>,
784 hover_state: HoverState,
785 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
786 gutter_hovered: bool,
787 hovered_link_state: Option<HoveredLinkState>,
788 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
789 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
790 active_inline_completion: Option<InlineCompletionState>,
791 /// Used to prevent flickering as the user types while the menu is open
792 stale_inline_completion_in_menu: Option<InlineCompletionState>,
793 edit_prediction_settings: EditPredictionSettings,
794 inline_completions_hidden_for_vim_mode: bool,
795 show_inline_completions_override: Option<bool>,
796 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
797 edit_prediction_preview: EditPredictionPreview,
798 edit_prediction_indent_conflict: bool,
799 edit_prediction_requires_modifier_in_indent_conflict: bool,
800 inlay_hint_cache: InlayHintCache,
801 next_inlay_id: usize,
802 _subscriptions: Vec<Subscription>,
803 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
804 gutter_dimensions: GutterDimensions,
805 style: Option<EditorStyle>,
806 text_style_refinement: Option<TextStyleRefinement>,
807 next_editor_action_id: EditorActionId,
808 editor_actions:
809 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
810 use_autoclose: bool,
811 use_auto_surround: bool,
812 auto_replace_emoji_shortcode: bool,
813 jsx_tag_auto_close_enabled_in_any_buffer: bool,
814 show_git_blame_gutter: bool,
815 show_git_blame_inline: bool,
816 show_git_blame_inline_delay_task: Option<Task<()>>,
817 pub git_blame_inline_tooltip: Option<AnyWeakEntity>,
818 git_blame_inline_enabled: bool,
819 render_diff_hunk_controls: RenderDiffHunkControlsFn,
820 serialize_dirty_buffers: bool,
821 show_selection_menu: Option<bool>,
822 blame: Option<Entity<GitBlame>>,
823 blame_subscription: Option<Subscription>,
824 custom_context_menu: Option<
825 Box<
826 dyn 'static
827 + Fn(
828 &mut Self,
829 DisplayPoint,
830 &mut Window,
831 &mut Context<Self>,
832 ) -> Option<Entity<ui::ContextMenu>>,
833 >,
834 >,
835 last_bounds: Option<Bounds<Pixels>>,
836 last_position_map: Option<Rc<PositionMap>>,
837 expect_bounds_change: Option<Bounds<Pixels>>,
838 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
839 tasks_update_task: Option<Task<()>>,
840 breakpoint_store: Option<Entity<BreakpointStore>>,
841 /// Allow's a user to create a breakpoint by selecting this indicator
842 /// It should be None while a user is not hovering over the gutter
843 /// Otherwise it represents the point that the breakpoint will be shown
844 gutter_breakpoint_indicator: (Option<(DisplayPoint, bool)>, Option<Task<()>>),
845 in_project_search: bool,
846 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
847 breadcrumb_header: Option<String>,
848 focused_block: Option<FocusedBlock>,
849 next_scroll_position: NextScrollCursorCenterTopBottom,
850 addons: HashMap<TypeId, Box<dyn Addon>>,
851 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
852 load_diff_task: Option<Shared<Task<()>>>,
853 selection_mark_mode: bool,
854 toggle_fold_multiple_buffers: Task<()>,
855 _scroll_cursor_center_top_bottom_task: Task<()>,
856 serialize_selections: Task<()>,
857 serialize_folds: Task<()>,
858 mouse_cursor_hidden: bool,
859 hide_mouse_mode: HideMouseMode,
860}
861
862#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
863enum NextScrollCursorCenterTopBottom {
864 #[default]
865 Center,
866 Top,
867 Bottom,
868}
869
870impl NextScrollCursorCenterTopBottom {
871 fn next(&self) -> Self {
872 match self {
873 Self::Center => Self::Top,
874 Self::Top => Self::Bottom,
875 Self::Bottom => Self::Center,
876 }
877 }
878}
879
880#[derive(Clone)]
881pub struct EditorSnapshot {
882 pub mode: EditorMode,
883 show_gutter: bool,
884 show_line_numbers: Option<bool>,
885 show_git_diff_gutter: Option<bool>,
886 show_code_actions: Option<bool>,
887 show_runnables: Option<bool>,
888 show_breakpoints: Option<bool>,
889 git_blame_gutter_max_author_length: Option<usize>,
890 pub display_snapshot: DisplaySnapshot,
891 pub placeholder_text: Option<Arc<str>>,
892 is_focused: bool,
893 scroll_anchor: ScrollAnchor,
894 ongoing_scroll: OngoingScroll,
895 current_line_highlight: CurrentLineHighlight,
896 gutter_hovered: bool,
897}
898
899#[derive(Default, Debug, Clone, Copy)]
900pub struct GutterDimensions {
901 pub left_padding: Pixels,
902 pub right_padding: Pixels,
903 pub width: Pixels,
904 pub margin: Pixels,
905 pub git_blame_entries_width: Option<Pixels>,
906}
907
908impl GutterDimensions {
909 /// The full width of the space taken up by the gutter.
910 pub fn full_width(&self) -> Pixels {
911 self.margin + self.width
912 }
913
914 /// The width of the space reserved for the fold indicators,
915 /// use alongside 'justify_end' and `gutter_width` to
916 /// right align content with the line numbers
917 pub fn fold_area_width(&self) -> Pixels {
918 self.margin + self.right_padding
919 }
920}
921
922#[derive(Debug)]
923pub struct RemoteSelection {
924 pub replica_id: ReplicaId,
925 pub selection: Selection<Anchor>,
926 pub cursor_shape: CursorShape,
927 pub peer_id: PeerId,
928 pub line_mode: bool,
929 pub participant_index: Option<ParticipantIndex>,
930 pub user_name: Option<SharedString>,
931}
932
933#[derive(Clone, Debug)]
934struct SelectionHistoryEntry {
935 selections: Arc<[Selection<Anchor>]>,
936 select_next_state: Option<SelectNextState>,
937 select_prev_state: Option<SelectNextState>,
938 add_selections_state: Option<AddSelectionsState>,
939}
940
941enum SelectionHistoryMode {
942 Normal,
943 Undoing,
944 Redoing,
945}
946
947#[derive(Clone, PartialEq, Eq, Hash)]
948struct HoveredCursor {
949 replica_id: u16,
950 selection_id: usize,
951}
952
953impl Default for SelectionHistoryMode {
954 fn default() -> Self {
955 Self::Normal
956 }
957}
958
959#[derive(Default)]
960struct SelectionHistory {
961 #[allow(clippy::type_complexity)]
962 selections_by_transaction:
963 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
964 mode: SelectionHistoryMode,
965 undo_stack: VecDeque<SelectionHistoryEntry>,
966 redo_stack: VecDeque<SelectionHistoryEntry>,
967}
968
969impl SelectionHistory {
970 fn insert_transaction(
971 &mut self,
972 transaction_id: TransactionId,
973 selections: Arc<[Selection<Anchor>]>,
974 ) {
975 self.selections_by_transaction
976 .insert(transaction_id, (selections, None));
977 }
978
979 #[allow(clippy::type_complexity)]
980 fn transaction(
981 &self,
982 transaction_id: TransactionId,
983 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
984 self.selections_by_transaction.get(&transaction_id)
985 }
986
987 #[allow(clippy::type_complexity)]
988 fn transaction_mut(
989 &mut self,
990 transaction_id: TransactionId,
991 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
992 self.selections_by_transaction.get_mut(&transaction_id)
993 }
994
995 fn push(&mut self, entry: SelectionHistoryEntry) {
996 if !entry.selections.is_empty() {
997 match self.mode {
998 SelectionHistoryMode::Normal => {
999 self.push_undo(entry);
1000 self.redo_stack.clear();
1001 }
1002 SelectionHistoryMode::Undoing => self.push_redo(entry),
1003 SelectionHistoryMode::Redoing => self.push_undo(entry),
1004 }
1005 }
1006 }
1007
1008 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1009 if self
1010 .undo_stack
1011 .back()
1012 .map_or(true, |e| e.selections != entry.selections)
1013 {
1014 self.undo_stack.push_back(entry);
1015 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1016 self.undo_stack.pop_front();
1017 }
1018 }
1019 }
1020
1021 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1022 if self
1023 .redo_stack
1024 .back()
1025 .map_or(true, |e| e.selections != entry.selections)
1026 {
1027 self.redo_stack.push_back(entry);
1028 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1029 self.redo_stack.pop_front();
1030 }
1031 }
1032 }
1033}
1034
1035struct RowHighlight {
1036 index: usize,
1037 range: Range<Anchor>,
1038 color: Hsla,
1039 should_autoscroll: bool,
1040}
1041
1042#[derive(Clone, Debug)]
1043struct AddSelectionsState {
1044 above: bool,
1045 stack: Vec<usize>,
1046}
1047
1048#[derive(Clone)]
1049struct SelectNextState {
1050 query: AhoCorasick,
1051 wordwise: bool,
1052 done: bool,
1053}
1054
1055impl std::fmt::Debug for SelectNextState {
1056 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1057 f.debug_struct(std::any::type_name::<Self>())
1058 .field("wordwise", &self.wordwise)
1059 .field("done", &self.done)
1060 .finish()
1061 }
1062}
1063
1064#[derive(Debug)]
1065struct AutocloseRegion {
1066 selection_id: usize,
1067 range: Range<Anchor>,
1068 pair: BracketPair,
1069}
1070
1071#[derive(Debug)]
1072struct SnippetState {
1073 ranges: Vec<Vec<Range<Anchor>>>,
1074 active_index: usize,
1075 choices: Vec<Option<Vec<String>>>,
1076}
1077
1078#[doc(hidden)]
1079pub struct RenameState {
1080 pub range: Range<Anchor>,
1081 pub old_name: Arc<str>,
1082 pub editor: Entity<Editor>,
1083 block_id: CustomBlockId,
1084}
1085
1086struct InvalidationStack<T>(Vec<T>);
1087
1088struct RegisteredInlineCompletionProvider {
1089 provider: Arc<dyn InlineCompletionProviderHandle>,
1090 _subscription: Subscription,
1091}
1092
1093#[derive(Debug, PartialEq, Eq)]
1094pub struct ActiveDiagnosticGroup {
1095 pub active_range: Range<Anchor>,
1096 pub active_message: String,
1097 pub group_id: usize,
1098 pub blocks: HashSet<CustomBlockId>,
1099}
1100
1101#[derive(Debug, PartialEq, Eq)]
1102#[allow(clippy::large_enum_variant)]
1103pub(crate) enum ActiveDiagnostic {
1104 None,
1105 All,
1106 Group(ActiveDiagnosticGroup),
1107}
1108
1109#[derive(Serialize, Deserialize, Clone, Debug)]
1110pub struct ClipboardSelection {
1111 /// The number of bytes in this selection.
1112 pub len: usize,
1113 /// Whether this was a full-line selection.
1114 pub is_entire_line: bool,
1115 /// The indentation of the first line when this content was originally copied.
1116 pub first_line_indent: u32,
1117}
1118
1119// selections, scroll behavior, was newest selection reversed
1120type SelectSyntaxNodeHistoryState = (
1121 Box<[Selection<usize>]>,
1122 SelectSyntaxNodeScrollBehavior,
1123 bool,
1124);
1125
1126#[derive(Default)]
1127struct SelectSyntaxNodeHistory {
1128 stack: Vec<SelectSyntaxNodeHistoryState>,
1129 // disable temporarily to allow changing selections without losing the stack
1130 pub disable_clearing: bool,
1131}
1132
1133impl SelectSyntaxNodeHistory {
1134 pub fn try_clear(&mut self) {
1135 if !self.disable_clearing {
1136 self.stack.clear();
1137 }
1138 }
1139
1140 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1141 self.stack.push(selection);
1142 }
1143
1144 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1145 self.stack.pop()
1146 }
1147}
1148
1149enum SelectSyntaxNodeScrollBehavior {
1150 CursorTop,
1151 FitSelection,
1152 CursorBottom,
1153}
1154
1155#[derive(Debug)]
1156pub(crate) struct NavigationData {
1157 cursor_anchor: Anchor,
1158 cursor_position: Point,
1159 scroll_anchor: ScrollAnchor,
1160 scroll_top_row: u32,
1161}
1162
1163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1164pub enum GotoDefinitionKind {
1165 Symbol,
1166 Declaration,
1167 Type,
1168 Implementation,
1169}
1170
1171#[derive(Debug, Clone)]
1172enum InlayHintRefreshReason {
1173 ModifiersChanged(bool),
1174 Toggle(bool),
1175 SettingsChange(InlayHintSettings),
1176 NewLinesShown,
1177 BufferEdited(HashSet<Arc<Language>>),
1178 RefreshRequested,
1179 ExcerptsRemoved(Vec<ExcerptId>),
1180}
1181
1182impl InlayHintRefreshReason {
1183 fn description(&self) -> &'static str {
1184 match self {
1185 Self::ModifiersChanged(_) => "modifiers changed",
1186 Self::Toggle(_) => "toggle",
1187 Self::SettingsChange(_) => "settings change",
1188 Self::NewLinesShown => "new lines shown",
1189 Self::BufferEdited(_) => "buffer edited",
1190 Self::RefreshRequested => "refresh requested",
1191 Self::ExcerptsRemoved(_) => "excerpts removed",
1192 }
1193 }
1194}
1195
1196pub enum FormatTarget {
1197 Buffers,
1198 Ranges(Vec<Range<MultiBufferPoint>>),
1199}
1200
1201pub(crate) struct FocusedBlock {
1202 id: BlockId,
1203 focus_handle: WeakFocusHandle,
1204}
1205
1206#[derive(Clone)]
1207enum JumpData {
1208 MultiBufferRow {
1209 row: MultiBufferRow,
1210 line_offset_from_top: u32,
1211 },
1212 MultiBufferPoint {
1213 excerpt_id: ExcerptId,
1214 position: Point,
1215 anchor: text::Anchor,
1216 line_offset_from_top: u32,
1217 },
1218}
1219
1220pub enum MultibufferSelectionMode {
1221 First,
1222 All,
1223}
1224
1225#[derive(Clone, Copy, Debug, Default)]
1226pub struct RewrapOptions {
1227 pub override_language_settings: bool,
1228 pub preserve_existing_whitespace: bool,
1229}
1230
1231impl Editor {
1232 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1233 let buffer = cx.new(|cx| Buffer::local("", cx));
1234 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1235 Self::new(
1236 EditorMode::SingleLine { auto_width: false },
1237 buffer,
1238 None,
1239 window,
1240 cx,
1241 )
1242 }
1243
1244 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1245 let buffer = cx.new(|cx| Buffer::local("", cx));
1246 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1247 Self::new(EditorMode::full(), buffer, None, window, cx)
1248 }
1249
1250 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1251 let buffer = cx.new(|cx| Buffer::local("", cx));
1252 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1253 Self::new(
1254 EditorMode::SingleLine { auto_width: true },
1255 buffer,
1256 None,
1257 window,
1258 cx,
1259 )
1260 }
1261
1262 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1263 let buffer = cx.new(|cx| Buffer::local("", cx));
1264 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1265 Self::new(
1266 EditorMode::AutoHeight { max_lines },
1267 buffer,
1268 None,
1269 window,
1270 cx,
1271 )
1272 }
1273
1274 pub fn for_buffer(
1275 buffer: Entity<Buffer>,
1276 project: Option<Entity<Project>>,
1277 window: &mut Window,
1278 cx: &mut Context<Self>,
1279 ) -> Self {
1280 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1281 Self::new(EditorMode::full(), buffer, project, window, cx)
1282 }
1283
1284 pub fn for_multibuffer(
1285 buffer: Entity<MultiBuffer>,
1286 project: Option<Entity<Project>>,
1287 window: &mut Window,
1288 cx: &mut Context<Self>,
1289 ) -> Self {
1290 Self::new(EditorMode::full(), buffer, project, window, cx)
1291 }
1292
1293 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1294 let mut clone = Self::new(
1295 self.mode,
1296 self.buffer.clone(),
1297 self.project.clone(),
1298 window,
1299 cx,
1300 );
1301 self.display_map.update(cx, |display_map, cx| {
1302 let snapshot = display_map.snapshot(cx);
1303 clone.display_map.update(cx, |display_map, cx| {
1304 display_map.set_state(&snapshot, cx);
1305 });
1306 });
1307 clone.folds_did_change(cx);
1308 clone.selections.clone_state(&self.selections);
1309 clone.scroll_manager.clone_state(&self.scroll_manager);
1310 clone.searchable = self.searchable;
1311 clone.read_only = self.read_only;
1312 clone
1313 }
1314
1315 pub fn new(
1316 mode: EditorMode,
1317 buffer: Entity<MultiBuffer>,
1318 project: Option<Entity<Project>>,
1319 window: &mut Window,
1320 cx: &mut Context<Self>,
1321 ) -> Self {
1322 let style = window.text_style();
1323 let font_size = style.font_size.to_pixels(window.rem_size());
1324 let editor = cx.entity().downgrade();
1325 let fold_placeholder = FoldPlaceholder {
1326 constrain_width: true,
1327 render: Arc::new(move |fold_id, fold_range, cx| {
1328 let editor = editor.clone();
1329 div()
1330 .id(fold_id)
1331 .bg(cx.theme().colors().ghost_element_background)
1332 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1333 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1334 .rounded_xs()
1335 .size_full()
1336 .cursor_pointer()
1337 .child("⋯")
1338 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1339 .on_click(move |_, _window, cx| {
1340 editor
1341 .update(cx, |editor, cx| {
1342 editor.unfold_ranges(
1343 &[fold_range.start..fold_range.end],
1344 true,
1345 false,
1346 cx,
1347 );
1348 cx.stop_propagation();
1349 })
1350 .ok();
1351 })
1352 .into_any()
1353 }),
1354 merge_adjacent: true,
1355 ..Default::default()
1356 };
1357 let display_map = cx.new(|cx| {
1358 DisplayMap::new(
1359 buffer.clone(),
1360 style.font(),
1361 font_size,
1362 None,
1363 FILE_HEADER_HEIGHT,
1364 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1365 fold_placeholder,
1366 cx,
1367 )
1368 });
1369
1370 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1371
1372 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1373
1374 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1375 .then(|| language_settings::SoftWrap::None);
1376
1377 let mut project_subscriptions = Vec::new();
1378 if mode.is_full() {
1379 if let Some(project) = project.as_ref() {
1380 project_subscriptions.push(cx.subscribe_in(
1381 project,
1382 window,
1383 |editor, _, event, window, cx| match event {
1384 project::Event::RefreshCodeLens => {
1385 // we always query lens with actions, without storing them, always refreshing them
1386 }
1387 project::Event::RefreshInlayHints => {
1388 editor
1389 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1390 }
1391 project::Event::SnippetEdit(id, snippet_edits) => {
1392 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1393 let focus_handle = editor.focus_handle(cx);
1394 if focus_handle.is_focused(window) {
1395 let snapshot = buffer.read(cx).snapshot();
1396 for (range, snippet) in snippet_edits {
1397 let editor_range =
1398 language::range_from_lsp(*range).to_offset(&snapshot);
1399 editor
1400 .insert_snippet(
1401 &[editor_range],
1402 snippet.clone(),
1403 window,
1404 cx,
1405 )
1406 .ok();
1407 }
1408 }
1409 }
1410 }
1411 _ => {}
1412 },
1413 ));
1414 if let Some(task_inventory) = project
1415 .read(cx)
1416 .task_store()
1417 .read(cx)
1418 .task_inventory()
1419 .cloned()
1420 {
1421 project_subscriptions.push(cx.observe_in(
1422 &task_inventory,
1423 window,
1424 |editor, _, window, cx| {
1425 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1426 },
1427 ));
1428 };
1429
1430 project_subscriptions.push(cx.subscribe_in(
1431 &project.read(cx).breakpoint_store(),
1432 window,
1433 |editor, _, event, window, cx| match event {
1434 BreakpointStoreEvent::ActiveDebugLineChanged => {
1435 if editor.go_to_active_debug_line(window, cx) {
1436 cx.stop_propagation();
1437 }
1438 }
1439 _ => {}
1440 },
1441 ));
1442 }
1443 }
1444
1445 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1446
1447 let inlay_hint_settings =
1448 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1449 let focus_handle = cx.focus_handle();
1450 cx.on_focus(&focus_handle, window, Self::handle_focus)
1451 .detach();
1452 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1453 .detach();
1454 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1455 .detach();
1456 cx.on_blur(&focus_handle, window, Self::handle_blur)
1457 .detach();
1458
1459 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1460 Some(false)
1461 } else {
1462 None
1463 };
1464
1465 let breakpoint_store = match (mode, project.as_ref()) {
1466 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1467 _ => None,
1468 };
1469
1470 let mut code_action_providers = Vec::new();
1471 let mut load_uncommitted_diff = None;
1472 if let Some(project) = project.clone() {
1473 load_uncommitted_diff = Some(
1474 get_uncommitted_diff_for_buffer(
1475 &project,
1476 buffer.read(cx).all_buffers(),
1477 buffer.clone(),
1478 cx,
1479 )
1480 .shared(),
1481 );
1482 code_action_providers.push(Rc::new(project) as Rc<_>);
1483 }
1484
1485 let mut this = Self {
1486 focus_handle,
1487 show_cursor_when_unfocused: false,
1488 last_focused_descendant: None,
1489 buffer: buffer.clone(),
1490 display_map: display_map.clone(),
1491 selections,
1492 scroll_manager: ScrollManager::new(cx),
1493 columnar_selection_tail: None,
1494 add_selections_state: None,
1495 select_next_state: None,
1496 select_prev_state: None,
1497 selection_history: Default::default(),
1498 autoclose_regions: Default::default(),
1499 snippet_stack: Default::default(),
1500 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1501 ime_transaction: Default::default(),
1502 active_diagnostics: ActiveDiagnostic::None,
1503 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1504 inline_diagnostics_update: Task::ready(()),
1505 inline_diagnostics: Vec::new(),
1506 soft_wrap_mode_override,
1507 hard_wrap: None,
1508 completion_provider: project.clone().map(|project| Box::new(project) as _),
1509 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1510 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1511 project,
1512 blink_manager: blink_manager.clone(),
1513 show_local_selections: true,
1514 show_scrollbars: true,
1515 mode,
1516 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1517 show_gutter: mode.is_full(),
1518 show_line_numbers: None,
1519 use_relative_line_numbers: None,
1520 show_git_diff_gutter: None,
1521 show_code_actions: None,
1522 show_runnables: None,
1523 show_breakpoints: None,
1524 show_wrap_guides: None,
1525 show_indent_guides,
1526 placeholder_text: None,
1527 highlight_order: 0,
1528 highlighted_rows: HashMap::default(),
1529 background_highlights: Default::default(),
1530 gutter_highlights: TreeMap::default(),
1531 scrollbar_marker_state: ScrollbarMarkerState::default(),
1532 active_indent_guides_state: ActiveIndentGuidesState::default(),
1533 nav_history: None,
1534 context_menu: RefCell::new(None),
1535 context_menu_options: None,
1536 mouse_context_menu: None,
1537 completion_tasks: Default::default(),
1538 signature_help_state: SignatureHelpState::default(),
1539 auto_signature_help: None,
1540 find_all_references_task_sources: Vec::new(),
1541 next_completion_id: 0,
1542 next_inlay_id: 0,
1543 code_action_providers,
1544 available_code_actions: Default::default(),
1545 code_actions_task: Default::default(),
1546 selection_highlight_task: Default::default(),
1547 document_highlights_task: Default::default(),
1548 linked_editing_range_task: Default::default(),
1549 pending_rename: Default::default(),
1550 searchable: true,
1551 cursor_shape: EditorSettings::get_global(cx)
1552 .cursor_shape
1553 .unwrap_or_default(),
1554 current_line_highlight: None,
1555 autoindent_mode: Some(AutoindentMode::EachLine),
1556 collapse_matches: false,
1557 workspace: None,
1558 input_enabled: true,
1559 use_modal_editing: mode.is_full(),
1560 read_only: false,
1561 use_autoclose: true,
1562 use_auto_surround: true,
1563 auto_replace_emoji_shortcode: false,
1564 jsx_tag_auto_close_enabled_in_any_buffer: false,
1565 leader_peer_id: None,
1566 remote_id: None,
1567 hover_state: Default::default(),
1568 pending_mouse_down: None,
1569 hovered_link_state: Default::default(),
1570 edit_prediction_provider: None,
1571 active_inline_completion: None,
1572 stale_inline_completion_in_menu: None,
1573 edit_prediction_preview: EditPredictionPreview::Inactive {
1574 released_too_fast: false,
1575 },
1576 inline_diagnostics_enabled: mode.is_full(),
1577 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1578
1579 gutter_hovered: false,
1580 pixel_position_of_newest_cursor: None,
1581 last_bounds: None,
1582 last_position_map: None,
1583 expect_bounds_change: None,
1584 gutter_dimensions: GutterDimensions::default(),
1585 style: None,
1586 show_cursor_names: false,
1587 hovered_cursors: Default::default(),
1588 next_editor_action_id: EditorActionId::default(),
1589 editor_actions: Rc::default(),
1590 inline_completions_hidden_for_vim_mode: false,
1591 show_inline_completions_override: None,
1592 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1593 edit_prediction_settings: EditPredictionSettings::Disabled,
1594 edit_prediction_indent_conflict: false,
1595 edit_prediction_requires_modifier_in_indent_conflict: true,
1596 custom_context_menu: None,
1597 show_git_blame_gutter: false,
1598 show_git_blame_inline: false,
1599 show_selection_menu: None,
1600 show_git_blame_inline_delay_task: None,
1601 git_blame_inline_tooltip: None,
1602 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1603 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1604 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1605 .session
1606 .restore_unsaved_buffers,
1607 blame: None,
1608 blame_subscription: None,
1609 tasks: Default::default(),
1610
1611 breakpoint_store,
1612 gutter_breakpoint_indicator: (None, None),
1613 _subscriptions: vec![
1614 cx.observe(&buffer, Self::on_buffer_changed),
1615 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1616 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1617 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1618 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1619 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1620 cx.observe_window_activation(window, |editor, window, cx| {
1621 let active = window.is_window_active();
1622 editor.blink_manager.update(cx, |blink_manager, cx| {
1623 if active {
1624 blink_manager.enable(cx);
1625 } else {
1626 blink_manager.disable(cx);
1627 }
1628 });
1629 }),
1630 ],
1631 tasks_update_task: None,
1632 linked_edit_ranges: Default::default(),
1633 in_project_search: false,
1634 previous_search_ranges: None,
1635 breadcrumb_header: None,
1636 focused_block: None,
1637 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1638 addons: HashMap::default(),
1639 registered_buffers: HashMap::default(),
1640 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1641 selection_mark_mode: false,
1642 toggle_fold_multiple_buffers: Task::ready(()),
1643 serialize_selections: Task::ready(()),
1644 serialize_folds: Task::ready(()),
1645 text_style_refinement: None,
1646 load_diff_task: load_uncommitted_diff,
1647 mouse_cursor_hidden: false,
1648 hide_mouse_mode: EditorSettings::get_global(cx)
1649 .hide_mouse
1650 .unwrap_or_default(),
1651 };
1652 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1653 this._subscriptions
1654 .push(cx.observe(breakpoints, |_, _, cx| {
1655 cx.notify();
1656 }));
1657 }
1658 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1659 this._subscriptions.extend(project_subscriptions);
1660
1661 this._subscriptions.push(cx.subscribe_in(
1662 &cx.entity(),
1663 window,
1664 |editor, _, e: &EditorEvent, window, cx| {
1665 if let EditorEvent::SelectionsChanged { local } = e {
1666 if *local {
1667 let new_anchor = editor.scroll_manager.anchor();
1668 let snapshot = editor.snapshot(window, cx);
1669 editor.update_restoration_data(cx, move |data| {
1670 data.scroll_position = (
1671 new_anchor.top_row(&snapshot.buffer_snapshot),
1672 new_anchor.offset,
1673 );
1674 });
1675 }
1676 }
1677 },
1678 ));
1679
1680 this.end_selection(window, cx);
1681 this.scroll_manager.show_scrollbars(window, cx);
1682 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1683
1684 if mode.is_full() {
1685 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1686 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1687
1688 if this.git_blame_inline_enabled {
1689 this.git_blame_inline_enabled = true;
1690 this.start_git_blame_inline(false, window, cx);
1691 }
1692
1693 this.go_to_active_debug_line(window, cx);
1694
1695 if let Some(buffer) = buffer.read(cx).as_singleton() {
1696 if let Some(project) = this.project.as_ref() {
1697 let handle = project.update(cx, |project, cx| {
1698 project.register_buffer_with_language_servers(&buffer, cx)
1699 });
1700 this.registered_buffers
1701 .insert(buffer.read(cx).remote_id(), handle);
1702 }
1703 }
1704 }
1705
1706 this.report_editor_event("Editor Opened", None, cx);
1707 this
1708 }
1709
1710 pub fn deploy_mouse_context_menu(
1711 &mut self,
1712 position: gpui::Point<Pixels>,
1713 context_menu: Entity<ContextMenu>,
1714 window: &mut Window,
1715 cx: &mut Context<Self>,
1716 ) {
1717 self.mouse_context_menu = Some(MouseContextMenu::new(
1718 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1719 context_menu,
1720 None,
1721 window,
1722 cx,
1723 ));
1724 }
1725
1726 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1727 self.mouse_context_menu
1728 .as_ref()
1729 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1730 }
1731
1732 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1733 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1734 }
1735
1736 fn key_context_internal(
1737 &self,
1738 has_active_edit_prediction: bool,
1739 window: &Window,
1740 cx: &App,
1741 ) -> KeyContext {
1742 let mut key_context = KeyContext::new_with_defaults();
1743 key_context.add("Editor");
1744 let mode = match self.mode {
1745 EditorMode::SingleLine { .. } => "single_line",
1746 EditorMode::AutoHeight { .. } => "auto_height",
1747 EditorMode::Full { .. } => "full",
1748 };
1749
1750 if EditorSettings::jupyter_enabled(cx) {
1751 key_context.add("jupyter");
1752 }
1753
1754 key_context.set("mode", mode);
1755 if self.pending_rename.is_some() {
1756 key_context.add("renaming");
1757 }
1758
1759 match self.context_menu.borrow().as_ref() {
1760 Some(CodeContextMenu::Completions(_)) => {
1761 key_context.add("menu");
1762 key_context.add("showing_completions");
1763 }
1764 Some(CodeContextMenu::CodeActions(_)) => {
1765 key_context.add("menu");
1766 key_context.add("showing_code_actions")
1767 }
1768 None => {}
1769 }
1770
1771 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1772 if !self.focus_handle(cx).contains_focused(window, cx)
1773 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1774 {
1775 for addon in self.addons.values() {
1776 addon.extend_key_context(&mut key_context, cx)
1777 }
1778 }
1779
1780 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1781 if let Some(extension) = singleton_buffer
1782 .read(cx)
1783 .file()
1784 .and_then(|file| file.path().extension()?.to_str())
1785 {
1786 key_context.set("extension", extension.to_string());
1787 }
1788 } else {
1789 key_context.add("multibuffer");
1790 }
1791
1792 if has_active_edit_prediction {
1793 if self.edit_prediction_in_conflict() {
1794 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
1795 } else {
1796 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
1797 key_context.add("copilot_suggestion");
1798 }
1799 }
1800
1801 if self.selection_mark_mode {
1802 key_context.add("selection_mode");
1803 }
1804
1805 key_context
1806 }
1807
1808 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
1809 self.mouse_cursor_hidden = match origin {
1810 HideMouseCursorOrigin::TypingAction => {
1811 matches!(
1812 self.hide_mouse_mode,
1813 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
1814 )
1815 }
1816 HideMouseCursorOrigin::MovementAction => {
1817 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
1818 }
1819 };
1820 }
1821
1822 pub fn edit_prediction_in_conflict(&self) -> bool {
1823 if !self.show_edit_predictions_in_menu() {
1824 return false;
1825 }
1826
1827 let showing_completions = self
1828 .context_menu
1829 .borrow()
1830 .as_ref()
1831 .map_or(false, |context| {
1832 matches!(context, CodeContextMenu::Completions(_))
1833 });
1834
1835 showing_completions
1836 || self.edit_prediction_requires_modifier()
1837 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
1838 // bindings to insert tab characters.
1839 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
1840 }
1841
1842 pub fn accept_edit_prediction_keybind(
1843 &self,
1844 window: &Window,
1845 cx: &App,
1846 ) -> AcceptEditPredictionBinding {
1847 let key_context = self.key_context_internal(true, window, cx);
1848 let in_conflict = self.edit_prediction_in_conflict();
1849
1850 AcceptEditPredictionBinding(
1851 window
1852 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
1853 .into_iter()
1854 .filter(|binding| {
1855 !in_conflict
1856 || binding
1857 .keystrokes()
1858 .first()
1859 .map_or(false, |keystroke| keystroke.modifiers.modified())
1860 })
1861 .rev()
1862 .min_by_key(|binding| {
1863 binding
1864 .keystrokes()
1865 .first()
1866 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
1867 }),
1868 )
1869 }
1870
1871 pub fn new_file(
1872 workspace: &mut Workspace,
1873 _: &workspace::NewFile,
1874 window: &mut Window,
1875 cx: &mut Context<Workspace>,
1876 ) {
1877 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
1878 "Failed to create buffer",
1879 window,
1880 cx,
1881 |e, _, _| match e.error_code() {
1882 ErrorCode::RemoteUpgradeRequired => Some(format!(
1883 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1884 e.error_tag("required").unwrap_or("the latest version")
1885 )),
1886 _ => None,
1887 },
1888 );
1889 }
1890
1891 pub fn new_in_workspace(
1892 workspace: &mut Workspace,
1893 window: &mut Window,
1894 cx: &mut Context<Workspace>,
1895 ) -> Task<Result<Entity<Editor>>> {
1896 let project = workspace.project().clone();
1897 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1898
1899 cx.spawn_in(window, async move |workspace, cx| {
1900 let buffer = create.await?;
1901 workspace.update_in(cx, |workspace, window, cx| {
1902 let editor =
1903 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
1904 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1905 editor
1906 })
1907 })
1908 }
1909
1910 fn new_file_vertical(
1911 workspace: &mut Workspace,
1912 _: &workspace::NewFileSplitVertical,
1913 window: &mut Window,
1914 cx: &mut Context<Workspace>,
1915 ) {
1916 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
1917 }
1918
1919 fn new_file_horizontal(
1920 workspace: &mut Workspace,
1921 _: &workspace::NewFileSplitHorizontal,
1922 window: &mut Window,
1923 cx: &mut Context<Workspace>,
1924 ) {
1925 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
1926 }
1927
1928 fn new_file_in_direction(
1929 workspace: &mut Workspace,
1930 direction: SplitDirection,
1931 window: &mut Window,
1932 cx: &mut Context<Workspace>,
1933 ) {
1934 let project = workspace.project().clone();
1935 let create = project.update(cx, |project, cx| project.create_buffer(cx));
1936
1937 cx.spawn_in(window, async move |workspace, cx| {
1938 let buffer = create.await?;
1939 workspace.update_in(cx, move |workspace, window, cx| {
1940 workspace.split_item(
1941 direction,
1942 Box::new(
1943 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
1944 ),
1945 window,
1946 cx,
1947 )
1948 })?;
1949 anyhow::Ok(())
1950 })
1951 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
1952 match e.error_code() {
1953 ErrorCode::RemoteUpgradeRequired => Some(format!(
1954 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
1955 e.error_tag("required").unwrap_or("the latest version")
1956 )),
1957 _ => None,
1958 }
1959 });
1960 }
1961
1962 pub fn leader_peer_id(&self) -> Option<PeerId> {
1963 self.leader_peer_id
1964 }
1965
1966 pub fn buffer(&self) -> &Entity<MultiBuffer> {
1967 &self.buffer
1968 }
1969
1970 pub fn workspace(&self) -> Option<Entity<Workspace>> {
1971 self.workspace.as_ref()?.0.upgrade()
1972 }
1973
1974 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
1975 self.buffer().read(cx).title(cx)
1976 }
1977
1978 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
1979 let git_blame_gutter_max_author_length = self
1980 .render_git_blame_gutter(cx)
1981 .then(|| {
1982 if let Some(blame) = self.blame.as_ref() {
1983 let max_author_length =
1984 blame.update(cx, |blame, cx| blame.max_author_length(cx));
1985 Some(max_author_length)
1986 } else {
1987 None
1988 }
1989 })
1990 .flatten();
1991
1992 EditorSnapshot {
1993 mode: self.mode,
1994 show_gutter: self.show_gutter,
1995 show_line_numbers: self.show_line_numbers,
1996 show_git_diff_gutter: self.show_git_diff_gutter,
1997 show_code_actions: self.show_code_actions,
1998 show_runnables: self.show_runnables,
1999 show_breakpoints: self.show_breakpoints,
2000 git_blame_gutter_max_author_length,
2001 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2002 scroll_anchor: self.scroll_manager.anchor(),
2003 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2004 placeholder_text: self.placeholder_text.clone(),
2005 is_focused: self.focus_handle.is_focused(window),
2006 current_line_highlight: self
2007 .current_line_highlight
2008 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2009 gutter_hovered: self.gutter_hovered,
2010 }
2011 }
2012
2013 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2014 self.buffer.read(cx).language_at(point, cx)
2015 }
2016
2017 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2018 self.buffer.read(cx).read(cx).file_at(point).cloned()
2019 }
2020
2021 pub fn active_excerpt(
2022 &self,
2023 cx: &App,
2024 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2025 self.buffer
2026 .read(cx)
2027 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2028 }
2029
2030 pub fn mode(&self) -> EditorMode {
2031 self.mode
2032 }
2033
2034 pub fn set_mode(&mut self, mode: EditorMode) {
2035 self.mode = mode;
2036 }
2037
2038 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2039 self.collaboration_hub.as_deref()
2040 }
2041
2042 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2043 self.collaboration_hub = Some(hub);
2044 }
2045
2046 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2047 self.in_project_search = in_project_search;
2048 }
2049
2050 pub fn set_custom_context_menu(
2051 &mut self,
2052 f: impl 'static
2053 + Fn(
2054 &mut Self,
2055 DisplayPoint,
2056 &mut Window,
2057 &mut Context<Self>,
2058 ) -> Option<Entity<ui::ContextMenu>>,
2059 ) {
2060 self.custom_context_menu = Some(Box::new(f))
2061 }
2062
2063 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2064 self.completion_provider = provider;
2065 }
2066
2067 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2068 self.semantics_provider.clone()
2069 }
2070
2071 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2072 self.semantics_provider = provider;
2073 }
2074
2075 pub fn set_edit_prediction_provider<T>(
2076 &mut self,
2077 provider: Option<Entity<T>>,
2078 window: &mut Window,
2079 cx: &mut Context<Self>,
2080 ) where
2081 T: EditPredictionProvider,
2082 {
2083 self.edit_prediction_provider =
2084 provider.map(|provider| RegisteredInlineCompletionProvider {
2085 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2086 if this.focus_handle.is_focused(window) {
2087 this.update_visible_inline_completion(window, cx);
2088 }
2089 }),
2090 provider: Arc::new(provider),
2091 });
2092 self.update_edit_prediction_settings(cx);
2093 self.refresh_inline_completion(false, false, window, cx);
2094 }
2095
2096 pub fn placeholder_text(&self) -> Option<&str> {
2097 self.placeholder_text.as_deref()
2098 }
2099
2100 pub fn set_placeholder_text(
2101 &mut self,
2102 placeholder_text: impl Into<Arc<str>>,
2103 cx: &mut Context<Self>,
2104 ) {
2105 let placeholder_text = Some(placeholder_text.into());
2106 if self.placeholder_text != placeholder_text {
2107 self.placeholder_text = placeholder_text;
2108 cx.notify();
2109 }
2110 }
2111
2112 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2113 self.cursor_shape = cursor_shape;
2114
2115 // Disrupt blink for immediate user feedback that the cursor shape has changed
2116 self.blink_manager.update(cx, BlinkManager::show_cursor);
2117
2118 cx.notify();
2119 }
2120
2121 pub fn set_current_line_highlight(
2122 &mut self,
2123 current_line_highlight: Option<CurrentLineHighlight>,
2124 ) {
2125 self.current_line_highlight = current_line_highlight;
2126 }
2127
2128 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2129 self.collapse_matches = collapse_matches;
2130 }
2131
2132 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2133 let buffers = self.buffer.read(cx).all_buffers();
2134 let Some(project) = self.project.as_ref() else {
2135 return;
2136 };
2137 project.update(cx, |project, cx| {
2138 for buffer in buffers {
2139 self.registered_buffers
2140 .entry(buffer.read(cx).remote_id())
2141 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2142 }
2143 })
2144 }
2145
2146 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2147 if self.collapse_matches {
2148 return range.start..range.start;
2149 }
2150 range.clone()
2151 }
2152
2153 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2154 if self.display_map.read(cx).clip_at_line_ends != clip {
2155 self.display_map
2156 .update(cx, |map, _| map.clip_at_line_ends = clip);
2157 }
2158 }
2159
2160 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2161 self.input_enabled = input_enabled;
2162 }
2163
2164 pub fn set_inline_completions_hidden_for_vim_mode(
2165 &mut self,
2166 hidden: bool,
2167 window: &mut Window,
2168 cx: &mut Context<Self>,
2169 ) {
2170 if hidden != self.inline_completions_hidden_for_vim_mode {
2171 self.inline_completions_hidden_for_vim_mode = hidden;
2172 if hidden {
2173 self.update_visible_inline_completion(window, cx);
2174 } else {
2175 self.refresh_inline_completion(true, false, window, cx);
2176 }
2177 }
2178 }
2179
2180 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2181 self.menu_inline_completions_policy = value;
2182 }
2183
2184 pub fn set_autoindent(&mut self, autoindent: bool) {
2185 if autoindent {
2186 self.autoindent_mode = Some(AutoindentMode::EachLine);
2187 } else {
2188 self.autoindent_mode = None;
2189 }
2190 }
2191
2192 pub fn read_only(&self, cx: &App) -> bool {
2193 self.read_only || self.buffer.read(cx).read_only()
2194 }
2195
2196 pub fn set_read_only(&mut self, read_only: bool) {
2197 self.read_only = read_only;
2198 }
2199
2200 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2201 self.use_autoclose = autoclose;
2202 }
2203
2204 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2205 self.use_auto_surround = auto_surround;
2206 }
2207
2208 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2209 self.auto_replace_emoji_shortcode = auto_replace;
2210 }
2211
2212 pub fn toggle_edit_predictions(
2213 &mut self,
2214 _: &ToggleEditPrediction,
2215 window: &mut Window,
2216 cx: &mut Context<Self>,
2217 ) {
2218 if self.show_inline_completions_override.is_some() {
2219 self.set_show_edit_predictions(None, window, cx);
2220 } else {
2221 let show_edit_predictions = !self.edit_predictions_enabled();
2222 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2223 }
2224 }
2225
2226 pub fn set_show_edit_predictions(
2227 &mut self,
2228 show_edit_predictions: Option<bool>,
2229 window: &mut Window,
2230 cx: &mut Context<Self>,
2231 ) {
2232 self.show_inline_completions_override = show_edit_predictions;
2233 self.update_edit_prediction_settings(cx);
2234
2235 if let Some(false) = show_edit_predictions {
2236 self.discard_inline_completion(false, cx);
2237 } else {
2238 self.refresh_inline_completion(false, true, window, cx);
2239 }
2240 }
2241
2242 fn inline_completions_disabled_in_scope(
2243 &self,
2244 buffer: &Entity<Buffer>,
2245 buffer_position: language::Anchor,
2246 cx: &App,
2247 ) -> bool {
2248 let snapshot = buffer.read(cx).snapshot();
2249 let settings = snapshot.settings_at(buffer_position, cx);
2250
2251 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2252 return false;
2253 };
2254
2255 scope.override_name().map_or(false, |scope_name| {
2256 settings
2257 .edit_predictions_disabled_in
2258 .iter()
2259 .any(|s| s == scope_name)
2260 })
2261 }
2262
2263 pub fn set_use_modal_editing(&mut self, to: bool) {
2264 self.use_modal_editing = to;
2265 }
2266
2267 pub fn use_modal_editing(&self) -> bool {
2268 self.use_modal_editing
2269 }
2270
2271 fn selections_did_change(
2272 &mut self,
2273 local: bool,
2274 old_cursor_position: &Anchor,
2275 show_completions: bool,
2276 window: &mut Window,
2277 cx: &mut Context<Self>,
2278 ) {
2279 window.invalidate_character_coordinates();
2280
2281 // Copy selections to primary selection buffer
2282 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2283 if local {
2284 let selections = self.selections.all::<usize>(cx);
2285 let buffer_handle = self.buffer.read(cx).read(cx);
2286
2287 let mut text = String::new();
2288 for (index, selection) in selections.iter().enumerate() {
2289 let text_for_selection = buffer_handle
2290 .text_for_range(selection.start..selection.end)
2291 .collect::<String>();
2292
2293 text.push_str(&text_for_selection);
2294 if index != selections.len() - 1 {
2295 text.push('\n');
2296 }
2297 }
2298
2299 if !text.is_empty() {
2300 cx.write_to_primary(ClipboardItem::new_string(text));
2301 }
2302 }
2303
2304 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2305 self.buffer.update(cx, |buffer, cx| {
2306 buffer.set_active_selections(
2307 &self.selections.disjoint_anchors(),
2308 self.selections.line_mode,
2309 self.cursor_shape,
2310 cx,
2311 )
2312 });
2313 }
2314 let display_map = self
2315 .display_map
2316 .update(cx, |display_map, cx| display_map.snapshot(cx));
2317 let buffer = &display_map.buffer_snapshot;
2318 self.add_selections_state = None;
2319 self.select_next_state = None;
2320 self.select_prev_state = None;
2321 self.select_syntax_node_history.try_clear();
2322 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2323 self.snippet_stack
2324 .invalidate(&self.selections.disjoint_anchors(), buffer);
2325 self.take_rename(false, window, cx);
2326
2327 let new_cursor_position = self.selections.newest_anchor().head();
2328
2329 self.push_to_nav_history(
2330 *old_cursor_position,
2331 Some(new_cursor_position.to_point(buffer)),
2332 false,
2333 cx,
2334 );
2335
2336 if local {
2337 let new_cursor_position = self.selections.newest_anchor().head();
2338 let mut context_menu = self.context_menu.borrow_mut();
2339 let completion_menu = match context_menu.as_ref() {
2340 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2341 _ => {
2342 *context_menu = None;
2343 None
2344 }
2345 };
2346 if let Some(buffer_id) = new_cursor_position.buffer_id {
2347 if !self.registered_buffers.contains_key(&buffer_id) {
2348 if let Some(project) = self.project.as_ref() {
2349 project.update(cx, |project, cx| {
2350 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2351 return;
2352 };
2353 self.registered_buffers.insert(
2354 buffer_id,
2355 project.register_buffer_with_language_servers(&buffer, cx),
2356 );
2357 })
2358 }
2359 }
2360 }
2361
2362 if let Some(completion_menu) = completion_menu {
2363 let cursor_position = new_cursor_position.to_offset(buffer);
2364 let (word_range, kind) =
2365 buffer.surrounding_word(completion_menu.initial_position, true);
2366 if kind == Some(CharKind::Word)
2367 && word_range.to_inclusive().contains(&cursor_position)
2368 {
2369 let mut completion_menu = completion_menu.clone();
2370 drop(context_menu);
2371
2372 let query = Self::completion_query(buffer, cursor_position);
2373 cx.spawn(async move |this, cx| {
2374 completion_menu
2375 .filter(query.as_deref(), cx.background_executor().clone())
2376 .await;
2377
2378 this.update(cx, |this, cx| {
2379 let mut context_menu = this.context_menu.borrow_mut();
2380 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2381 else {
2382 return;
2383 };
2384
2385 if menu.id > completion_menu.id {
2386 return;
2387 }
2388
2389 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2390 drop(context_menu);
2391 cx.notify();
2392 })
2393 })
2394 .detach();
2395
2396 if show_completions {
2397 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2398 }
2399 } else {
2400 drop(context_menu);
2401 self.hide_context_menu(window, cx);
2402 }
2403 } else {
2404 drop(context_menu);
2405 }
2406
2407 hide_hover(self, cx);
2408
2409 if old_cursor_position.to_display_point(&display_map).row()
2410 != new_cursor_position.to_display_point(&display_map).row()
2411 {
2412 self.available_code_actions.take();
2413 }
2414 self.refresh_code_actions(window, cx);
2415 self.refresh_document_highlights(cx);
2416 self.refresh_selected_text_highlights(window, cx);
2417 refresh_matching_bracket_highlights(self, window, cx);
2418 self.update_visible_inline_completion(window, cx);
2419 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2420 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2421 if self.git_blame_inline_enabled {
2422 self.start_inline_blame_timer(window, cx);
2423 }
2424 }
2425
2426 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2427 cx.emit(EditorEvent::SelectionsChanged { local });
2428
2429 let selections = &self.selections.disjoint;
2430 if selections.len() == 1 {
2431 cx.emit(SearchEvent::ActiveMatchChanged)
2432 }
2433 if local {
2434 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2435 let inmemory_selections = selections
2436 .iter()
2437 .map(|s| {
2438 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2439 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2440 })
2441 .collect();
2442 self.update_restoration_data(cx, |data| {
2443 data.selections = inmemory_selections;
2444 });
2445
2446 if WorkspaceSettings::get(None, cx).restore_on_startup
2447 != RestoreOnStartupBehavior::None
2448 {
2449 if let Some(workspace_id) =
2450 self.workspace.as_ref().and_then(|workspace| workspace.1)
2451 {
2452 let snapshot = self.buffer().read(cx).snapshot(cx);
2453 let selections = selections.clone();
2454 let background_executor = cx.background_executor().clone();
2455 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2456 self.serialize_selections = cx.background_spawn(async move {
2457 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2458 let db_selections = selections
2459 .iter()
2460 .map(|selection| {
2461 (
2462 selection.start.to_offset(&snapshot),
2463 selection.end.to_offset(&snapshot),
2464 )
2465 })
2466 .collect();
2467
2468 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2469 .await
2470 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2471 .log_err();
2472 });
2473 }
2474 }
2475 }
2476 }
2477
2478 cx.notify();
2479 }
2480
2481 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2482 use text::ToOffset as _;
2483 use text::ToPoint as _;
2484
2485 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2486 return;
2487 }
2488
2489 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2490 return;
2491 };
2492
2493 let snapshot = singleton.read(cx).snapshot();
2494 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2495 let display_snapshot = display_map.snapshot(cx);
2496
2497 display_snapshot
2498 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2499 .map(|fold| {
2500 fold.range.start.text_anchor.to_point(&snapshot)
2501 ..fold.range.end.text_anchor.to_point(&snapshot)
2502 })
2503 .collect()
2504 });
2505 self.update_restoration_data(cx, |data| {
2506 data.folds = inmemory_folds;
2507 });
2508
2509 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2510 return;
2511 };
2512 let background_executor = cx.background_executor().clone();
2513 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2514 let db_folds = self.display_map.update(cx, |display_map, cx| {
2515 display_map
2516 .snapshot(cx)
2517 .folds_in_range(0..snapshot.len())
2518 .map(|fold| {
2519 (
2520 fold.range.start.text_anchor.to_offset(&snapshot),
2521 fold.range.end.text_anchor.to_offset(&snapshot),
2522 )
2523 })
2524 .collect()
2525 });
2526 self.serialize_folds = cx.background_spawn(async move {
2527 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2528 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2529 .await
2530 .with_context(|| {
2531 format!(
2532 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2533 )
2534 })
2535 .log_err();
2536 });
2537 }
2538
2539 pub fn sync_selections(
2540 &mut self,
2541 other: Entity<Editor>,
2542 cx: &mut Context<Self>,
2543 ) -> gpui::Subscription {
2544 let other_selections = other.read(cx).selections.disjoint.to_vec();
2545 self.selections.change_with(cx, |selections| {
2546 selections.select_anchors(other_selections);
2547 });
2548
2549 let other_subscription =
2550 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2551 EditorEvent::SelectionsChanged { local: true } => {
2552 let other_selections = other.read(cx).selections.disjoint.to_vec();
2553 if other_selections.is_empty() {
2554 return;
2555 }
2556 this.selections.change_with(cx, |selections| {
2557 selections.select_anchors(other_selections);
2558 });
2559 }
2560 _ => {}
2561 });
2562
2563 let this_subscription =
2564 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2565 EditorEvent::SelectionsChanged { local: true } => {
2566 let these_selections = this.selections.disjoint.to_vec();
2567 if these_selections.is_empty() {
2568 return;
2569 }
2570 other.update(cx, |other_editor, cx| {
2571 other_editor.selections.change_with(cx, |selections| {
2572 selections.select_anchors(these_selections);
2573 })
2574 });
2575 }
2576 _ => {}
2577 });
2578
2579 Subscription::join(other_subscription, this_subscription)
2580 }
2581
2582 pub fn change_selections<R>(
2583 &mut self,
2584 autoscroll: Option<Autoscroll>,
2585 window: &mut Window,
2586 cx: &mut Context<Self>,
2587 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2588 ) -> R {
2589 self.change_selections_inner(autoscroll, true, window, cx, change)
2590 }
2591
2592 fn change_selections_inner<R>(
2593 &mut self,
2594 autoscroll: Option<Autoscroll>,
2595 request_completions: bool,
2596 window: &mut Window,
2597 cx: &mut Context<Self>,
2598 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2599 ) -> R {
2600 let old_cursor_position = self.selections.newest_anchor().head();
2601 self.push_to_selection_history();
2602
2603 let (changed, result) = self.selections.change_with(cx, change);
2604
2605 if changed {
2606 if let Some(autoscroll) = autoscroll {
2607 self.request_autoscroll(autoscroll, cx);
2608 }
2609 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2610
2611 if self.should_open_signature_help_automatically(
2612 &old_cursor_position,
2613 self.signature_help_state.backspace_pressed(),
2614 cx,
2615 ) {
2616 self.show_signature_help(&ShowSignatureHelp, window, cx);
2617 }
2618 self.signature_help_state.set_backspace_pressed(false);
2619 }
2620
2621 result
2622 }
2623
2624 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2625 where
2626 I: IntoIterator<Item = (Range<S>, T)>,
2627 S: ToOffset,
2628 T: Into<Arc<str>>,
2629 {
2630 if self.read_only(cx) {
2631 return;
2632 }
2633
2634 self.buffer
2635 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2636 }
2637
2638 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2639 where
2640 I: IntoIterator<Item = (Range<S>, T)>,
2641 S: ToOffset,
2642 T: Into<Arc<str>>,
2643 {
2644 if self.read_only(cx) {
2645 return;
2646 }
2647
2648 self.buffer.update(cx, |buffer, cx| {
2649 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2650 });
2651 }
2652
2653 pub fn edit_with_block_indent<I, S, T>(
2654 &mut self,
2655 edits: I,
2656 original_indent_columns: Vec<Option<u32>>,
2657 cx: &mut Context<Self>,
2658 ) where
2659 I: IntoIterator<Item = (Range<S>, T)>,
2660 S: ToOffset,
2661 T: Into<Arc<str>>,
2662 {
2663 if self.read_only(cx) {
2664 return;
2665 }
2666
2667 self.buffer.update(cx, |buffer, cx| {
2668 buffer.edit(
2669 edits,
2670 Some(AutoindentMode::Block {
2671 original_indent_columns,
2672 }),
2673 cx,
2674 )
2675 });
2676 }
2677
2678 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2679 self.hide_context_menu(window, cx);
2680
2681 match phase {
2682 SelectPhase::Begin {
2683 position,
2684 add,
2685 click_count,
2686 } => self.begin_selection(position, add, click_count, window, cx),
2687 SelectPhase::BeginColumnar {
2688 position,
2689 goal_column,
2690 reset,
2691 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2692 SelectPhase::Extend {
2693 position,
2694 click_count,
2695 } => self.extend_selection(position, click_count, window, cx),
2696 SelectPhase::Update {
2697 position,
2698 goal_column,
2699 scroll_delta,
2700 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2701 SelectPhase::End => self.end_selection(window, cx),
2702 }
2703 }
2704
2705 fn extend_selection(
2706 &mut self,
2707 position: DisplayPoint,
2708 click_count: usize,
2709 window: &mut Window,
2710 cx: &mut Context<Self>,
2711 ) {
2712 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2713 let tail = self.selections.newest::<usize>(cx).tail();
2714 self.begin_selection(position, false, click_count, window, cx);
2715
2716 let position = position.to_offset(&display_map, Bias::Left);
2717 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2718
2719 let mut pending_selection = self
2720 .selections
2721 .pending_anchor()
2722 .expect("extend_selection not called with pending selection");
2723 if position >= tail {
2724 pending_selection.start = tail_anchor;
2725 } else {
2726 pending_selection.end = tail_anchor;
2727 pending_selection.reversed = true;
2728 }
2729
2730 let mut pending_mode = self.selections.pending_mode().unwrap();
2731 match &mut pending_mode {
2732 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2733 _ => {}
2734 }
2735
2736 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2737 s.set_pending(pending_selection, pending_mode)
2738 });
2739 }
2740
2741 fn begin_selection(
2742 &mut self,
2743 position: DisplayPoint,
2744 add: bool,
2745 click_count: usize,
2746 window: &mut Window,
2747 cx: &mut Context<Self>,
2748 ) {
2749 if !self.focus_handle.is_focused(window) {
2750 self.last_focused_descendant = None;
2751 window.focus(&self.focus_handle);
2752 }
2753
2754 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2755 let buffer = &display_map.buffer_snapshot;
2756 let newest_selection = self.selections.newest_anchor().clone();
2757 let position = display_map.clip_point(position, Bias::Left);
2758
2759 let start;
2760 let end;
2761 let mode;
2762 let mut auto_scroll;
2763 match click_count {
2764 1 => {
2765 start = buffer.anchor_before(position.to_point(&display_map));
2766 end = start;
2767 mode = SelectMode::Character;
2768 auto_scroll = true;
2769 }
2770 2 => {
2771 let range = movement::surrounding_word(&display_map, position);
2772 start = buffer.anchor_before(range.start.to_point(&display_map));
2773 end = buffer.anchor_before(range.end.to_point(&display_map));
2774 mode = SelectMode::Word(start..end);
2775 auto_scroll = true;
2776 }
2777 3 => {
2778 let position = display_map
2779 .clip_point(position, Bias::Left)
2780 .to_point(&display_map);
2781 let line_start = display_map.prev_line_boundary(position).0;
2782 let next_line_start = buffer.clip_point(
2783 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2784 Bias::Left,
2785 );
2786 start = buffer.anchor_before(line_start);
2787 end = buffer.anchor_before(next_line_start);
2788 mode = SelectMode::Line(start..end);
2789 auto_scroll = true;
2790 }
2791 _ => {
2792 start = buffer.anchor_before(0);
2793 end = buffer.anchor_before(buffer.len());
2794 mode = SelectMode::All;
2795 auto_scroll = false;
2796 }
2797 }
2798 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
2799
2800 let point_to_delete: Option<usize> = {
2801 let selected_points: Vec<Selection<Point>> =
2802 self.selections.disjoint_in_range(start..end, cx);
2803
2804 if !add || click_count > 1 {
2805 None
2806 } else if !selected_points.is_empty() {
2807 Some(selected_points[0].id)
2808 } else {
2809 let clicked_point_already_selected =
2810 self.selections.disjoint.iter().find(|selection| {
2811 selection.start.to_point(buffer) == start.to_point(buffer)
2812 || selection.end.to_point(buffer) == end.to_point(buffer)
2813 });
2814
2815 clicked_point_already_selected.map(|selection| selection.id)
2816 }
2817 };
2818
2819 let selections_count = self.selections.count();
2820
2821 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
2822 if let Some(point_to_delete) = point_to_delete {
2823 s.delete(point_to_delete);
2824
2825 if selections_count == 1 {
2826 s.set_pending_anchor_range(start..end, mode);
2827 }
2828 } else {
2829 if !add {
2830 s.clear_disjoint();
2831 } else if click_count > 1 {
2832 s.delete(newest_selection.id)
2833 }
2834
2835 s.set_pending_anchor_range(start..end, mode);
2836 }
2837 });
2838 }
2839
2840 fn begin_columnar_selection(
2841 &mut self,
2842 position: DisplayPoint,
2843 goal_column: u32,
2844 reset: bool,
2845 window: &mut Window,
2846 cx: &mut Context<Self>,
2847 ) {
2848 if !self.focus_handle.is_focused(window) {
2849 self.last_focused_descendant = None;
2850 window.focus(&self.focus_handle);
2851 }
2852
2853 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2854
2855 if reset {
2856 let pointer_position = display_map
2857 .buffer_snapshot
2858 .anchor_before(position.to_point(&display_map));
2859
2860 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
2861 s.clear_disjoint();
2862 s.set_pending_anchor_range(
2863 pointer_position..pointer_position,
2864 SelectMode::Character,
2865 );
2866 });
2867 }
2868
2869 let tail = self.selections.newest::<Point>(cx).tail();
2870 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
2871
2872 if !reset {
2873 self.select_columns(
2874 tail.to_display_point(&display_map),
2875 position,
2876 goal_column,
2877 &display_map,
2878 window,
2879 cx,
2880 );
2881 }
2882 }
2883
2884 fn update_selection(
2885 &mut self,
2886 position: DisplayPoint,
2887 goal_column: u32,
2888 scroll_delta: gpui::Point<f32>,
2889 window: &mut Window,
2890 cx: &mut Context<Self>,
2891 ) {
2892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2893
2894 if let Some(tail) = self.columnar_selection_tail.as_ref() {
2895 let tail = tail.to_display_point(&display_map);
2896 self.select_columns(tail, position, goal_column, &display_map, window, cx);
2897 } else if let Some(mut pending) = self.selections.pending_anchor() {
2898 let buffer = self.buffer.read(cx).snapshot(cx);
2899 let head;
2900 let tail;
2901 let mode = self.selections.pending_mode().unwrap();
2902 match &mode {
2903 SelectMode::Character => {
2904 head = position.to_point(&display_map);
2905 tail = pending.tail().to_point(&buffer);
2906 }
2907 SelectMode::Word(original_range) => {
2908 let original_display_range = original_range.start.to_display_point(&display_map)
2909 ..original_range.end.to_display_point(&display_map);
2910 let original_buffer_range = original_display_range.start.to_point(&display_map)
2911 ..original_display_range.end.to_point(&display_map);
2912 if movement::is_inside_word(&display_map, position)
2913 || original_display_range.contains(&position)
2914 {
2915 let word_range = movement::surrounding_word(&display_map, position);
2916 if word_range.start < original_display_range.start {
2917 head = word_range.start.to_point(&display_map);
2918 } else {
2919 head = word_range.end.to_point(&display_map);
2920 }
2921 } else {
2922 head = position.to_point(&display_map);
2923 }
2924
2925 if head <= original_buffer_range.start {
2926 tail = original_buffer_range.end;
2927 } else {
2928 tail = original_buffer_range.start;
2929 }
2930 }
2931 SelectMode::Line(original_range) => {
2932 let original_range = original_range.to_point(&display_map.buffer_snapshot);
2933
2934 let position = display_map
2935 .clip_point(position, Bias::Left)
2936 .to_point(&display_map);
2937 let line_start = display_map.prev_line_boundary(position).0;
2938 let next_line_start = buffer.clip_point(
2939 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2940 Bias::Left,
2941 );
2942
2943 if line_start < original_range.start {
2944 head = line_start
2945 } else {
2946 head = next_line_start
2947 }
2948
2949 if head <= original_range.start {
2950 tail = original_range.end;
2951 } else {
2952 tail = original_range.start;
2953 }
2954 }
2955 SelectMode::All => {
2956 return;
2957 }
2958 };
2959
2960 if head < tail {
2961 pending.start = buffer.anchor_before(head);
2962 pending.end = buffer.anchor_before(tail);
2963 pending.reversed = true;
2964 } else {
2965 pending.start = buffer.anchor_before(tail);
2966 pending.end = buffer.anchor_before(head);
2967 pending.reversed = false;
2968 }
2969
2970 self.change_selections(None, window, cx, |s| {
2971 s.set_pending(pending, mode);
2972 });
2973 } else {
2974 log::error!("update_selection dispatched with no pending selection");
2975 return;
2976 }
2977
2978 self.apply_scroll_delta(scroll_delta, window, cx);
2979 cx.notify();
2980 }
2981
2982 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2983 self.columnar_selection_tail.take();
2984 if self.selections.pending_anchor().is_some() {
2985 let selections = self.selections.all::<usize>(cx);
2986 self.change_selections(None, window, cx, |s| {
2987 s.select(selections);
2988 s.clear_pending();
2989 });
2990 }
2991 }
2992
2993 fn select_columns(
2994 &mut self,
2995 tail: DisplayPoint,
2996 head: DisplayPoint,
2997 goal_column: u32,
2998 display_map: &DisplaySnapshot,
2999 window: &mut Window,
3000 cx: &mut Context<Self>,
3001 ) {
3002 let start_row = cmp::min(tail.row(), head.row());
3003 let end_row = cmp::max(tail.row(), head.row());
3004 let start_column = cmp::min(tail.column(), goal_column);
3005 let end_column = cmp::max(tail.column(), goal_column);
3006 let reversed = start_column < tail.column();
3007
3008 let selection_ranges = (start_row.0..=end_row.0)
3009 .map(DisplayRow)
3010 .filter_map(|row| {
3011 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3012 let start = display_map
3013 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3014 .to_point(display_map);
3015 let end = display_map
3016 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3017 .to_point(display_map);
3018 if reversed {
3019 Some(end..start)
3020 } else {
3021 Some(start..end)
3022 }
3023 } else {
3024 None
3025 }
3026 })
3027 .collect::<Vec<_>>();
3028
3029 self.change_selections(None, window, cx, |s| {
3030 s.select_ranges(selection_ranges);
3031 });
3032 cx.notify();
3033 }
3034
3035 pub fn has_pending_nonempty_selection(&self) -> bool {
3036 let pending_nonempty_selection = match self.selections.pending_anchor() {
3037 Some(Selection { start, end, .. }) => start != end,
3038 None => false,
3039 };
3040
3041 pending_nonempty_selection
3042 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3043 }
3044
3045 pub fn has_pending_selection(&self) -> bool {
3046 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3047 }
3048
3049 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3050 self.selection_mark_mode = false;
3051
3052 if self.clear_expanded_diff_hunks(cx) {
3053 cx.notify();
3054 return;
3055 }
3056 if self.dismiss_menus_and_popups(true, window, cx) {
3057 return;
3058 }
3059
3060 if self.mode.is_full()
3061 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3062 {
3063 return;
3064 }
3065
3066 cx.propagate();
3067 }
3068
3069 pub fn dismiss_menus_and_popups(
3070 &mut self,
3071 is_user_requested: bool,
3072 window: &mut Window,
3073 cx: &mut Context<Self>,
3074 ) -> bool {
3075 if self.take_rename(false, window, cx).is_some() {
3076 return true;
3077 }
3078
3079 if hide_hover(self, cx) {
3080 return true;
3081 }
3082
3083 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3084 return true;
3085 }
3086
3087 if self.hide_context_menu(window, cx).is_some() {
3088 return true;
3089 }
3090
3091 if self.mouse_context_menu.take().is_some() {
3092 return true;
3093 }
3094
3095 if is_user_requested && self.discard_inline_completion(true, cx) {
3096 return true;
3097 }
3098
3099 if self.snippet_stack.pop().is_some() {
3100 return true;
3101 }
3102
3103 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3104 self.dismiss_diagnostics(cx);
3105 return true;
3106 }
3107
3108 false
3109 }
3110
3111 fn linked_editing_ranges_for(
3112 &self,
3113 selection: Range<text::Anchor>,
3114 cx: &App,
3115 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3116 if self.linked_edit_ranges.is_empty() {
3117 return None;
3118 }
3119 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3120 selection.end.buffer_id.and_then(|end_buffer_id| {
3121 if selection.start.buffer_id != Some(end_buffer_id) {
3122 return None;
3123 }
3124 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3125 let snapshot = buffer.read(cx).snapshot();
3126 self.linked_edit_ranges
3127 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3128 .map(|ranges| (ranges, snapshot, buffer))
3129 })?;
3130 use text::ToOffset as TO;
3131 // find offset from the start of current range to current cursor position
3132 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3133
3134 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3135 let start_difference = start_offset - start_byte_offset;
3136 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3137 let end_difference = end_offset - start_byte_offset;
3138 // Current range has associated linked ranges.
3139 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3140 for range in linked_ranges.iter() {
3141 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3142 let end_offset = start_offset + end_difference;
3143 let start_offset = start_offset + start_difference;
3144 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3145 continue;
3146 }
3147 if self.selections.disjoint_anchor_ranges().any(|s| {
3148 if s.start.buffer_id != selection.start.buffer_id
3149 || s.end.buffer_id != selection.end.buffer_id
3150 {
3151 return false;
3152 }
3153 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3154 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3155 }) {
3156 continue;
3157 }
3158 let start = buffer_snapshot.anchor_after(start_offset);
3159 let end = buffer_snapshot.anchor_after(end_offset);
3160 linked_edits
3161 .entry(buffer.clone())
3162 .or_default()
3163 .push(start..end);
3164 }
3165 Some(linked_edits)
3166 }
3167
3168 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3169 let text: Arc<str> = text.into();
3170
3171 if self.read_only(cx) {
3172 return;
3173 }
3174
3175 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3176
3177 let selections = self.selections.all_adjusted(cx);
3178 let mut bracket_inserted = false;
3179 let mut edits = Vec::new();
3180 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3181 let mut new_selections = Vec::with_capacity(selections.len());
3182 let mut new_autoclose_regions = Vec::new();
3183 let snapshot = self.buffer.read(cx).read(cx);
3184 let mut clear_linked_edit_ranges = false;
3185
3186 for (selection, autoclose_region) in
3187 self.selections_with_autoclose_regions(selections, &snapshot)
3188 {
3189 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3190 // Determine if the inserted text matches the opening or closing
3191 // bracket of any of this language's bracket pairs.
3192 let mut bracket_pair = None;
3193 let mut is_bracket_pair_start = false;
3194 let mut is_bracket_pair_end = false;
3195 if !text.is_empty() {
3196 let mut bracket_pair_matching_end = None;
3197 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3198 // and they are removing the character that triggered IME popup.
3199 for (pair, enabled) in scope.brackets() {
3200 if !pair.close && !pair.surround {
3201 continue;
3202 }
3203
3204 if enabled && pair.start.ends_with(text.as_ref()) {
3205 let prefix_len = pair.start.len() - text.len();
3206 let preceding_text_matches_prefix = prefix_len == 0
3207 || (selection.start.column >= (prefix_len as u32)
3208 && snapshot.contains_str_at(
3209 Point::new(
3210 selection.start.row,
3211 selection.start.column - (prefix_len as u32),
3212 ),
3213 &pair.start[..prefix_len],
3214 ));
3215 if preceding_text_matches_prefix {
3216 bracket_pair = Some(pair.clone());
3217 is_bracket_pair_start = true;
3218 break;
3219 }
3220 }
3221 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3222 {
3223 // take first bracket pair matching end, but don't break in case a later bracket
3224 // pair matches start
3225 bracket_pair_matching_end = Some(pair.clone());
3226 }
3227 }
3228 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3229 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3230 is_bracket_pair_end = true;
3231 }
3232 }
3233
3234 if let Some(bracket_pair) = bracket_pair {
3235 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3236 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3237 let auto_surround =
3238 self.use_auto_surround && snapshot_settings.use_auto_surround;
3239 if selection.is_empty() {
3240 if is_bracket_pair_start {
3241 // If the inserted text is a suffix of an opening bracket and the
3242 // selection is preceded by the rest of the opening bracket, then
3243 // insert the closing bracket.
3244 let following_text_allows_autoclose = snapshot
3245 .chars_at(selection.start)
3246 .next()
3247 .map_or(true, |c| scope.should_autoclose_before(c));
3248
3249 let preceding_text_allows_autoclose = selection.start.column == 0
3250 || snapshot.reversed_chars_at(selection.start).next().map_or(
3251 true,
3252 |c| {
3253 bracket_pair.start != bracket_pair.end
3254 || !snapshot
3255 .char_classifier_at(selection.start)
3256 .is_word(c)
3257 },
3258 );
3259
3260 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3261 && bracket_pair.start.len() == 1
3262 {
3263 let target = bracket_pair.start.chars().next().unwrap();
3264 let current_line_count = snapshot
3265 .reversed_chars_at(selection.start)
3266 .take_while(|&c| c != '\n')
3267 .filter(|&c| c == target)
3268 .count();
3269 current_line_count % 2 == 1
3270 } else {
3271 false
3272 };
3273
3274 if autoclose
3275 && bracket_pair.close
3276 && following_text_allows_autoclose
3277 && preceding_text_allows_autoclose
3278 && !is_closing_quote
3279 {
3280 let anchor = snapshot.anchor_before(selection.end);
3281 new_selections.push((selection.map(|_| anchor), text.len()));
3282 new_autoclose_regions.push((
3283 anchor,
3284 text.len(),
3285 selection.id,
3286 bracket_pair.clone(),
3287 ));
3288 edits.push((
3289 selection.range(),
3290 format!("{}{}", text, bracket_pair.end).into(),
3291 ));
3292 bracket_inserted = true;
3293 continue;
3294 }
3295 }
3296
3297 if let Some(region) = autoclose_region {
3298 // If the selection is followed by an auto-inserted closing bracket,
3299 // then don't insert that closing bracket again; just move the selection
3300 // past the closing bracket.
3301 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3302 && text.as_ref() == region.pair.end.as_str();
3303 if should_skip {
3304 let anchor = snapshot.anchor_after(selection.end);
3305 new_selections
3306 .push((selection.map(|_| anchor), region.pair.end.len()));
3307 continue;
3308 }
3309 }
3310
3311 let always_treat_brackets_as_autoclosed = snapshot
3312 .language_settings_at(selection.start, cx)
3313 .always_treat_brackets_as_autoclosed;
3314 if always_treat_brackets_as_autoclosed
3315 && is_bracket_pair_end
3316 && snapshot.contains_str_at(selection.end, text.as_ref())
3317 {
3318 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3319 // and the inserted text is a closing bracket and the selection is followed
3320 // by the closing bracket then move the selection past the closing bracket.
3321 let anchor = snapshot.anchor_after(selection.end);
3322 new_selections.push((selection.map(|_| anchor), text.len()));
3323 continue;
3324 }
3325 }
3326 // If an opening bracket is 1 character long and is typed while
3327 // text is selected, then surround that text with the bracket pair.
3328 else if auto_surround
3329 && bracket_pair.surround
3330 && is_bracket_pair_start
3331 && bracket_pair.start.chars().count() == 1
3332 {
3333 edits.push((selection.start..selection.start, text.clone()));
3334 edits.push((
3335 selection.end..selection.end,
3336 bracket_pair.end.as_str().into(),
3337 ));
3338 bracket_inserted = true;
3339 new_selections.push((
3340 Selection {
3341 id: selection.id,
3342 start: snapshot.anchor_after(selection.start),
3343 end: snapshot.anchor_before(selection.end),
3344 reversed: selection.reversed,
3345 goal: selection.goal,
3346 },
3347 0,
3348 ));
3349 continue;
3350 }
3351 }
3352 }
3353
3354 if self.auto_replace_emoji_shortcode
3355 && selection.is_empty()
3356 && text.as_ref().ends_with(':')
3357 {
3358 if let Some(possible_emoji_short_code) =
3359 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3360 {
3361 if !possible_emoji_short_code.is_empty() {
3362 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3363 let emoji_shortcode_start = Point::new(
3364 selection.start.row,
3365 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3366 );
3367
3368 // Remove shortcode from buffer
3369 edits.push((
3370 emoji_shortcode_start..selection.start,
3371 "".to_string().into(),
3372 ));
3373 new_selections.push((
3374 Selection {
3375 id: selection.id,
3376 start: snapshot.anchor_after(emoji_shortcode_start),
3377 end: snapshot.anchor_before(selection.start),
3378 reversed: selection.reversed,
3379 goal: selection.goal,
3380 },
3381 0,
3382 ));
3383
3384 // Insert emoji
3385 let selection_start_anchor = snapshot.anchor_after(selection.start);
3386 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3387 edits.push((selection.start..selection.end, emoji.to_string().into()));
3388
3389 continue;
3390 }
3391 }
3392 }
3393 }
3394
3395 // If not handling any auto-close operation, then just replace the selected
3396 // text with the given input and move the selection to the end of the
3397 // newly inserted text.
3398 let anchor = snapshot.anchor_after(selection.end);
3399 if !self.linked_edit_ranges.is_empty() {
3400 let start_anchor = snapshot.anchor_before(selection.start);
3401
3402 let is_word_char = text.chars().next().map_or(true, |char| {
3403 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3404 classifier.is_word(char)
3405 });
3406
3407 if is_word_char {
3408 if let Some(ranges) = self
3409 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3410 {
3411 for (buffer, edits) in ranges {
3412 linked_edits
3413 .entry(buffer.clone())
3414 .or_default()
3415 .extend(edits.into_iter().map(|range| (range, text.clone())));
3416 }
3417 }
3418 } else {
3419 clear_linked_edit_ranges = true;
3420 }
3421 }
3422
3423 new_selections.push((selection.map(|_| anchor), 0));
3424 edits.push((selection.start..selection.end, text.clone()));
3425 }
3426
3427 drop(snapshot);
3428
3429 self.transact(window, cx, |this, window, cx| {
3430 if clear_linked_edit_ranges {
3431 this.linked_edit_ranges.clear();
3432 }
3433 let initial_buffer_versions =
3434 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3435
3436 this.buffer.update(cx, |buffer, cx| {
3437 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3438 });
3439 for (buffer, edits) in linked_edits {
3440 buffer.update(cx, |buffer, cx| {
3441 let snapshot = buffer.snapshot();
3442 let edits = edits
3443 .into_iter()
3444 .map(|(range, text)| {
3445 use text::ToPoint as TP;
3446 let end_point = TP::to_point(&range.end, &snapshot);
3447 let start_point = TP::to_point(&range.start, &snapshot);
3448 (start_point..end_point, text)
3449 })
3450 .sorted_by_key(|(range, _)| range.start);
3451 buffer.edit(edits, None, cx);
3452 })
3453 }
3454 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3455 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3456 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3457 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3458 .zip(new_selection_deltas)
3459 .map(|(selection, delta)| Selection {
3460 id: selection.id,
3461 start: selection.start + delta,
3462 end: selection.end + delta,
3463 reversed: selection.reversed,
3464 goal: SelectionGoal::None,
3465 })
3466 .collect::<Vec<_>>();
3467
3468 let mut i = 0;
3469 for (position, delta, selection_id, pair) in new_autoclose_regions {
3470 let position = position.to_offset(&map.buffer_snapshot) + delta;
3471 let start = map.buffer_snapshot.anchor_before(position);
3472 let end = map.buffer_snapshot.anchor_after(position);
3473 while let Some(existing_state) = this.autoclose_regions.get(i) {
3474 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3475 Ordering::Less => i += 1,
3476 Ordering::Greater => break,
3477 Ordering::Equal => {
3478 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3479 Ordering::Less => i += 1,
3480 Ordering::Equal => break,
3481 Ordering::Greater => break,
3482 }
3483 }
3484 }
3485 }
3486 this.autoclose_regions.insert(
3487 i,
3488 AutocloseRegion {
3489 selection_id,
3490 range: start..end,
3491 pair,
3492 },
3493 );
3494 }
3495
3496 let had_active_inline_completion = this.has_active_inline_completion();
3497 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3498 s.select(new_selections)
3499 });
3500
3501 if !bracket_inserted {
3502 if let Some(on_type_format_task) =
3503 this.trigger_on_type_formatting(text.to_string(), window, cx)
3504 {
3505 on_type_format_task.detach_and_log_err(cx);
3506 }
3507 }
3508
3509 let editor_settings = EditorSettings::get_global(cx);
3510 if bracket_inserted
3511 && (editor_settings.auto_signature_help
3512 || editor_settings.show_signature_help_after_edits)
3513 {
3514 this.show_signature_help(&ShowSignatureHelp, window, cx);
3515 }
3516
3517 let trigger_in_words =
3518 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3519 if this.hard_wrap.is_some() {
3520 let latest: Range<Point> = this.selections.newest(cx).range();
3521 if latest.is_empty()
3522 && this
3523 .buffer()
3524 .read(cx)
3525 .snapshot(cx)
3526 .line_len(MultiBufferRow(latest.start.row))
3527 == latest.start.column
3528 {
3529 this.rewrap_impl(
3530 RewrapOptions {
3531 override_language_settings: true,
3532 preserve_existing_whitespace: true,
3533 },
3534 cx,
3535 )
3536 }
3537 }
3538 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3539 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3540 this.refresh_inline_completion(true, false, window, cx);
3541 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3542 });
3543 }
3544
3545 fn find_possible_emoji_shortcode_at_position(
3546 snapshot: &MultiBufferSnapshot,
3547 position: Point,
3548 ) -> Option<String> {
3549 let mut chars = Vec::new();
3550 let mut found_colon = false;
3551 for char in snapshot.reversed_chars_at(position).take(100) {
3552 // Found a possible emoji shortcode in the middle of the buffer
3553 if found_colon {
3554 if char.is_whitespace() {
3555 chars.reverse();
3556 return Some(chars.iter().collect());
3557 }
3558 // If the previous character is not a whitespace, we are in the middle of a word
3559 // and we only want to complete the shortcode if the word is made up of other emojis
3560 let mut containing_word = String::new();
3561 for ch in snapshot
3562 .reversed_chars_at(position)
3563 .skip(chars.len() + 1)
3564 .take(100)
3565 {
3566 if ch.is_whitespace() {
3567 break;
3568 }
3569 containing_word.push(ch);
3570 }
3571 let containing_word = containing_word.chars().rev().collect::<String>();
3572 if util::word_consists_of_emojis(containing_word.as_str()) {
3573 chars.reverse();
3574 return Some(chars.iter().collect());
3575 }
3576 }
3577
3578 if char.is_whitespace() || !char.is_ascii() {
3579 return None;
3580 }
3581 if char == ':' {
3582 found_colon = true;
3583 } else {
3584 chars.push(char);
3585 }
3586 }
3587 // Found a possible emoji shortcode at the beginning of the buffer
3588 chars.reverse();
3589 Some(chars.iter().collect())
3590 }
3591
3592 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3594 self.transact(window, cx, |this, window, cx| {
3595 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3596 let selections = this.selections.all::<usize>(cx);
3597 let multi_buffer = this.buffer.read(cx);
3598 let buffer = multi_buffer.snapshot(cx);
3599 selections
3600 .iter()
3601 .map(|selection| {
3602 let start_point = selection.start.to_point(&buffer);
3603 let mut indent =
3604 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3605 indent.len = cmp::min(indent.len, start_point.column);
3606 let start = selection.start;
3607 let end = selection.end;
3608 let selection_is_empty = start == end;
3609 let language_scope = buffer.language_scope_at(start);
3610 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3611 &language_scope
3612 {
3613 let insert_extra_newline =
3614 insert_extra_newline_brackets(&buffer, start..end, language)
3615 || insert_extra_newline_tree_sitter(&buffer, start..end);
3616
3617 // Comment extension on newline is allowed only for cursor selections
3618 let comment_delimiter = maybe!({
3619 if !selection_is_empty {
3620 return None;
3621 }
3622
3623 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3624 return None;
3625 }
3626
3627 let delimiters = language.line_comment_prefixes();
3628 let max_len_of_delimiter =
3629 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3630 let (snapshot, range) =
3631 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3632
3633 let mut index_of_first_non_whitespace = 0;
3634 let comment_candidate = snapshot
3635 .chars_for_range(range)
3636 .skip_while(|c| {
3637 let should_skip = c.is_whitespace();
3638 if should_skip {
3639 index_of_first_non_whitespace += 1;
3640 }
3641 should_skip
3642 })
3643 .take(max_len_of_delimiter)
3644 .collect::<String>();
3645 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3646 comment_candidate.starts_with(comment_prefix.as_ref())
3647 })?;
3648 let cursor_is_placed_after_comment_marker =
3649 index_of_first_non_whitespace + comment_prefix.len()
3650 <= start_point.column as usize;
3651 if cursor_is_placed_after_comment_marker {
3652 Some(comment_prefix.clone())
3653 } else {
3654 None
3655 }
3656 });
3657 (comment_delimiter, insert_extra_newline)
3658 } else {
3659 (None, false)
3660 };
3661
3662 let capacity_for_delimiter = comment_delimiter
3663 .as_deref()
3664 .map(str::len)
3665 .unwrap_or_default();
3666 let mut new_text =
3667 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3668 new_text.push('\n');
3669 new_text.extend(indent.chars());
3670 if let Some(delimiter) = &comment_delimiter {
3671 new_text.push_str(delimiter);
3672 }
3673 if insert_extra_newline {
3674 new_text = new_text.repeat(2);
3675 }
3676
3677 let anchor = buffer.anchor_after(end);
3678 let new_selection = selection.map(|_| anchor);
3679 (
3680 (start..end, new_text),
3681 (insert_extra_newline, new_selection),
3682 )
3683 })
3684 .unzip()
3685 };
3686
3687 this.edit_with_autoindent(edits, cx);
3688 let buffer = this.buffer.read(cx).snapshot(cx);
3689 let new_selections = selection_fixup_info
3690 .into_iter()
3691 .map(|(extra_newline_inserted, new_selection)| {
3692 let mut cursor = new_selection.end.to_point(&buffer);
3693 if extra_newline_inserted {
3694 cursor.row -= 1;
3695 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3696 }
3697 new_selection.map(|_| cursor)
3698 })
3699 .collect();
3700
3701 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3702 s.select(new_selections)
3703 });
3704 this.refresh_inline_completion(true, false, window, cx);
3705 });
3706 }
3707
3708 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3709 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3710
3711 let buffer = self.buffer.read(cx);
3712 let snapshot = buffer.snapshot(cx);
3713
3714 let mut edits = Vec::new();
3715 let mut rows = Vec::new();
3716
3717 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3718 let cursor = selection.head();
3719 let row = cursor.row;
3720
3721 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3722
3723 let newline = "\n".to_string();
3724 edits.push((start_of_line..start_of_line, newline));
3725
3726 rows.push(row + rows_inserted as u32);
3727 }
3728
3729 self.transact(window, cx, |editor, window, cx| {
3730 editor.edit(edits, cx);
3731
3732 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3733 let mut index = 0;
3734 s.move_cursors_with(|map, _, _| {
3735 let row = rows[index];
3736 index += 1;
3737
3738 let point = Point::new(row, 0);
3739 let boundary = map.next_line_boundary(point).1;
3740 let clipped = map.clip_point(boundary, Bias::Left);
3741
3742 (clipped, SelectionGoal::None)
3743 });
3744 });
3745
3746 let mut indent_edits = Vec::new();
3747 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3748 for row in rows {
3749 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3750 for (row, indent) in indents {
3751 if indent.len == 0 {
3752 continue;
3753 }
3754
3755 let text = match indent.kind {
3756 IndentKind::Space => " ".repeat(indent.len as usize),
3757 IndentKind::Tab => "\t".repeat(indent.len as usize),
3758 };
3759 let point = Point::new(row.0, 0);
3760 indent_edits.push((point..point, text));
3761 }
3762 }
3763 editor.edit(indent_edits, cx);
3764 });
3765 }
3766
3767 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3768 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3769
3770 let buffer = self.buffer.read(cx);
3771 let snapshot = buffer.snapshot(cx);
3772
3773 let mut edits = Vec::new();
3774 let mut rows = Vec::new();
3775 let mut rows_inserted = 0;
3776
3777 for selection in self.selections.all_adjusted(cx) {
3778 let cursor = selection.head();
3779 let row = cursor.row;
3780
3781 let point = Point::new(row + 1, 0);
3782 let start_of_line = snapshot.clip_point(point, Bias::Left);
3783
3784 let newline = "\n".to_string();
3785 edits.push((start_of_line..start_of_line, newline));
3786
3787 rows_inserted += 1;
3788 rows.push(row + rows_inserted);
3789 }
3790
3791 self.transact(window, cx, |editor, window, cx| {
3792 editor.edit(edits, cx);
3793
3794 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3795 let mut index = 0;
3796 s.move_cursors_with(|map, _, _| {
3797 let row = rows[index];
3798 index += 1;
3799
3800 let point = Point::new(row, 0);
3801 let boundary = map.next_line_boundary(point).1;
3802 let clipped = map.clip_point(boundary, Bias::Left);
3803
3804 (clipped, SelectionGoal::None)
3805 });
3806 });
3807
3808 let mut indent_edits = Vec::new();
3809 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3810 for row in rows {
3811 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3812 for (row, indent) in indents {
3813 if indent.len == 0 {
3814 continue;
3815 }
3816
3817 let text = match indent.kind {
3818 IndentKind::Space => " ".repeat(indent.len as usize),
3819 IndentKind::Tab => "\t".repeat(indent.len as usize),
3820 };
3821 let point = Point::new(row.0, 0);
3822 indent_edits.push((point..point, text));
3823 }
3824 }
3825 editor.edit(indent_edits, cx);
3826 });
3827 }
3828
3829 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3830 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
3831 original_indent_columns: Vec::new(),
3832 });
3833 self.insert_with_autoindent_mode(text, autoindent, window, cx);
3834 }
3835
3836 fn insert_with_autoindent_mode(
3837 &mut self,
3838 text: &str,
3839 autoindent_mode: Option<AutoindentMode>,
3840 window: &mut Window,
3841 cx: &mut Context<Self>,
3842 ) {
3843 if self.read_only(cx) {
3844 return;
3845 }
3846
3847 let text: Arc<str> = text.into();
3848 self.transact(window, cx, |this, window, cx| {
3849 let old_selections = this.selections.all_adjusted(cx);
3850 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
3851 let anchors = {
3852 let snapshot = buffer.read(cx);
3853 old_selections
3854 .iter()
3855 .map(|s| {
3856 let anchor = snapshot.anchor_after(s.head());
3857 s.map(|_| anchor)
3858 })
3859 .collect::<Vec<_>>()
3860 };
3861 buffer.edit(
3862 old_selections
3863 .iter()
3864 .map(|s| (s.start..s.end, text.clone())),
3865 autoindent_mode,
3866 cx,
3867 );
3868 anchors
3869 });
3870
3871 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3872 s.select_anchors(selection_anchors);
3873 });
3874
3875 cx.notify();
3876 });
3877 }
3878
3879 fn trigger_completion_on_input(
3880 &mut self,
3881 text: &str,
3882 trigger_in_words: bool,
3883 window: &mut Window,
3884 cx: &mut Context<Self>,
3885 ) {
3886 let ignore_completion_provider = self
3887 .context_menu
3888 .borrow()
3889 .as_ref()
3890 .map(|menu| match menu {
3891 CodeContextMenu::Completions(completions_menu) => {
3892 completions_menu.ignore_completion_provider
3893 }
3894 CodeContextMenu::CodeActions(_) => false,
3895 })
3896 .unwrap_or(false);
3897
3898 if ignore_completion_provider {
3899 self.show_word_completions(&ShowWordCompletions, window, cx);
3900 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
3901 self.show_completions(
3902 &ShowCompletions {
3903 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
3904 },
3905 window,
3906 cx,
3907 );
3908 } else {
3909 self.hide_context_menu(window, cx);
3910 }
3911 }
3912
3913 fn is_completion_trigger(
3914 &self,
3915 text: &str,
3916 trigger_in_words: bool,
3917 cx: &mut Context<Self>,
3918 ) -> bool {
3919 let position = self.selections.newest_anchor().head();
3920 let multibuffer = self.buffer.read(cx);
3921 let Some(buffer) = position
3922 .buffer_id
3923 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
3924 else {
3925 return false;
3926 };
3927
3928 if let Some(completion_provider) = &self.completion_provider {
3929 completion_provider.is_completion_trigger(
3930 &buffer,
3931 position.text_anchor,
3932 text,
3933 trigger_in_words,
3934 cx,
3935 )
3936 } else {
3937 false
3938 }
3939 }
3940
3941 /// If any empty selections is touching the start of its innermost containing autoclose
3942 /// region, expand it to select the brackets.
3943 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3944 let selections = self.selections.all::<usize>(cx);
3945 let buffer = self.buffer.read(cx).read(cx);
3946 let new_selections = self
3947 .selections_with_autoclose_regions(selections, &buffer)
3948 .map(|(mut selection, region)| {
3949 if !selection.is_empty() {
3950 return selection;
3951 }
3952
3953 if let Some(region) = region {
3954 let mut range = region.range.to_offset(&buffer);
3955 if selection.start == range.start && range.start >= region.pair.start.len() {
3956 range.start -= region.pair.start.len();
3957 if buffer.contains_str_at(range.start, ®ion.pair.start)
3958 && buffer.contains_str_at(range.end, ®ion.pair.end)
3959 {
3960 range.end += region.pair.end.len();
3961 selection.start = range.start;
3962 selection.end = range.end;
3963
3964 return selection;
3965 }
3966 }
3967 }
3968
3969 let always_treat_brackets_as_autoclosed = buffer
3970 .language_settings_at(selection.start, cx)
3971 .always_treat_brackets_as_autoclosed;
3972
3973 if !always_treat_brackets_as_autoclosed {
3974 return selection;
3975 }
3976
3977 if let Some(scope) = buffer.language_scope_at(selection.start) {
3978 for (pair, enabled) in scope.brackets() {
3979 if !enabled || !pair.close {
3980 continue;
3981 }
3982
3983 if buffer.contains_str_at(selection.start, &pair.end) {
3984 let pair_start_len = pair.start.len();
3985 if buffer.contains_str_at(
3986 selection.start.saturating_sub(pair_start_len),
3987 &pair.start,
3988 ) {
3989 selection.start -= pair_start_len;
3990 selection.end += pair.end.len();
3991
3992 return selection;
3993 }
3994 }
3995 }
3996 }
3997
3998 selection
3999 })
4000 .collect();
4001
4002 drop(buffer);
4003 self.change_selections(None, window, cx, |selections| {
4004 selections.select(new_selections)
4005 });
4006 }
4007
4008 /// Iterate the given selections, and for each one, find the smallest surrounding
4009 /// autoclose region. This uses the ordering of the selections and the autoclose
4010 /// regions to avoid repeated comparisons.
4011 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4012 &'a self,
4013 selections: impl IntoIterator<Item = Selection<D>>,
4014 buffer: &'a MultiBufferSnapshot,
4015 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4016 let mut i = 0;
4017 let mut regions = self.autoclose_regions.as_slice();
4018 selections.into_iter().map(move |selection| {
4019 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4020
4021 let mut enclosing = None;
4022 while let Some(pair_state) = regions.get(i) {
4023 if pair_state.range.end.to_offset(buffer) < range.start {
4024 regions = ®ions[i + 1..];
4025 i = 0;
4026 } else if pair_state.range.start.to_offset(buffer) > range.end {
4027 break;
4028 } else {
4029 if pair_state.selection_id == selection.id {
4030 enclosing = Some(pair_state);
4031 }
4032 i += 1;
4033 }
4034 }
4035
4036 (selection, enclosing)
4037 })
4038 }
4039
4040 /// Remove any autoclose regions that no longer contain their selection.
4041 fn invalidate_autoclose_regions(
4042 &mut self,
4043 mut selections: &[Selection<Anchor>],
4044 buffer: &MultiBufferSnapshot,
4045 ) {
4046 self.autoclose_regions.retain(|state| {
4047 let mut i = 0;
4048 while let Some(selection) = selections.get(i) {
4049 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4050 selections = &selections[1..];
4051 continue;
4052 }
4053 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4054 break;
4055 }
4056 if selection.id == state.selection_id {
4057 return true;
4058 } else {
4059 i += 1;
4060 }
4061 }
4062 false
4063 });
4064 }
4065
4066 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4067 let offset = position.to_offset(buffer);
4068 let (word_range, kind) = buffer.surrounding_word(offset, true);
4069 if offset > word_range.start && kind == Some(CharKind::Word) {
4070 Some(
4071 buffer
4072 .text_for_range(word_range.start..offset)
4073 .collect::<String>(),
4074 )
4075 } else {
4076 None
4077 }
4078 }
4079
4080 pub fn toggle_inlay_hints(
4081 &mut self,
4082 _: &ToggleInlayHints,
4083 _: &mut Window,
4084 cx: &mut Context<Self>,
4085 ) {
4086 self.refresh_inlay_hints(
4087 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4088 cx,
4089 );
4090 }
4091
4092 pub fn inlay_hints_enabled(&self) -> bool {
4093 self.inlay_hint_cache.enabled
4094 }
4095
4096 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4097 if self.semantics_provider.is_none() || !self.mode.is_full() {
4098 return;
4099 }
4100
4101 let reason_description = reason.description();
4102 let ignore_debounce = matches!(
4103 reason,
4104 InlayHintRefreshReason::SettingsChange(_)
4105 | InlayHintRefreshReason::Toggle(_)
4106 | InlayHintRefreshReason::ExcerptsRemoved(_)
4107 | InlayHintRefreshReason::ModifiersChanged(_)
4108 );
4109 let (invalidate_cache, required_languages) = match reason {
4110 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4111 match self.inlay_hint_cache.modifiers_override(enabled) {
4112 Some(enabled) => {
4113 if enabled {
4114 (InvalidationStrategy::RefreshRequested, None)
4115 } else {
4116 self.splice_inlays(
4117 &self
4118 .visible_inlay_hints(cx)
4119 .iter()
4120 .map(|inlay| inlay.id)
4121 .collect::<Vec<InlayId>>(),
4122 Vec::new(),
4123 cx,
4124 );
4125 return;
4126 }
4127 }
4128 None => return,
4129 }
4130 }
4131 InlayHintRefreshReason::Toggle(enabled) => {
4132 if self.inlay_hint_cache.toggle(enabled) {
4133 if enabled {
4134 (InvalidationStrategy::RefreshRequested, None)
4135 } else {
4136 self.splice_inlays(
4137 &self
4138 .visible_inlay_hints(cx)
4139 .iter()
4140 .map(|inlay| inlay.id)
4141 .collect::<Vec<InlayId>>(),
4142 Vec::new(),
4143 cx,
4144 );
4145 return;
4146 }
4147 } else {
4148 return;
4149 }
4150 }
4151 InlayHintRefreshReason::SettingsChange(new_settings) => {
4152 match self.inlay_hint_cache.update_settings(
4153 &self.buffer,
4154 new_settings,
4155 self.visible_inlay_hints(cx),
4156 cx,
4157 ) {
4158 ControlFlow::Break(Some(InlaySplice {
4159 to_remove,
4160 to_insert,
4161 })) => {
4162 self.splice_inlays(&to_remove, to_insert, cx);
4163 return;
4164 }
4165 ControlFlow::Break(None) => return,
4166 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4167 }
4168 }
4169 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4170 if let Some(InlaySplice {
4171 to_remove,
4172 to_insert,
4173 }) = self.inlay_hint_cache.remove_excerpts(excerpts_removed)
4174 {
4175 self.splice_inlays(&to_remove, to_insert, cx);
4176 }
4177 return;
4178 }
4179 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4180 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4181 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4182 }
4183 InlayHintRefreshReason::RefreshRequested => {
4184 (InvalidationStrategy::RefreshRequested, None)
4185 }
4186 };
4187
4188 if let Some(InlaySplice {
4189 to_remove,
4190 to_insert,
4191 }) = self.inlay_hint_cache.spawn_hint_refresh(
4192 reason_description,
4193 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4194 invalidate_cache,
4195 ignore_debounce,
4196 cx,
4197 ) {
4198 self.splice_inlays(&to_remove, to_insert, cx);
4199 }
4200 }
4201
4202 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4203 self.display_map
4204 .read(cx)
4205 .current_inlays()
4206 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4207 .cloned()
4208 .collect()
4209 }
4210
4211 pub fn excerpts_for_inlay_hints_query(
4212 &self,
4213 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4214 cx: &mut Context<Editor>,
4215 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4216 let Some(project) = self.project.as_ref() else {
4217 return HashMap::default();
4218 };
4219 let project = project.read(cx);
4220 let multi_buffer = self.buffer().read(cx);
4221 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4222 let multi_buffer_visible_start = self
4223 .scroll_manager
4224 .anchor()
4225 .anchor
4226 .to_point(&multi_buffer_snapshot);
4227 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4228 multi_buffer_visible_start
4229 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4230 Bias::Left,
4231 );
4232 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4233 multi_buffer_snapshot
4234 .range_to_buffer_ranges(multi_buffer_visible_range)
4235 .into_iter()
4236 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4237 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4238 let buffer_file = project::File::from_dyn(buffer.file())?;
4239 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4240 let worktree_entry = buffer_worktree
4241 .read(cx)
4242 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4243 if worktree_entry.is_ignored {
4244 return None;
4245 }
4246
4247 let language = buffer.language()?;
4248 if let Some(restrict_to_languages) = restrict_to_languages {
4249 if !restrict_to_languages.contains(language) {
4250 return None;
4251 }
4252 }
4253 Some((
4254 excerpt_id,
4255 (
4256 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4257 buffer.version().clone(),
4258 excerpt_visible_range,
4259 ),
4260 ))
4261 })
4262 .collect()
4263 }
4264
4265 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4266 TextLayoutDetails {
4267 text_system: window.text_system().clone(),
4268 editor_style: self.style.clone().unwrap(),
4269 rem_size: window.rem_size(),
4270 scroll_anchor: self.scroll_manager.anchor(),
4271 visible_rows: self.visible_line_count(),
4272 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4273 }
4274 }
4275
4276 pub fn splice_inlays(
4277 &self,
4278 to_remove: &[InlayId],
4279 to_insert: Vec<Inlay>,
4280 cx: &mut Context<Self>,
4281 ) {
4282 self.display_map.update(cx, |display_map, cx| {
4283 display_map.splice_inlays(to_remove, to_insert, cx)
4284 });
4285 cx.notify();
4286 }
4287
4288 fn trigger_on_type_formatting(
4289 &self,
4290 input: String,
4291 window: &mut Window,
4292 cx: &mut Context<Self>,
4293 ) -> Option<Task<Result<()>>> {
4294 if input.len() != 1 {
4295 return None;
4296 }
4297
4298 let project = self.project.as_ref()?;
4299 let position = self.selections.newest_anchor().head();
4300 let (buffer, buffer_position) = self
4301 .buffer
4302 .read(cx)
4303 .text_anchor_for_position(position, cx)?;
4304
4305 let settings = language_settings::language_settings(
4306 buffer
4307 .read(cx)
4308 .language_at(buffer_position)
4309 .map(|l| l.name()),
4310 buffer.read(cx).file(),
4311 cx,
4312 );
4313 if !settings.use_on_type_format {
4314 return None;
4315 }
4316
4317 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4318 // hence we do LSP request & edit on host side only — add formats to host's history.
4319 let push_to_lsp_host_history = true;
4320 // If this is not the host, append its history with new edits.
4321 let push_to_client_history = project.read(cx).is_via_collab();
4322
4323 let on_type_formatting = project.update(cx, |project, cx| {
4324 project.on_type_format(
4325 buffer.clone(),
4326 buffer_position,
4327 input,
4328 push_to_lsp_host_history,
4329 cx,
4330 )
4331 });
4332 Some(cx.spawn_in(window, async move |editor, cx| {
4333 if let Some(transaction) = on_type_formatting.await? {
4334 if push_to_client_history {
4335 buffer
4336 .update(cx, |buffer, _| {
4337 buffer.push_transaction(transaction, Instant::now());
4338 buffer.finalize_last_transaction();
4339 })
4340 .ok();
4341 }
4342 editor.update(cx, |editor, cx| {
4343 editor.refresh_document_highlights(cx);
4344 })?;
4345 }
4346 Ok(())
4347 }))
4348 }
4349
4350 pub fn show_word_completions(
4351 &mut self,
4352 _: &ShowWordCompletions,
4353 window: &mut Window,
4354 cx: &mut Context<Self>,
4355 ) {
4356 self.open_completions_menu(true, None, window, cx);
4357 }
4358
4359 pub fn show_completions(
4360 &mut self,
4361 options: &ShowCompletions,
4362 window: &mut Window,
4363 cx: &mut Context<Self>,
4364 ) {
4365 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4366 }
4367
4368 fn open_completions_menu(
4369 &mut self,
4370 ignore_completion_provider: bool,
4371 trigger: Option<&str>,
4372 window: &mut Window,
4373 cx: &mut Context<Self>,
4374 ) {
4375 if self.pending_rename.is_some() {
4376 return;
4377 }
4378 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4379 return;
4380 }
4381
4382 let position = self.selections.newest_anchor().head();
4383 if position.diff_base_anchor.is_some() {
4384 return;
4385 }
4386 let (buffer, buffer_position) =
4387 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4388 output
4389 } else {
4390 return;
4391 };
4392 let buffer_snapshot = buffer.read(cx).snapshot();
4393 let show_completion_documentation = buffer_snapshot
4394 .settings_at(buffer_position, cx)
4395 .show_completion_documentation;
4396
4397 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4398
4399 let trigger_kind = match trigger {
4400 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4401 CompletionTriggerKind::TRIGGER_CHARACTER
4402 }
4403 _ => CompletionTriggerKind::INVOKED,
4404 };
4405 let completion_context = CompletionContext {
4406 trigger_character: trigger.and_then(|trigger| {
4407 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4408 Some(String::from(trigger))
4409 } else {
4410 None
4411 }
4412 }),
4413 trigger_kind,
4414 };
4415
4416 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4417 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4418 let word_to_exclude = buffer_snapshot
4419 .text_for_range(old_range.clone())
4420 .collect::<String>();
4421 (
4422 buffer_snapshot.anchor_before(old_range.start)
4423 ..buffer_snapshot.anchor_after(old_range.end),
4424 Some(word_to_exclude),
4425 )
4426 } else {
4427 (buffer_position..buffer_position, None)
4428 };
4429
4430 let completion_settings = language_settings(
4431 buffer_snapshot
4432 .language_at(buffer_position)
4433 .map(|language| language.name()),
4434 buffer_snapshot.file(),
4435 cx,
4436 )
4437 .completions;
4438
4439 // The document can be large, so stay in reasonable bounds when searching for words,
4440 // otherwise completion pop-up might be slow to appear.
4441 const WORD_LOOKUP_ROWS: u32 = 5_000;
4442 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4443 let min_word_search = buffer_snapshot.clip_point(
4444 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4445 Bias::Left,
4446 );
4447 let max_word_search = buffer_snapshot.clip_point(
4448 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4449 Bias::Right,
4450 );
4451 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4452 ..buffer_snapshot.point_to_offset(max_word_search);
4453
4454 let provider = self
4455 .completion_provider
4456 .as_ref()
4457 .filter(|_| !ignore_completion_provider);
4458 let skip_digits = query
4459 .as_ref()
4460 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4461
4462 let (mut words, provided_completions) = match provider {
4463 Some(provider) => {
4464 let completions = provider.completions(
4465 position.excerpt_id,
4466 &buffer,
4467 buffer_position,
4468 completion_context,
4469 window,
4470 cx,
4471 );
4472
4473 let words = match completion_settings.words {
4474 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4475 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4476 .background_spawn(async move {
4477 buffer_snapshot.words_in_range(WordsQuery {
4478 fuzzy_contents: None,
4479 range: word_search_range,
4480 skip_digits,
4481 })
4482 }),
4483 };
4484
4485 (words, completions)
4486 }
4487 None => (
4488 cx.background_spawn(async move {
4489 buffer_snapshot.words_in_range(WordsQuery {
4490 fuzzy_contents: None,
4491 range: word_search_range,
4492 skip_digits,
4493 })
4494 }),
4495 Task::ready(Ok(None)),
4496 ),
4497 };
4498
4499 let sort_completions = provider
4500 .as_ref()
4501 .map_or(false, |provider| provider.sort_completions());
4502
4503 let filter_completions = provider
4504 .as_ref()
4505 .map_or(true, |provider| provider.filter_completions());
4506
4507 let id = post_inc(&mut self.next_completion_id);
4508 let task = cx.spawn_in(window, async move |editor, cx| {
4509 async move {
4510 editor.update(cx, |this, _| {
4511 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4512 })?;
4513
4514 let mut completions = Vec::new();
4515 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4516 completions.extend(provided_completions);
4517 if completion_settings.words == WordsCompletionMode::Fallback {
4518 words = Task::ready(BTreeMap::default());
4519 }
4520 }
4521
4522 let mut words = words.await;
4523 if let Some(word_to_exclude) = &word_to_exclude {
4524 words.remove(word_to_exclude);
4525 }
4526 for lsp_completion in &completions {
4527 words.remove(&lsp_completion.new_text);
4528 }
4529 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4530 replace_range: old_range.clone(),
4531 new_text: word.clone(),
4532 label: CodeLabel::plain(word, None),
4533 icon_path: None,
4534 documentation: None,
4535 source: CompletionSource::BufferWord {
4536 word_range,
4537 resolved: false,
4538 },
4539 insert_text_mode: Some(InsertTextMode::AS_IS),
4540 confirm: None,
4541 }));
4542
4543 let menu = if completions.is_empty() {
4544 None
4545 } else {
4546 let mut menu = CompletionsMenu::new(
4547 id,
4548 sort_completions,
4549 show_completion_documentation,
4550 ignore_completion_provider,
4551 position,
4552 buffer.clone(),
4553 completions.into(),
4554 );
4555
4556 menu.filter(
4557 if filter_completions {
4558 query.as_deref()
4559 } else {
4560 None
4561 },
4562 cx.background_executor().clone(),
4563 )
4564 .await;
4565
4566 menu.visible().then_some(menu)
4567 };
4568
4569 editor.update_in(cx, |editor, window, cx| {
4570 match editor.context_menu.borrow().as_ref() {
4571 None => {}
4572 Some(CodeContextMenu::Completions(prev_menu)) => {
4573 if prev_menu.id > id {
4574 return;
4575 }
4576 }
4577 _ => return,
4578 }
4579
4580 if editor.focus_handle.is_focused(window) && menu.is_some() {
4581 let mut menu = menu.unwrap();
4582 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4583
4584 *editor.context_menu.borrow_mut() =
4585 Some(CodeContextMenu::Completions(menu));
4586
4587 if editor.show_edit_predictions_in_menu() {
4588 editor.update_visible_inline_completion(window, cx);
4589 } else {
4590 editor.discard_inline_completion(false, cx);
4591 }
4592
4593 cx.notify();
4594 } else if editor.completion_tasks.len() <= 1 {
4595 // If there are no more completion tasks and the last menu was
4596 // empty, we should hide it.
4597 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4598 // If it was already hidden and we don't show inline
4599 // completions in the menu, we should also show the
4600 // inline-completion when available.
4601 if was_hidden && editor.show_edit_predictions_in_menu() {
4602 editor.update_visible_inline_completion(window, cx);
4603 }
4604 }
4605 })?;
4606
4607 anyhow::Ok(())
4608 }
4609 .log_err()
4610 .await
4611 });
4612
4613 self.completion_tasks.push((id, task));
4614 }
4615
4616 #[cfg(feature = "test-support")]
4617 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4618 let menu = self.context_menu.borrow();
4619 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4620 let completions = menu.completions.borrow();
4621 Some(completions.to_vec())
4622 } else {
4623 None
4624 }
4625 }
4626
4627 pub fn confirm_completion(
4628 &mut self,
4629 action: &ConfirmCompletion,
4630 window: &mut Window,
4631 cx: &mut Context<Self>,
4632 ) -> Option<Task<Result<()>>> {
4633 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4634 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4635 }
4636
4637 pub fn confirm_completion_insert(
4638 &mut self,
4639 _: &ConfirmCompletionInsert,
4640 window: &mut Window,
4641 cx: &mut Context<Self>,
4642 ) -> Option<Task<Result<()>>> {
4643 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4644 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4645 }
4646
4647 pub fn confirm_completion_replace(
4648 &mut self,
4649 _: &ConfirmCompletionReplace,
4650 window: &mut Window,
4651 cx: &mut Context<Self>,
4652 ) -> Option<Task<Result<()>>> {
4653 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4654 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4655 }
4656
4657 pub fn compose_completion(
4658 &mut self,
4659 action: &ComposeCompletion,
4660 window: &mut Window,
4661 cx: &mut Context<Self>,
4662 ) -> Option<Task<Result<()>>> {
4663 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4664 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4665 }
4666
4667 fn do_completion(
4668 &mut self,
4669 item_ix: Option<usize>,
4670 intent: CompletionIntent,
4671 window: &mut Window,
4672 cx: &mut Context<Editor>,
4673 ) -> Option<Task<Result<()>>> {
4674 use language::ToOffset as _;
4675
4676 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4677 else {
4678 return None;
4679 };
4680
4681 let candidate_id = {
4682 let entries = completions_menu.entries.borrow();
4683 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4684 if self.show_edit_predictions_in_menu() {
4685 self.discard_inline_completion(true, cx);
4686 }
4687 mat.candidate_id
4688 };
4689
4690 let buffer_handle = completions_menu.buffer;
4691 let completion = completions_menu
4692 .completions
4693 .borrow()
4694 .get(candidate_id)?
4695 .clone();
4696 cx.stop_propagation();
4697
4698 let snippet;
4699 let new_text;
4700 if completion.is_snippet() {
4701 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4702 new_text = snippet.as_ref().unwrap().text.clone();
4703 } else {
4704 snippet = None;
4705 new_text = completion.new_text.clone();
4706 };
4707
4708 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4709 let buffer = buffer_handle.read(cx);
4710 let snapshot = self.buffer.read(cx).snapshot(cx);
4711 let replace_range_multibuffer = {
4712 let excerpt = snapshot
4713 .excerpt_containing(self.selections.newest_anchor().range())
4714 .unwrap();
4715 let multibuffer_anchor = snapshot
4716 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4717 .unwrap()
4718 ..snapshot
4719 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4720 .unwrap();
4721 multibuffer_anchor.start.to_offset(&snapshot)
4722 ..multibuffer_anchor.end.to_offset(&snapshot)
4723 };
4724 let newest_anchor = self.selections.newest_anchor();
4725 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4726 return None;
4727 }
4728
4729 let old_text = buffer
4730 .text_for_range(replace_range.clone())
4731 .collect::<String>();
4732 let lookbehind = newest_anchor
4733 .start
4734 .text_anchor
4735 .to_offset(buffer)
4736 .saturating_sub(replace_range.start);
4737 let lookahead = replace_range
4738 .end
4739 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4740 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4741 let suffix = &old_text[lookbehind.min(old_text.len())..];
4742
4743 let selections = self.selections.all::<usize>(cx);
4744 let mut edits = Vec::new();
4745 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4746
4747 for selection in &selections {
4748 let edit = if selection.id == newest_anchor.id {
4749 (replace_range_multibuffer.clone(), new_text.as_str())
4750 } else {
4751 let mut range = selection.range();
4752 let mut text = new_text.as_str();
4753
4754 // if prefix is present, don't duplicate it
4755 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4756 text = &new_text[lookbehind.min(new_text.len())..];
4757
4758 // if suffix is also present, mimic the newest cursor and replace it
4759 if selection.id != newest_anchor.id
4760 && snapshot.contains_str_at(range.end, suffix)
4761 {
4762 range.end += lookahead;
4763 }
4764 }
4765 (range, text)
4766 };
4767
4768 edits.push(edit);
4769
4770 if !self.linked_edit_ranges.is_empty() {
4771 let start_anchor = snapshot.anchor_before(selection.head());
4772 let end_anchor = snapshot.anchor_after(selection.tail());
4773 if let Some(ranges) = self
4774 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
4775 {
4776 for (buffer, edits) in ranges {
4777 linked_edits
4778 .entry(buffer.clone())
4779 .or_default()
4780 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
4781 }
4782 }
4783 }
4784 }
4785
4786 cx.emit(EditorEvent::InputHandled {
4787 utf16_range_to_replace: None,
4788 text: new_text.clone().into(),
4789 });
4790
4791 self.transact(window, cx, |this, window, cx| {
4792 if let Some(mut snippet) = snippet {
4793 snippet.text = new_text.to_string();
4794 let ranges = edits
4795 .iter()
4796 .map(|(range, _)| range.clone())
4797 .collect::<Vec<_>>();
4798 this.insert_snippet(&ranges, snippet, window, cx).log_err();
4799 } else {
4800 this.buffer.update(cx, |buffer, cx| {
4801 let auto_indent = if completion.insert_text_mode == Some(InsertTextMode::AS_IS)
4802 {
4803 None
4804 } else {
4805 this.autoindent_mode.clone()
4806 };
4807 buffer.edit(edits, auto_indent, cx);
4808 });
4809 }
4810 for (buffer, edits) in linked_edits {
4811 buffer.update(cx, |buffer, cx| {
4812 let snapshot = buffer.snapshot();
4813 let edits = edits
4814 .into_iter()
4815 .map(|(range, text)| {
4816 use text::ToPoint as TP;
4817 let end_point = TP::to_point(&range.end, &snapshot);
4818 let start_point = TP::to_point(&range.start, &snapshot);
4819 (start_point..end_point, text)
4820 })
4821 .sorted_by_key(|(range, _)| range.start);
4822 buffer.edit(edits, None, cx);
4823 })
4824 }
4825
4826 this.refresh_inline_completion(true, false, window, cx);
4827 });
4828
4829 let show_new_completions_on_confirm = completion
4830 .confirm
4831 .as_ref()
4832 .map_or(false, |confirm| confirm(intent, window, cx));
4833 if show_new_completions_on_confirm {
4834 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
4835 }
4836
4837 let provider = self.completion_provider.as_ref()?;
4838 drop(completion);
4839 let apply_edits = provider.apply_additional_edits_for_completion(
4840 buffer_handle,
4841 completions_menu.completions.clone(),
4842 candidate_id,
4843 true,
4844 cx,
4845 );
4846
4847 let editor_settings = EditorSettings::get_global(cx);
4848 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
4849 // After the code completion is finished, users often want to know what signatures are needed.
4850 // so we should automatically call signature_help
4851 self.show_signature_help(&ShowSignatureHelp, window, cx);
4852 }
4853
4854 Some(cx.foreground_executor().spawn(async move {
4855 apply_edits.await?;
4856 Ok(())
4857 }))
4858 }
4859
4860 fn prepare_code_actions_task(
4861 &mut self,
4862 action: &ToggleCodeActions,
4863 window: &mut Window,
4864 cx: &mut Context<Self>,
4865 ) -> Task<Option<(Entity<Buffer>, CodeActionContents)>> {
4866 let snapshot = self.snapshot(window, cx);
4867 let multibuffer_point = action
4868 .deployed_from_indicator
4869 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
4870 .unwrap_or_else(|| self.selections.newest::<Point>(cx).head());
4871
4872 let Some((buffer, buffer_row)) = snapshot
4873 .buffer_snapshot
4874 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
4875 .and_then(|(buffer_snapshot, range)| {
4876 self.buffer
4877 .read(cx)
4878 .buffer(buffer_snapshot.remote_id())
4879 .map(|buffer| (buffer, range.start.row))
4880 })
4881 else {
4882 return Task::ready(None);
4883 };
4884
4885 let (_, code_actions) = self
4886 .available_code_actions
4887 .clone()
4888 .and_then(|(location, code_actions)| {
4889 let snapshot = location.buffer.read(cx).snapshot();
4890 let point_range = location.range.to_point(&snapshot);
4891 let point_range = point_range.start.row..=point_range.end.row;
4892 if point_range.contains(&buffer_row) {
4893 Some((location, code_actions))
4894 } else {
4895 None
4896 }
4897 })
4898 .unzip();
4899
4900 let buffer_id = buffer.read(cx).remote_id();
4901 let tasks = self
4902 .tasks
4903 .get(&(buffer_id, buffer_row))
4904 .map(|t| Arc::new(t.to_owned()));
4905
4906 if tasks.is_none() && code_actions.is_none() {
4907 return Task::ready(None);
4908 }
4909
4910 self.completion_tasks.clear();
4911 self.discard_inline_completion(false, cx);
4912
4913 let task_context = tasks
4914 .as_ref()
4915 .zip(self.project.clone())
4916 .map(|(tasks, project)| {
4917 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
4918 });
4919
4920 cx.spawn_in(window, async move |_, _| {
4921 let task_context = match task_context {
4922 Some(task_context) => task_context.await,
4923 None => None,
4924 };
4925 let resolved_tasks = tasks.zip(task_context).map(|(tasks, task_context)| {
4926 Rc::new(ResolvedTasks {
4927 templates: tasks.resolve(&task_context).collect(),
4928 position: snapshot
4929 .buffer_snapshot
4930 .anchor_before(Point::new(multibuffer_point.row, tasks.column)),
4931 })
4932 });
4933 Some((
4934 buffer,
4935 CodeActionContents {
4936 actions: code_actions,
4937 tasks: resolved_tasks,
4938 },
4939 ))
4940 })
4941 }
4942
4943 pub fn toggle_code_actions(
4944 &mut self,
4945 action: &ToggleCodeActions,
4946 window: &mut Window,
4947 cx: &mut Context<Self>,
4948 ) {
4949 let mut context_menu = self.context_menu.borrow_mut();
4950 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
4951 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
4952 // Toggle if we're selecting the same one
4953 *context_menu = None;
4954 cx.notify();
4955 return;
4956 } else {
4957 // Otherwise, clear it and start a new one
4958 *context_menu = None;
4959 cx.notify();
4960 }
4961 }
4962 drop(context_menu);
4963
4964 let deployed_from_indicator = action.deployed_from_indicator;
4965 let mut task = self.code_actions_task.take();
4966 let action = action.clone();
4967
4968 cx.spawn_in(window, async move |editor, cx| {
4969 while let Some(prev_task) = task {
4970 prev_task.await.log_err();
4971 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
4972 }
4973
4974 let context_menu_task = editor.update_in(cx, |editor, window, cx| {
4975 if !editor.focus_handle.is_focused(window) {
4976 return Some(Task::ready(Ok(())));
4977 }
4978 let debugger_flag = cx.has_flag::<Debugger>();
4979 let code_actions_task = editor.prepare_code_actions_task(&action, window, cx);
4980 Some(cx.spawn_in(window, async move |editor, cx| {
4981 if let Some((buffer, code_action_contents)) = code_actions_task.await {
4982 let spawn_straight_away =
4983 code_action_contents.tasks.as_ref().map_or(false, |tasks| {
4984 tasks
4985 .templates
4986 .iter()
4987 .filter(|task| {
4988 if matches!(task.1.task_type(), task::TaskType::Debug(_)) {
4989 debugger_flag
4990 } else {
4991 true
4992 }
4993 })
4994 .count()
4995 == 1
4996 }) && code_action_contents
4997 .actions
4998 .as_ref()
4999 .map_or(true, |actions| actions.is_empty());
5000 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5001 *editor.context_menu.borrow_mut() =
5002 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5003 buffer,
5004 actions: code_action_contents,
5005 selected_item: Default::default(),
5006 scroll_handle: UniformListScrollHandle::default(),
5007 deployed_from_indicator,
5008 }));
5009 if spawn_straight_away {
5010 if let Some(task) = editor.confirm_code_action(
5011 &ConfirmCodeAction {
5012 item_ix: Some(0),
5013 from_mouse_context_menu: false,
5014 },
5015 window,
5016 cx,
5017 ) {
5018 cx.notify();
5019 return task;
5020 }
5021 }
5022 cx.notify();
5023 Task::ready(Ok(()))
5024 }) {
5025 task.await
5026 } else {
5027 Ok(())
5028 }
5029 } else {
5030 Ok(())
5031 }
5032 }))
5033 })?;
5034 if let Some(task) = context_menu_task {
5035 task.await?;
5036 }
5037
5038 Ok::<_, anyhow::Error>(())
5039 })
5040 .detach_and_log_err(cx);
5041 }
5042
5043 pub fn confirm_code_action(
5044 &mut self,
5045 action: &ConfirmCodeAction,
5046 window: &mut Window,
5047 cx: &mut Context<Self>,
5048 ) -> Option<Task<Result<()>>> {
5049 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5050
5051 let (action, buffer) = if action.from_mouse_context_menu {
5052 if let Some(menu) = self.mouse_context_menu.take() {
5053 let code_action = menu.code_action?;
5054 let index = action.item_ix?;
5055 let action = code_action.actions.get(index)?;
5056 (action, code_action.buffer)
5057 } else {
5058 return None;
5059 }
5060 } else {
5061 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5062 let action_ix = action.item_ix.unwrap_or(menu.selected_item);
5063 let action = menu.actions.get(action_ix)?;
5064 let buffer = menu.buffer;
5065 (action, buffer)
5066 } else {
5067 return None;
5068 }
5069 };
5070
5071 let title = action.label();
5072 let workspace = self.workspace()?;
5073
5074 match action {
5075 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5076 match resolved_task.task_type() {
5077 task::TaskType::Script => workspace.update(cx, |workspace, cx| {
5078 workspace::tasks::schedule_resolved_task(
5079 workspace,
5080 task_source_kind,
5081 resolved_task,
5082 false,
5083 cx,
5084 );
5085
5086 Some(Task::ready(Ok(())))
5087 }),
5088 task::TaskType::Debug(debug_args) => {
5089 if debug_args.locator.is_some() {
5090 workspace.update(cx, |workspace, cx| {
5091 workspace::tasks::schedule_resolved_task(
5092 workspace,
5093 task_source_kind,
5094 resolved_task,
5095 false,
5096 cx,
5097 );
5098 });
5099
5100 return Some(Task::ready(Ok(())));
5101 }
5102
5103 if let Some(project) = self.project.as_ref() {
5104 project
5105 .update(cx, |project, cx| {
5106 project.start_debug_session(
5107 resolved_task.resolved_debug_adapter_config().unwrap(),
5108 cx,
5109 )
5110 })
5111 .detach_and_log_err(cx);
5112 Some(Task::ready(Ok(())))
5113 } else {
5114 Some(Task::ready(Ok(())))
5115 }
5116 }
5117 }
5118 }
5119 CodeActionsItem::CodeAction {
5120 excerpt_id,
5121 action,
5122 provider,
5123 } => {
5124 let apply_code_action =
5125 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5126 let workspace = workspace.downgrade();
5127 Some(cx.spawn_in(window, async move |editor, cx| {
5128 let project_transaction = apply_code_action.await?;
5129 Self::open_project_transaction(
5130 &editor,
5131 workspace,
5132 project_transaction,
5133 title,
5134 cx,
5135 )
5136 .await
5137 }))
5138 }
5139 }
5140 }
5141
5142 pub async fn open_project_transaction(
5143 this: &WeakEntity<Editor>,
5144 workspace: WeakEntity<Workspace>,
5145 transaction: ProjectTransaction,
5146 title: String,
5147 cx: &mut AsyncWindowContext,
5148 ) -> Result<()> {
5149 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5150 cx.update(|_, cx| {
5151 entries.sort_unstable_by_key(|(buffer, _)| {
5152 buffer.read(cx).file().map(|f| f.path().clone())
5153 });
5154 })?;
5155
5156 // If the project transaction's edits are all contained within this editor, then
5157 // avoid opening a new editor to display them.
5158
5159 if let Some((buffer, transaction)) = entries.first() {
5160 if entries.len() == 1 {
5161 let excerpt = this.update(cx, |editor, cx| {
5162 editor
5163 .buffer()
5164 .read(cx)
5165 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5166 })?;
5167 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5168 if excerpted_buffer == *buffer {
5169 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5170 let excerpt_range = excerpt_range.to_offset(buffer);
5171 buffer
5172 .edited_ranges_for_transaction::<usize>(transaction)
5173 .all(|range| {
5174 excerpt_range.start <= range.start
5175 && excerpt_range.end >= range.end
5176 })
5177 })?;
5178
5179 if all_edits_within_excerpt {
5180 return Ok(());
5181 }
5182 }
5183 }
5184 }
5185 } else {
5186 return Ok(());
5187 }
5188
5189 let mut ranges_to_highlight = Vec::new();
5190 let excerpt_buffer = cx.new(|cx| {
5191 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5192 for (buffer_handle, transaction) in &entries {
5193 let edited_ranges = buffer_handle
5194 .read(cx)
5195 .edited_ranges_for_transaction::<Point>(transaction)
5196 .collect::<Vec<_>>();
5197 let (ranges, _) = multibuffer.set_excerpts_for_path(
5198 PathKey::for_buffer(buffer_handle, cx),
5199 buffer_handle.clone(),
5200 edited_ranges,
5201 DEFAULT_MULTIBUFFER_CONTEXT,
5202 cx,
5203 );
5204
5205 ranges_to_highlight.extend(ranges);
5206 }
5207 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5208 multibuffer
5209 })?;
5210
5211 workspace.update_in(cx, |workspace, window, cx| {
5212 let project = workspace.project().clone();
5213 let editor =
5214 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5215 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5216 editor.update(cx, |editor, cx| {
5217 editor.highlight_background::<Self>(
5218 &ranges_to_highlight,
5219 |theme| theme.editor_highlighted_line_background,
5220 cx,
5221 );
5222 });
5223 })?;
5224
5225 Ok(())
5226 }
5227
5228 pub fn clear_code_action_providers(&mut self) {
5229 self.code_action_providers.clear();
5230 self.available_code_actions.take();
5231 }
5232
5233 pub fn add_code_action_provider(
5234 &mut self,
5235 provider: Rc<dyn CodeActionProvider>,
5236 window: &mut Window,
5237 cx: &mut Context<Self>,
5238 ) {
5239 if self
5240 .code_action_providers
5241 .iter()
5242 .any(|existing_provider| existing_provider.id() == provider.id())
5243 {
5244 return;
5245 }
5246
5247 self.code_action_providers.push(provider);
5248 self.refresh_code_actions(window, cx);
5249 }
5250
5251 pub fn remove_code_action_provider(
5252 &mut self,
5253 id: Arc<str>,
5254 window: &mut Window,
5255 cx: &mut Context<Self>,
5256 ) {
5257 self.code_action_providers
5258 .retain(|provider| provider.id() != id);
5259 self.refresh_code_actions(window, cx);
5260 }
5261
5262 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5263 let newest_selection = self.selections.newest_anchor().clone();
5264 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5265 let buffer = self.buffer.read(cx);
5266 if newest_selection.head().diff_base_anchor.is_some() {
5267 return None;
5268 }
5269 let (start_buffer, start) =
5270 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5271 let (end_buffer, end) =
5272 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5273 if start_buffer != end_buffer {
5274 return None;
5275 }
5276
5277 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5278 cx.background_executor()
5279 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5280 .await;
5281
5282 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5283 let providers = this.code_action_providers.clone();
5284 let tasks = this
5285 .code_action_providers
5286 .iter()
5287 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5288 .collect::<Vec<_>>();
5289 (providers, tasks)
5290 })?;
5291
5292 let mut actions = Vec::new();
5293 for (provider, provider_actions) in
5294 providers.into_iter().zip(future::join_all(tasks).await)
5295 {
5296 if let Some(provider_actions) = provider_actions.log_err() {
5297 actions.extend(provider_actions.into_iter().map(|action| {
5298 AvailableCodeAction {
5299 excerpt_id: newest_selection.start.excerpt_id,
5300 action,
5301 provider: provider.clone(),
5302 }
5303 }));
5304 }
5305 }
5306
5307 this.update(cx, |this, cx| {
5308 this.available_code_actions = if actions.is_empty() {
5309 None
5310 } else {
5311 Some((
5312 Location {
5313 buffer: start_buffer,
5314 range: start..end,
5315 },
5316 actions.into(),
5317 ))
5318 };
5319 cx.notify();
5320 })
5321 }));
5322 None
5323 }
5324
5325 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5326 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5327 self.show_git_blame_inline = false;
5328
5329 self.show_git_blame_inline_delay_task =
5330 Some(cx.spawn_in(window, async move |this, cx| {
5331 cx.background_executor().timer(delay).await;
5332
5333 this.update(cx, |this, cx| {
5334 this.show_git_blame_inline = true;
5335 cx.notify();
5336 })
5337 .log_err();
5338 }));
5339 }
5340 }
5341
5342 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5343 if self.pending_rename.is_some() {
5344 return None;
5345 }
5346
5347 let provider = self.semantics_provider.clone()?;
5348 let buffer = self.buffer.read(cx);
5349 let newest_selection = self.selections.newest_anchor().clone();
5350 let cursor_position = newest_selection.head();
5351 let (cursor_buffer, cursor_buffer_position) =
5352 buffer.text_anchor_for_position(cursor_position, cx)?;
5353 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5354 if cursor_buffer != tail_buffer {
5355 return None;
5356 }
5357 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5358 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5359 cx.background_executor()
5360 .timer(Duration::from_millis(debounce))
5361 .await;
5362
5363 let highlights = if let Some(highlights) = cx
5364 .update(|cx| {
5365 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5366 })
5367 .ok()
5368 .flatten()
5369 {
5370 highlights.await.log_err()
5371 } else {
5372 None
5373 };
5374
5375 if let Some(highlights) = highlights {
5376 this.update(cx, |this, cx| {
5377 if this.pending_rename.is_some() {
5378 return;
5379 }
5380
5381 let buffer_id = cursor_position.buffer_id;
5382 let buffer = this.buffer.read(cx);
5383 if !buffer
5384 .text_anchor_for_position(cursor_position, cx)
5385 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5386 {
5387 return;
5388 }
5389
5390 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5391 let mut write_ranges = Vec::new();
5392 let mut read_ranges = Vec::new();
5393 for highlight in highlights {
5394 for (excerpt_id, excerpt_range) in
5395 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5396 {
5397 let start = highlight
5398 .range
5399 .start
5400 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5401 let end = highlight
5402 .range
5403 .end
5404 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5405 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5406 continue;
5407 }
5408
5409 let range = Anchor {
5410 buffer_id,
5411 excerpt_id,
5412 text_anchor: start,
5413 diff_base_anchor: None,
5414 }..Anchor {
5415 buffer_id,
5416 excerpt_id,
5417 text_anchor: end,
5418 diff_base_anchor: None,
5419 };
5420 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5421 write_ranges.push(range);
5422 } else {
5423 read_ranges.push(range);
5424 }
5425 }
5426 }
5427
5428 this.highlight_background::<DocumentHighlightRead>(
5429 &read_ranges,
5430 |theme| theme.editor_document_highlight_read_background,
5431 cx,
5432 );
5433 this.highlight_background::<DocumentHighlightWrite>(
5434 &write_ranges,
5435 |theme| theme.editor_document_highlight_write_background,
5436 cx,
5437 );
5438 cx.notify();
5439 })
5440 .log_err();
5441 }
5442 }));
5443 None
5444 }
5445
5446 pub fn refresh_selected_text_highlights(
5447 &mut self,
5448 window: &mut Window,
5449 cx: &mut Context<Editor>,
5450 ) {
5451 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5452 return;
5453 }
5454 self.selection_highlight_task.take();
5455 if !EditorSettings::get_global(cx).selection_highlight {
5456 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5457 return;
5458 }
5459 if self.selections.count() != 1 || self.selections.line_mode {
5460 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5461 return;
5462 }
5463 let selection = self.selections.newest::<Point>(cx);
5464 if selection.is_empty() || selection.start.row != selection.end.row {
5465 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5466 return;
5467 }
5468 let debounce = EditorSettings::get_global(cx).selection_highlight_debounce;
5469 self.selection_highlight_task = Some(cx.spawn_in(window, async move |editor, cx| {
5470 cx.background_executor()
5471 .timer(Duration::from_millis(debounce))
5472 .await;
5473 let Some(Some(matches_task)) = editor
5474 .update_in(cx, |editor, _, cx| {
5475 if editor.selections.count() != 1 || editor.selections.line_mode {
5476 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5477 return None;
5478 }
5479 let selection = editor.selections.newest::<Point>(cx);
5480 if selection.is_empty() || selection.start.row != selection.end.row {
5481 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5482 return None;
5483 }
5484 let buffer = editor.buffer().read(cx).snapshot(cx);
5485 let query = buffer.text_for_range(selection.range()).collect::<String>();
5486 if query.trim().is_empty() {
5487 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5488 return None;
5489 }
5490 Some(cx.background_spawn(async move {
5491 let mut ranges = Vec::new();
5492 let selection_anchors = selection.range().to_anchors(&buffer);
5493 for range in [buffer.anchor_before(0)..buffer.anchor_after(buffer.len())] {
5494 for (search_buffer, search_range, excerpt_id) in
5495 buffer.range_to_buffer_ranges(range)
5496 {
5497 ranges.extend(
5498 project::search::SearchQuery::text(
5499 query.clone(),
5500 false,
5501 false,
5502 false,
5503 Default::default(),
5504 Default::default(),
5505 None,
5506 )
5507 .unwrap()
5508 .search(search_buffer, Some(search_range.clone()))
5509 .await
5510 .into_iter()
5511 .filter_map(
5512 |match_range| {
5513 let start = search_buffer.anchor_after(
5514 search_range.start + match_range.start,
5515 );
5516 let end = search_buffer.anchor_before(
5517 search_range.start + match_range.end,
5518 );
5519 let range = Anchor::range_in_buffer(
5520 excerpt_id,
5521 search_buffer.remote_id(),
5522 start..end,
5523 );
5524 (range != selection_anchors).then_some(range)
5525 },
5526 ),
5527 );
5528 }
5529 }
5530 ranges
5531 }))
5532 })
5533 .log_err()
5534 else {
5535 return;
5536 };
5537 let matches = matches_task.await;
5538 editor
5539 .update_in(cx, |editor, _, cx| {
5540 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5541 if !matches.is_empty() {
5542 editor.highlight_background::<SelectedTextHighlight>(
5543 &matches,
5544 |theme| theme.editor_document_highlight_bracket_background,
5545 cx,
5546 )
5547 }
5548 })
5549 .log_err();
5550 }));
5551 }
5552
5553 pub fn refresh_inline_completion(
5554 &mut self,
5555 debounce: bool,
5556 user_requested: bool,
5557 window: &mut Window,
5558 cx: &mut Context<Self>,
5559 ) -> Option<()> {
5560 let provider = self.edit_prediction_provider()?;
5561 let cursor = self.selections.newest_anchor().head();
5562 let (buffer, cursor_buffer_position) =
5563 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5564
5565 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5566 self.discard_inline_completion(false, cx);
5567 return None;
5568 }
5569
5570 if !user_requested
5571 && (!self.should_show_edit_predictions()
5572 || !self.is_focused(window)
5573 || buffer.read(cx).is_empty())
5574 {
5575 self.discard_inline_completion(false, cx);
5576 return None;
5577 }
5578
5579 self.update_visible_inline_completion(window, cx);
5580 provider.refresh(
5581 self.project.clone(),
5582 buffer,
5583 cursor_buffer_position,
5584 debounce,
5585 cx,
5586 );
5587 Some(())
5588 }
5589
5590 fn show_edit_predictions_in_menu(&self) -> bool {
5591 match self.edit_prediction_settings {
5592 EditPredictionSettings::Disabled => false,
5593 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5594 }
5595 }
5596
5597 pub fn edit_predictions_enabled(&self) -> bool {
5598 match self.edit_prediction_settings {
5599 EditPredictionSettings::Disabled => false,
5600 EditPredictionSettings::Enabled { .. } => true,
5601 }
5602 }
5603
5604 fn edit_prediction_requires_modifier(&self) -> bool {
5605 match self.edit_prediction_settings {
5606 EditPredictionSettings::Disabled => false,
5607 EditPredictionSettings::Enabled {
5608 preview_requires_modifier,
5609 ..
5610 } => preview_requires_modifier,
5611 }
5612 }
5613
5614 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5615 if self.edit_prediction_provider.is_none() {
5616 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5617 } else {
5618 let selection = self.selections.newest_anchor();
5619 let cursor = selection.head();
5620
5621 if let Some((buffer, cursor_buffer_position)) =
5622 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5623 {
5624 self.edit_prediction_settings =
5625 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5626 }
5627 }
5628 }
5629
5630 fn edit_prediction_settings_at_position(
5631 &self,
5632 buffer: &Entity<Buffer>,
5633 buffer_position: language::Anchor,
5634 cx: &App,
5635 ) -> EditPredictionSettings {
5636 if !self.mode.is_full()
5637 || !self.show_inline_completions_override.unwrap_or(true)
5638 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5639 {
5640 return EditPredictionSettings::Disabled;
5641 }
5642
5643 let buffer = buffer.read(cx);
5644
5645 let file = buffer.file();
5646
5647 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5648 return EditPredictionSettings::Disabled;
5649 };
5650
5651 let by_provider = matches!(
5652 self.menu_inline_completions_policy,
5653 MenuInlineCompletionsPolicy::ByProvider
5654 );
5655
5656 let show_in_menu = by_provider
5657 && self
5658 .edit_prediction_provider
5659 .as_ref()
5660 .map_or(false, |provider| {
5661 provider.provider.show_completions_in_menu()
5662 });
5663
5664 let preview_requires_modifier =
5665 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
5666
5667 EditPredictionSettings::Enabled {
5668 show_in_menu,
5669 preview_requires_modifier,
5670 }
5671 }
5672
5673 fn should_show_edit_predictions(&self) -> bool {
5674 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
5675 }
5676
5677 pub fn edit_prediction_preview_is_active(&self) -> bool {
5678 matches!(
5679 self.edit_prediction_preview,
5680 EditPredictionPreview::Active { .. }
5681 )
5682 }
5683
5684 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
5685 let cursor = self.selections.newest_anchor().head();
5686 if let Some((buffer, cursor_position)) =
5687 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5688 {
5689 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
5690 } else {
5691 false
5692 }
5693 }
5694
5695 fn edit_predictions_enabled_in_buffer(
5696 &self,
5697 buffer: &Entity<Buffer>,
5698 buffer_position: language::Anchor,
5699 cx: &App,
5700 ) -> bool {
5701 maybe!({
5702 if self.read_only(cx) {
5703 return Some(false);
5704 }
5705 let provider = self.edit_prediction_provider()?;
5706 if !provider.is_enabled(&buffer, buffer_position, cx) {
5707 return Some(false);
5708 }
5709 let buffer = buffer.read(cx);
5710 let Some(file) = buffer.file() else {
5711 return Some(true);
5712 };
5713 let settings = all_language_settings(Some(file), cx);
5714 Some(settings.edit_predictions_enabled_for_file(file, cx))
5715 })
5716 .unwrap_or(false)
5717 }
5718
5719 fn cycle_inline_completion(
5720 &mut self,
5721 direction: Direction,
5722 window: &mut Window,
5723 cx: &mut Context<Self>,
5724 ) -> Option<()> {
5725 let provider = self.edit_prediction_provider()?;
5726 let cursor = self.selections.newest_anchor().head();
5727 let (buffer, cursor_buffer_position) =
5728 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5729 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
5730 return None;
5731 }
5732
5733 provider.cycle(buffer, cursor_buffer_position, direction, cx);
5734 self.update_visible_inline_completion(window, cx);
5735
5736 Some(())
5737 }
5738
5739 pub fn show_inline_completion(
5740 &mut self,
5741 _: &ShowEditPrediction,
5742 window: &mut Window,
5743 cx: &mut Context<Self>,
5744 ) {
5745 if !self.has_active_inline_completion() {
5746 self.refresh_inline_completion(false, true, window, cx);
5747 return;
5748 }
5749
5750 self.update_visible_inline_completion(window, cx);
5751 }
5752
5753 pub fn display_cursor_names(
5754 &mut self,
5755 _: &DisplayCursorNames,
5756 window: &mut Window,
5757 cx: &mut Context<Self>,
5758 ) {
5759 self.show_cursor_names(window, cx);
5760 }
5761
5762 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5763 self.show_cursor_names = true;
5764 cx.notify();
5765 cx.spawn_in(window, async move |this, cx| {
5766 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
5767 this.update(cx, |this, cx| {
5768 this.show_cursor_names = false;
5769 cx.notify()
5770 })
5771 .ok()
5772 })
5773 .detach();
5774 }
5775
5776 pub fn next_edit_prediction(
5777 &mut self,
5778 _: &NextEditPrediction,
5779 window: &mut Window,
5780 cx: &mut Context<Self>,
5781 ) {
5782 if self.has_active_inline_completion() {
5783 self.cycle_inline_completion(Direction::Next, window, cx);
5784 } else {
5785 let is_copilot_disabled = self
5786 .refresh_inline_completion(false, true, window, cx)
5787 .is_none();
5788 if is_copilot_disabled {
5789 cx.propagate();
5790 }
5791 }
5792 }
5793
5794 pub fn previous_edit_prediction(
5795 &mut self,
5796 _: &PreviousEditPrediction,
5797 window: &mut Window,
5798 cx: &mut Context<Self>,
5799 ) {
5800 if self.has_active_inline_completion() {
5801 self.cycle_inline_completion(Direction::Prev, window, cx);
5802 } else {
5803 let is_copilot_disabled = self
5804 .refresh_inline_completion(false, true, window, cx)
5805 .is_none();
5806 if is_copilot_disabled {
5807 cx.propagate();
5808 }
5809 }
5810 }
5811
5812 pub fn accept_edit_prediction(
5813 &mut self,
5814 _: &AcceptEditPrediction,
5815 window: &mut Window,
5816 cx: &mut Context<Self>,
5817 ) {
5818 if self.show_edit_predictions_in_menu() {
5819 self.hide_context_menu(window, cx);
5820 }
5821
5822 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5823 return;
5824 };
5825
5826 self.report_inline_completion_event(
5827 active_inline_completion.completion_id.clone(),
5828 true,
5829 cx,
5830 );
5831
5832 match &active_inline_completion.completion {
5833 InlineCompletion::Move { target, .. } => {
5834 let target = *target;
5835
5836 if let Some(position_map) = &self.last_position_map {
5837 if position_map
5838 .visible_row_range
5839 .contains(&target.to_display_point(&position_map.snapshot).row())
5840 || !self.edit_prediction_requires_modifier()
5841 {
5842 self.unfold_ranges(&[target..target], true, false, cx);
5843 // Note that this is also done in vim's handler of the Tab action.
5844 self.change_selections(
5845 Some(Autoscroll::newest()),
5846 window,
5847 cx,
5848 |selections| {
5849 selections.select_anchor_ranges([target..target]);
5850 },
5851 );
5852 self.clear_row_highlights::<EditPredictionPreview>();
5853
5854 self.edit_prediction_preview
5855 .set_previous_scroll_position(None);
5856 } else {
5857 self.edit_prediction_preview
5858 .set_previous_scroll_position(Some(
5859 position_map.snapshot.scroll_anchor,
5860 ));
5861
5862 self.highlight_rows::<EditPredictionPreview>(
5863 target..target,
5864 cx.theme().colors().editor_highlighted_line_background,
5865 true,
5866 cx,
5867 );
5868 self.request_autoscroll(Autoscroll::fit(), cx);
5869 }
5870 }
5871 }
5872 InlineCompletion::Edit { edits, .. } => {
5873 if let Some(provider) = self.edit_prediction_provider() {
5874 provider.accept(cx);
5875 }
5876
5877 let snapshot = self.buffer.read(cx).snapshot(cx);
5878 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
5879
5880 self.buffer.update(cx, |buffer, cx| {
5881 buffer.edit(edits.iter().cloned(), None, cx)
5882 });
5883
5884 self.change_selections(None, window, cx, |s| {
5885 s.select_anchor_ranges([last_edit_end..last_edit_end])
5886 });
5887
5888 self.update_visible_inline_completion(window, cx);
5889 if self.active_inline_completion.is_none() {
5890 self.refresh_inline_completion(true, true, window, cx);
5891 }
5892
5893 cx.notify();
5894 }
5895 }
5896
5897 self.edit_prediction_requires_modifier_in_indent_conflict = false;
5898 }
5899
5900 pub fn accept_partial_inline_completion(
5901 &mut self,
5902 _: &AcceptPartialEditPrediction,
5903 window: &mut Window,
5904 cx: &mut Context<Self>,
5905 ) {
5906 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
5907 return;
5908 };
5909 if self.selections.count() != 1 {
5910 return;
5911 }
5912
5913 self.report_inline_completion_event(
5914 active_inline_completion.completion_id.clone(),
5915 true,
5916 cx,
5917 );
5918
5919 match &active_inline_completion.completion {
5920 InlineCompletion::Move { target, .. } => {
5921 let target = *target;
5922 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
5923 selections.select_anchor_ranges([target..target]);
5924 });
5925 }
5926 InlineCompletion::Edit { edits, .. } => {
5927 // Find an insertion that starts at the cursor position.
5928 let snapshot = self.buffer.read(cx).snapshot(cx);
5929 let cursor_offset = self.selections.newest::<usize>(cx).head();
5930 let insertion = edits.iter().find_map(|(range, text)| {
5931 let range = range.to_offset(&snapshot);
5932 if range.is_empty() && range.start == cursor_offset {
5933 Some(text)
5934 } else {
5935 None
5936 }
5937 });
5938
5939 if let Some(text) = insertion {
5940 let mut partial_completion = text
5941 .chars()
5942 .by_ref()
5943 .take_while(|c| c.is_alphabetic())
5944 .collect::<String>();
5945 if partial_completion.is_empty() {
5946 partial_completion = text
5947 .chars()
5948 .by_ref()
5949 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
5950 .collect::<String>();
5951 }
5952
5953 cx.emit(EditorEvent::InputHandled {
5954 utf16_range_to_replace: None,
5955 text: partial_completion.clone().into(),
5956 });
5957
5958 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
5959
5960 self.refresh_inline_completion(true, true, window, cx);
5961 cx.notify();
5962 } else {
5963 self.accept_edit_prediction(&Default::default(), window, cx);
5964 }
5965 }
5966 }
5967 }
5968
5969 fn discard_inline_completion(
5970 &mut self,
5971 should_report_inline_completion_event: bool,
5972 cx: &mut Context<Self>,
5973 ) -> bool {
5974 if should_report_inline_completion_event {
5975 let completion_id = self
5976 .active_inline_completion
5977 .as_ref()
5978 .and_then(|active_completion| active_completion.completion_id.clone());
5979
5980 self.report_inline_completion_event(completion_id, false, cx);
5981 }
5982
5983 if let Some(provider) = self.edit_prediction_provider() {
5984 provider.discard(cx);
5985 }
5986
5987 self.take_active_inline_completion(cx)
5988 }
5989
5990 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
5991 let Some(provider) = self.edit_prediction_provider() else {
5992 return;
5993 };
5994
5995 let Some((_, buffer, _)) = self
5996 .buffer
5997 .read(cx)
5998 .excerpt_containing(self.selections.newest_anchor().head(), cx)
5999 else {
6000 return;
6001 };
6002
6003 let extension = buffer
6004 .read(cx)
6005 .file()
6006 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6007
6008 let event_type = match accepted {
6009 true => "Edit Prediction Accepted",
6010 false => "Edit Prediction Discarded",
6011 };
6012 telemetry::event!(
6013 event_type,
6014 provider = provider.name(),
6015 prediction_id = id,
6016 suggestion_accepted = accepted,
6017 file_extension = extension,
6018 );
6019 }
6020
6021 pub fn has_active_inline_completion(&self) -> bool {
6022 self.active_inline_completion.is_some()
6023 }
6024
6025 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6026 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6027 return false;
6028 };
6029
6030 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6031 self.clear_highlights::<InlineCompletionHighlight>(cx);
6032 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6033 true
6034 }
6035
6036 /// Returns true when we're displaying the edit prediction popover below the cursor
6037 /// like we are not previewing and the LSP autocomplete menu is visible
6038 /// or we are in `when_holding_modifier` mode.
6039 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6040 if self.edit_prediction_preview_is_active()
6041 || !self.show_edit_predictions_in_menu()
6042 || !self.edit_predictions_enabled()
6043 {
6044 return false;
6045 }
6046
6047 if self.has_visible_completions_menu() {
6048 return true;
6049 }
6050
6051 has_completion && self.edit_prediction_requires_modifier()
6052 }
6053
6054 fn handle_modifiers_changed(
6055 &mut self,
6056 modifiers: Modifiers,
6057 position_map: &PositionMap,
6058 window: &mut Window,
6059 cx: &mut Context<Self>,
6060 ) {
6061 if self.show_edit_predictions_in_menu() {
6062 self.update_edit_prediction_preview(&modifiers, window, cx);
6063 }
6064
6065 self.update_selection_mode(&modifiers, position_map, window, cx);
6066
6067 let mouse_position = window.mouse_position();
6068 if !position_map.text_hitbox.is_hovered(window) {
6069 return;
6070 }
6071
6072 self.update_hovered_link(
6073 position_map.point_for_position(mouse_position),
6074 &position_map.snapshot,
6075 modifiers,
6076 window,
6077 cx,
6078 )
6079 }
6080
6081 fn update_selection_mode(
6082 &mut self,
6083 modifiers: &Modifiers,
6084 position_map: &PositionMap,
6085 window: &mut Window,
6086 cx: &mut Context<Self>,
6087 ) {
6088 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6089 return;
6090 }
6091
6092 let mouse_position = window.mouse_position();
6093 let point_for_position = position_map.point_for_position(mouse_position);
6094 let position = point_for_position.previous_valid;
6095
6096 self.select(
6097 SelectPhase::BeginColumnar {
6098 position,
6099 reset: false,
6100 goal_column: point_for_position.exact_unclipped.column(),
6101 },
6102 window,
6103 cx,
6104 );
6105 }
6106
6107 fn update_edit_prediction_preview(
6108 &mut self,
6109 modifiers: &Modifiers,
6110 window: &mut Window,
6111 cx: &mut Context<Self>,
6112 ) {
6113 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6114 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6115 return;
6116 };
6117
6118 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6119 if matches!(
6120 self.edit_prediction_preview,
6121 EditPredictionPreview::Inactive { .. }
6122 ) {
6123 self.edit_prediction_preview = EditPredictionPreview::Active {
6124 previous_scroll_position: None,
6125 since: Instant::now(),
6126 };
6127
6128 self.update_visible_inline_completion(window, cx);
6129 cx.notify();
6130 }
6131 } else if let EditPredictionPreview::Active {
6132 previous_scroll_position,
6133 since,
6134 } = self.edit_prediction_preview
6135 {
6136 if let (Some(previous_scroll_position), Some(position_map)) =
6137 (previous_scroll_position, self.last_position_map.as_ref())
6138 {
6139 self.set_scroll_position(
6140 previous_scroll_position
6141 .scroll_position(&position_map.snapshot.display_snapshot),
6142 window,
6143 cx,
6144 );
6145 }
6146
6147 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6148 released_too_fast: since.elapsed() < Duration::from_millis(200),
6149 };
6150 self.clear_row_highlights::<EditPredictionPreview>();
6151 self.update_visible_inline_completion(window, cx);
6152 cx.notify();
6153 }
6154 }
6155
6156 fn update_visible_inline_completion(
6157 &mut self,
6158 _window: &mut Window,
6159 cx: &mut Context<Self>,
6160 ) -> Option<()> {
6161 let selection = self.selections.newest_anchor();
6162 let cursor = selection.head();
6163 let multibuffer = self.buffer.read(cx).snapshot(cx);
6164 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6165 let excerpt_id = cursor.excerpt_id;
6166
6167 let show_in_menu = self.show_edit_predictions_in_menu();
6168 let completions_menu_has_precedence = !show_in_menu
6169 && (self.context_menu.borrow().is_some()
6170 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6171
6172 if completions_menu_has_precedence
6173 || !offset_selection.is_empty()
6174 || self
6175 .active_inline_completion
6176 .as_ref()
6177 .map_or(false, |completion| {
6178 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6179 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6180 !invalidation_range.contains(&offset_selection.head())
6181 })
6182 {
6183 self.discard_inline_completion(false, cx);
6184 return None;
6185 }
6186
6187 self.take_active_inline_completion(cx);
6188 let Some(provider) = self.edit_prediction_provider() else {
6189 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6190 return None;
6191 };
6192
6193 let (buffer, cursor_buffer_position) =
6194 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6195
6196 self.edit_prediction_settings =
6197 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6198
6199 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6200
6201 if self.edit_prediction_indent_conflict {
6202 let cursor_point = cursor.to_point(&multibuffer);
6203
6204 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6205
6206 if let Some((_, indent)) = indents.iter().next() {
6207 if indent.len == cursor_point.column {
6208 self.edit_prediction_indent_conflict = false;
6209 }
6210 }
6211 }
6212
6213 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6214 let edits = inline_completion
6215 .edits
6216 .into_iter()
6217 .flat_map(|(range, new_text)| {
6218 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6219 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6220 Some((start..end, new_text))
6221 })
6222 .collect::<Vec<_>>();
6223 if edits.is_empty() {
6224 return None;
6225 }
6226
6227 let first_edit_start = edits.first().unwrap().0.start;
6228 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6229 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6230
6231 let last_edit_end = edits.last().unwrap().0.end;
6232 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6233 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6234
6235 let cursor_row = cursor.to_point(&multibuffer).row;
6236
6237 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6238
6239 let mut inlay_ids = Vec::new();
6240 let invalidation_row_range;
6241 let move_invalidation_row_range = if cursor_row < edit_start_row {
6242 Some(cursor_row..edit_end_row)
6243 } else if cursor_row > edit_end_row {
6244 Some(edit_start_row..cursor_row)
6245 } else {
6246 None
6247 };
6248 let is_move =
6249 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6250 let completion = if is_move {
6251 invalidation_row_range =
6252 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6253 let target = first_edit_start;
6254 InlineCompletion::Move { target, snapshot }
6255 } else {
6256 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6257 && !self.inline_completions_hidden_for_vim_mode;
6258
6259 if show_completions_in_buffer {
6260 if edits
6261 .iter()
6262 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6263 {
6264 let mut inlays = Vec::new();
6265 for (range, new_text) in &edits {
6266 let inlay = Inlay::inline_completion(
6267 post_inc(&mut self.next_inlay_id),
6268 range.start,
6269 new_text.as_str(),
6270 );
6271 inlay_ids.push(inlay.id);
6272 inlays.push(inlay);
6273 }
6274
6275 self.splice_inlays(&[], inlays, cx);
6276 } else {
6277 let background_color = cx.theme().status().deleted_background;
6278 self.highlight_text::<InlineCompletionHighlight>(
6279 edits.iter().map(|(range, _)| range.clone()).collect(),
6280 HighlightStyle {
6281 background_color: Some(background_color),
6282 ..Default::default()
6283 },
6284 cx,
6285 );
6286 }
6287 }
6288
6289 invalidation_row_range = edit_start_row..edit_end_row;
6290
6291 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6292 if provider.show_tab_accept_marker() {
6293 EditDisplayMode::TabAccept
6294 } else {
6295 EditDisplayMode::Inline
6296 }
6297 } else {
6298 EditDisplayMode::DiffPopover
6299 };
6300
6301 InlineCompletion::Edit {
6302 edits,
6303 edit_preview: inline_completion.edit_preview,
6304 display_mode,
6305 snapshot,
6306 }
6307 };
6308
6309 let invalidation_range = multibuffer
6310 .anchor_before(Point::new(invalidation_row_range.start, 0))
6311 ..multibuffer.anchor_after(Point::new(
6312 invalidation_row_range.end,
6313 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6314 ));
6315
6316 self.stale_inline_completion_in_menu = None;
6317 self.active_inline_completion = Some(InlineCompletionState {
6318 inlay_ids,
6319 completion,
6320 completion_id: inline_completion.id,
6321 invalidation_range,
6322 });
6323
6324 cx.notify();
6325
6326 Some(())
6327 }
6328
6329 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6330 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6331 }
6332
6333 fn render_code_actions_indicator(
6334 &self,
6335 _style: &EditorStyle,
6336 row: DisplayRow,
6337 is_active: bool,
6338 breakpoint: Option<&(Anchor, Breakpoint)>,
6339 cx: &mut Context<Self>,
6340 ) -> Option<IconButton> {
6341 let color = Color::Muted;
6342 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6343 let show_tooltip = !self.context_menu_visible();
6344
6345 if self.available_code_actions.is_some() {
6346 Some(
6347 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6348 .shape(ui::IconButtonShape::Square)
6349 .icon_size(IconSize::XSmall)
6350 .icon_color(color)
6351 .toggle_state(is_active)
6352 .when(show_tooltip, |this| {
6353 this.tooltip({
6354 let focus_handle = self.focus_handle.clone();
6355 move |window, cx| {
6356 Tooltip::for_action_in(
6357 "Toggle Code Actions",
6358 &ToggleCodeActions {
6359 deployed_from_indicator: None,
6360 },
6361 &focus_handle,
6362 window,
6363 cx,
6364 )
6365 }
6366 })
6367 })
6368 .on_click(cx.listener(move |editor, _e, window, cx| {
6369 window.focus(&editor.focus_handle(cx));
6370 editor.toggle_code_actions(
6371 &ToggleCodeActions {
6372 deployed_from_indicator: Some(row),
6373 },
6374 window,
6375 cx,
6376 );
6377 }))
6378 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6379 editor.set_breakpoint_context_menu(
6380 row,
6381 position,
6382 event.down.position,
6383 window,
6384 cx,
6385 );
6386 })),
6387 )
6388 } else {
6389 None
6390 }
6391 }
6392
6393 fn clear_tasks(&mut self) {
6394 self.tasks.clear()
6395 }
6396
6397 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6398 if self.tasks.insert(key, value).is_some() {
6399 // This case should hopefully be rare, but just in case...
6400 log::error!(
6401 "multiple different run targets found on a single line, only the last target will be rendered"
6402 )
6403 }
6404 }
6405
6406 /// Get all display points of breakpoints that will be rendered within editor
6407 ///
6408 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6409 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6410 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6411 fn active_breakpoints(
6412 &self,
6413 range: Range<DisplayRow>,
6414 window: &mut Window,
6415 cx: &mut Context<Self>,
6416 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6417 let mut breakpoint_display_points = HashMap::default();
6418
6419 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6420 return breakpoint_display_points;
6421 };
6422
6423 let snapshot = self.snapshot(window, cx);
6424
6425 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6426 let Some(project) = self.project.as_ref() else {
6427 return breakpoint_display_points;
6428 };
6429
6430 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6431 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6432
6433 for (buffer_snapshot, range, excerpt_id) in
6434 multi_buffer_snapshot.range_to_buffer_ranges(range)
6435 {
6436 let Some(buffer) = project.read_with(cx, |this, cx| {
6437 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6438 }) else {
6439 continue;
6440 };
6441 let breakpoints = breakpoint_store.read(cx).breakpoints(
6442 &buffer,
6443 Some(
6444 buffer_snapshot.anchor_before(range.start)
6445 ..buffer_snapshot.anchor_after(range.end),
6446 ),
6447 buffer_snapshot,
6448 cx,
6449 );
6450 for (anchor, breakpoint) in breakpoints {
6451 let multi_buffer_anchor =
6452 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6453 let position = multi_buffer_anchor
6454 .to_point(&multi_buffer_snapshot)
6455 .to_display_point(&snapshot);
6456
6457 breakpoint_display_points
6458 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6459 }
6460 }
6461
6462 breakpoint_display_points
6463 }
6464
6465 fn breakpoint_context_menu(
6466 &self,
6467 anchor: Anchor,
6468 window: &mut Window,
6469 cx: &mut Context<Self>,
6470 ) -> Entity<ui::ContextMenu> {
6471 let weak_editor = cx.weak_entity();
6472 let focus_handle = self.focus_handle(cx);
6473
6474 let row = self
6475 .buffer
6476 .read(cx)
6477 .snapshot(cx)
6478 .summary_for_anchor::<Point>(&anchor)
6479 .row;
6480
6481 let breakpoint = self
6482 .breakpoint_at_row(row, window, cx)
6483 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6484
6485 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6486 "Edit Log Breakpoint"
6487 } else {
6488 "Set Log Breakpoint"
6489 };
6490
6491 let condition_breakpoint_msg = if breakpoint
6492 .as_ref()
6493 .is_some_and(|bp| bp.1.condition.is_some())
6494 {
6495 "Edit Condition Breakpoint"
6496 } else {
6497 "Set Condition Breakpoint"
6498 };
6499
6500 let hit_condition_breakpoint_msg = if breakpoint
6501 .as_ref()
6502 .is_some_and(|bp| bp.1.hit_condition.is_some())
6503 {
6504 "Edit Hit Condition Breakpoint"
6505 } else {
6506 "Set Hit Condition Breakpoint"
6507 };
6508
6509 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6510 "Unset Breakpoint"
6511 } else {
6512 "Set Breakpoint"
6513 };
6514
6515 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6516 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6517
6518 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6519 BreakpointState::Enabled => Some("Disable"),
6520 BreakpointState::Disabled => Some("Enable"),
6521 });
6522
6523 let (anchor, breakpoint) =
6524 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6525
6526 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6527 menu.on_blur_subscription(Subscription::new(|| {}))
6528 .context(focus_handle)
6529 .when(run_to_cursor, |this| {
6530 let weak_editor = weak_editor.clone();
6531 this.entry("Run to cursor", None, move |window, cx| {
6532 weak_editor
6533 .update(cx, |editor, cx| {
6534 editor.change_selections(None, window, cx, |s| {
6535 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6536 });
6537 })
6538 .ok();
6539
6540 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6541 })
6542 .separator()
6543 })
6544 .when_some(toggle_state_msg, |this, msg| {
6545 this.entry(msg, None, {
6546 let weak_editor = weak_editor.clone();
6547 let breakpoint = breakpoint.clone();
6548 move |_window, cx| {
6549 weak_editor
6550 .update(cx, |this, cx| {
6551 this.edit_breakpoint_at_anchor(
6552 anchor,
6553 breakpoint.as_ref().clone(),
6554 BreakpointEditAction::InvertState,
6555 cx,
6556 );
6557 })
6558 .log_err();
6559 }
6560 })
6561 })
6562 .entry(set_breakpoint_msg, None, {
6563 let weak_editor = weak_editor.clone();
6564 let breakpoint = breakpoint.clone();
6565 move |_window, cx| {
6566 weak_editor
6567 .update(cx, |this, cx| {
6568 this.edit_breakpoint_at_anchor(
6569 anchor,
6570 breakpoint.as_ref().clone(),
6571 BreakpointEditAction::Toggle,
6572 cx,
6573 );
6574 })
6575 .log_err();
6576 }
6577 })
6578 .entry(log_breakpoint_msg, None, {
6579 let breakpoint = breakpoint.clone();
6580 let weak_editor = weak_editor.clone();
6581 move |window, cx| {
6582 weak_editor
6583 .update(cx, |this, cx| {
6584 this.add_edit_breakpoint_block(
6585 anchor,
6586 breakpoint.as_ref(),
6587 BreakpointPromptEditAction::Log,
6588 window,
6589 cx,
6590 );
6591 })
6592 .log_err();
6593 }
6594 })
6595 .entry(condition_breakpoint_msg, None, {
6596 let breakpoint = breakpoint.clone();
6597 let weak_editor = weak_editor.clone();
6598 move |window, cx| {
6599 weak_editor
6600 .update(cx, |this, cx| {
6601 this.add_edit_breakpoint_block(
6602 anchor,
6603 breakpoint.as_ref(),
6604 BreakpointPromptEditAction::Condition,
6605 window,
6606 cx,
6607 );
6608 })
6609 .log_err();
6610 }
6611 })
6612 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6613 weak_editor
6614 .update(cx, |this, cx| {
6615 this.add_edit_breakpoint_block(
6616 anchor,
6617 breakpoint.as_ref(),
6618 BreakpointPromptEditAction::HitCondition,
6619 window,
6620 cx,
6621 );
6622 })
6623 .log_err();
6624 })
6625 })
6626 }
6627
6628 fn render_breakpoint(
6629 &self,
6630 position: Anchor,
6631 row: DisplayRow,
6632 breakpoint: &Breakpoint,
6633 cx: &mut Context<Self>,
6634 ) -> IconButton {
6635 let (color, icon) = {
6636 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6637 (false, false) => ui::IconName::DebugBreakpoint,
6638 (true, false) => ui::IconName::DebugLogBreakpoint,
6639 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6640 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6641 };
6642
6643 let color = if self
6644 .gutter_breakpoint_indicator
6645 .0
6646 .is_some_and(|(point, is_visible)| is_visible && point.row() == row)
6647 {
6648 Color::Hint
6649 } else {
6650 Color::Debugger
6651 };
6652
6653 (color, icon)
6654 };
6655
6656 let breakpoint = Arc::from(breakpoint.clone());
6657
6658 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
6659 .icon_size(IconSize::XSmall)
6660 .size(ui::ButtonSize::None)
6661 .icon_color(color)
6662 .style(ButtonStyle::Transparent)
6663 .on_click(cx.listener({
6664 let breakpoint = breakpoint.clone();
6665
6666 move |editor, event: &ClickEvent, window, cx| {
6667 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
6668 BreakpointEditAction::InvertState
6669 } else {
6670 BreakpointEditAction::Toggle
6671 };
6672
6673 window.focus(&editor.focus_handle(cx));
6674 editor.edit_breakpoint_at_anchor(
6675 position,
6676 breakpoint.as_ref().clone(),
6677 edit_action,
6678 cx,
6679 );
6680 }
6681 }))
6682 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6683 editor.set_breakpoint_context_menu(
6684 row,
6685 Some(position),
6686 event.down.position,
6687 window,
6688 cx,
6689 );
6690 }))
6691 }
6692
6693 fn build_tasks_context(
6694 project: &Entity<Project>,
6695 buffer: &Entity<Buffer>,
6696 buffer_row: u32,
6697 tasks: &Arc<RunnableTasks>,
6698 cx: &mut Context<Self>,
6699 ) -> Task<Option<task::TaskContext>> {
6700 let position = Point::new(buffer_row, tasks.column);
6701 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
6702 let location = Location {
6703 buffer: buffer.clone(),
6704 range: range_start..range_start,
6705 };
6706 // Fill in the environmental variables from the tree-sitter captures
6707 let mut captured_task_variables = TaskVariables::default();
6708 for (capture_name, value) in tasks.extra_variables.clone() {
6709 captured_task_variables.insert(
6710 task::VariableName::Custom(capture_name.into()),
6711 value.clone(),
6712 );
6713 }
6714 project.update(cx, |project, cx| {
6715 project.task_store().update(cx, |task_store, cx| {
6716 task_store.task_context_for_location(captured_task_variables, location, cx)
6717 })
6718 })
6719 }
6720
6721 pub fn spawn_nearest_task(
6722 &mut self,
6723 action: &SpawnNearestTask,
6724 window: &mut Window,
6725 cx: &mut Context<Self>,
6726 ) {
6727 let Some((workspace, _)) = self.workspace.clone() else {
6728 return;
6729 };
6730 let Some(project) = self.project.clone() else {
6731 return;
6732 };
6733
6734 // Try to find a closest, enclosing node using tree-sitter that has a
6735 // task
6736 let Some((buffer, buffer_row, tasks)) = self
6737 .find_enclosing_node_task(cx)
6738 // Or find the task that's closest in row-distance.
6739 .or_else(|| self.find_closest_task(cx))
6740 else {
6741 return;
6742 };
6743
6744 let reveal_strategy = action.reveal;
6745 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
6746 cx.spawn_in(window, async move |_, cx| {
6747 let context = task_context.await?;
6748 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
6749
6750 let resolved = resolved_task.resolved.as_mut()?;
6751 resolved.reveal = reveal_strategy;
6752
6753 workspace
6754 .update(cx, |workspace, cx| {
6755 workspace::tasks::schedule_resolved_task(
6756 workspace,
6757 task_source_kind,
6758 resolved_task,
6759 false,
6760 cx,
6761 );
6762 })
6763 .ok()
6764 })
6765 .detach();
6766 }
6767
6768 fn find_closest_task(
6769 &mut self,
6770 cx: &mut Context<Self>,
6771 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6772 let cursor_row = self.selections.newest_adjusted(cx).head().row;
6773
6774 let ((buffer_id, row), tasks) = self
6775 .tasks
6776 .iter()
6777 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
6778
6779 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
6780 let tasks = Arc::new(tasks.to_owned());
6781 Some((buffer, *row, tasks))
6782 }
6783
6784 fn find_enclosing_node_task(
6785 &mut self,
6786 cx: &mut Context<Self>,
6787 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
6788 let snapshot = self.buffer.read(cx).snapshot(cx);
6789 let offset = self.selections.newest::<usize>(cx).head();
6790 let excerpt = snapshot.excerpt_containing(offset..offset)?;
6791 let buffer_id = excerpt.buffer().remote_id();
6792
6793 let layer = excerpt.buffer().syntax_layer_at(offset)?;
6794 let mut cursor = layer.node().walk();
6795
6796 while cursor.goto_first_child_for_byte(offset).is_some() {
6797 if cursor.node().end_byte() == offset {
6798 cursor.goto_next_sibling();
6799 }
6800 }
6801
6802 // Ascend to the smallest ancestor that contains the range and has a task.
6803 loop {
6804 let node = cursor.node();
6805 let node_range = node.byte_range();
6806 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
6807
6808 // Check if this node contains our offset
6809 if node_range.start <= offset && node_range.end >= offset {
6810 // If it contains offset, check for task
6811 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
6812 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
6813 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
6814 }
6815 }
6816
6817 if !cursor.goto_parent() {
6818 break;
6819 }
6820 }
6821 None
6822 }
6823
6824 fn render_run_indicator(
6825 &self,
6826 _style: &EditorStyle,
6827 is_active: bool,
6828 row: DisplayRow,
6829 breakpoint: Option<(Anchor, Breakpoint)>,
6830 cx: &mut Context<Self>,
6831 ) -> IconButton {
6832 let color = Color::Muted;
6833 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6834
6835 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
6836 .shape(ui::IconButtonShape::Square)
6837 .icon_size(IconSize::XSmall)
6838 .icon_color(color)
6839 .toggle_state(is_active)
6840 .on_click(cx.listener(move |editor, _e, window, cx| {
6841 window.focus(&editor.focus_handle(cx));
6842 editor.toggle_code_actions(
6843 &ToggleCodeActions {
6844 deployed_from_indicator: Some(row),
6845 },
6846 window,
6847 cx,
6848 );
6849 }))
6850 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6851 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
6852 }))
6853 }
6854
6855 pub fn context_menu_visible(&self) -> bool {
6856 !self.edit_prediction_preview_is_active()
6857 && self
6858 .context_menu
6859 .borrow()
6860 .as_ref()
6861 .map_or(false, |menu| menu.visible())
6862 }
6863
6864 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
6865 self.context_menu
6866 .borrow()
6867 .as_ref()
6868 .map(|menu| menu.origin())
6869 }
6870
6871 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
6872 self.context_menu_options = Some(options);
6873 }
6874
6875 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
6876 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
6877
6878 fn render_edit_prediction_popover(
6879 &mut self,
6880 text_bounds: &Bounds<Pixels>,
6881 content_origin: gpui::Point<Pixels>,
6882 editor_snapshot: &EditorSnapshot,
6883 visible_row_range: Range<DisplayRow>,
6884 scroll_top: f32,
6885 scroll_bottom: f32,
6886 line_layouts: &[LineWithInvisibles],
6887 line_height: Pixels,
6888 scroll_pixel_position: gpui::Point<Pixels>,
6889 newest_selection_head: Option<DisplayPoint>,
6890 editor_width: Pixels,
6891 style: &EditorStyle,
6892 window: &mut Window,
6893 cx: &mut App,
6894 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
6895 let active_inline_completion = self.active_inline_completion.as_ref()?;
6896
6897 if self.edit_prediction_visible_in_cursor_popover(true) {
6898 return None;
6899 }
6900
6901 match &active_inline_completion.completion {
6902 InlineCompletion::Move { target, .. } => {
6903 let target_display_point = target.to_display_point(editor_snapshot);
6904
6905 if self.edit_prediction_requires_modifier() {
6906 if !self.edit_prediction_preview_is_active() {
6907 return None;
6908 }
6909
6910 self.render_edit_prediction_modifier_jump_popover(
6911 text_bounds,
6912 content_origin,
6913 visible_row_range,
6914 line_layouts,
6915 line_height,
6916 scroll_pixel_position,
6917 newest_selection_head,
6918 target_display_point,
6919 window,
6920 cx,
6921 )
6922 } else {
6923 self.render_edit_prediction_eager_jump_popover(
6924 text_bounds,
6925 content_origin,
6926 editor_snapshot,
6927 visible_row_range,
6928 scroll_top,
6929 scroll_bottom,
6930 line_height,
6931 scroll_pixel_position,
6932 target_display_point,
6933 editor_width,
6934 window,
6935 cx,
6936 )
6937 }
6938 }
6939 InlineCompletion::Edit {
6940 display_mode: EditDisplayMode::Inline,
6941 ..
6942 } => None,
6943 InlineCompletion::Edit {
6944 display_mode: EditDisplayMode::TabAccept,
6945 edits,
6946 ..
6947 } => {
6948 let range = &edits.first()?.0;
6949 let target_display_point = range.end.to_display_point(editor_snapshot);
6950
6951 self.render_edit_prediction_end_of_line_popover(
6952 "Accept",
6953 editor_snapshot,
6954 visible_row_range,
6955 target_display_point,
6956 line_height,
6957 scroll_pixel_position,
6958 content_origin,
6959 editor_width,
6960 window,
6961 cx,
6962 )
6963 }
6964 InlineCompletion::Edit {
6965 edits,
6966 edit_preview,
6967 display_mode: EditDisplayMode::DiffPopover,
6968 snapshot,
6969 } => self.render_edit_prediction_diff_popover(
6970 text_bounds,
6971 content_origin,
6972 editor_snapshot,
6973 visible_row_range,
6974 line_layouts,
6975 line_height,
6976 scroll_pixel_position,
6977 newest_selection_head,
6978 editor_width,
6979 style,
6980 edits,
6981 edit_preview,
6982 snapshot,
6983 window,
6984 cx,
6985 ),
6986 }
6987 }
6988
6989 fn render_edit_prediction_modifier_jump_popover(
6990 &mut self,
6991 text_bounds: &Bounds<Pixels>,
6992 content_origin: gpui::Point<Pixels>,
6993 visible_row_range: Range<DisplayRow>,
6994 line_layouts: &[LineWithInvisibles],
6995 line_height: Pixels,
6996 scroll_pixel_position: gpui::Point<Pixels>,
6997 newest_selection_head: Option<DisplayPoint>,
6998 target_display_point: DisplayPoint,
6999 window: &mut Window,
7000 cx: &mut App,
7001 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7002 let scrolled_content_origin =
7003 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7004
7005 const SCROLL_PADDING_Y: Pixels = px(12.);
7006
7007 if target_display_point.row() < visible_row_range.start {
7008 return self.render_edit_prediction_scroll_popover(
7009 |_| SCROLL_PADDING_Y,
7010 IconName::ArrowUp,
7011 visible_row_range,
7012 line_layouts,
7013 newest_selection_head,
7014 scrolled_content_origin,
7015 window,
7016 cx,
7017 );
7018 } else if target_display_point.row() >= visible_row_range.end {
7019 return self.render_edit_prediction_scroll_popover(
7020 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7021 IconName::ArrowDown,
7022 visible_row_range,
7023 line_layouts,
7024 newest_selection_head,
7025 scrolled_content_origin,
7026 window,
7027 cx,
7028 );
7029 }
7030
7031 const POLE_WIDTH: Pixels = px(2.);
7032
7033 let line_layout =
7034 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7035 let target_column = target_display_point.column() as usize;
7036
7037 let target_x = line_layout.x_for_index(target_column);
7038 let target_y =
7039 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7040
7041 let flag_on_right = target_x < text_bounds.size.width / 2.;
7042
7043 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7044 border_color.l += 0.001;
7045
7046 let mut element = v_flex()
7047 .items_end()
7048 .when(flag_on_right, |el| el.items_start())
7049 .child(if flag_on_right {
7050 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7051 .rounded_bl(px(0.))
7052 .rounded_tl(px(0.))
7053 .border_l_2()
7054 .border_color(border_color)
7055 } else {
7056 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7057 .rounded_br(px(0.))
7058 .rounded_tr(px(0.))
7059 .border_r_2()
7060 .border_color(border_color)
7061 })
7062 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7063 .into_any();
7064
7065 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7066
7067 let mut origin = scrolled_content_origin + point(target_x, target_y)
7068 - point(
7069 if flag_on_right {
7070 POLE_WIDTH
7071 } else {
7072 size.width - POLE_WIDTH
7073 },
7074 size.height - line_height,
7075 );
7076
7077 origin.x = origin.x.max(content_origin.x);
7078
7079 element.prepaint_at(origin, window, cx);
7080
7081 Some((element, origin))
7082 }
7083
7084 fn render_edit_prediction_scroll_popover(
7085 &mut self,
7086 to_y: impl Fn(Size<Pixels>) -> Pixels,
7087 scroll_icon: IconName,
7088 visible_row_range: Range<DisplayRow>,
7089 line_layouts: &[LineWithInvisibles],
7090 newest_selection_head: Option<DisplayPoint>,
7091 scrolled_content_origin: gpui::Point<Pixels>,
7092 window: &mut Window,
7093 cx: &mut App,
7094 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7095 let mut element = self
7096 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7097 .into_any();
7098
7099 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7100
7101 let cursor = newest_selection_head?;
7102 let cursor_row_layout =
7103 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7104 let cursor_column = cursor.column() as usize;
7105
7106 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7107
7108 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7109
7110 element.prepaint_at(origin, window, cx);
7111 Some((element, origin))
7112 }
7113
7114 fn render_edit_prediction_eager_jump_popover(
7115 &mut self,
7116 text_bounds: &Bounds<Pixels>,
7117 content_origin: gpui::Point<Pixels>,
7118 editor_snapshot: &EditorSnapshot,
7119 visible_row_range: Range<DisplayRow>,
7120 scroll_top: f32,
7121 scroll_bottom: f32,
7122 line_height: Pixels,
7123 scroll_pixel_position: gpui::Point<Pixels>,
7124 target_display_point: DisplayPoint,
7125 editor_width: Pixels,
7126 window: &mut Window,
7127 cx: &mut App,
7128 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7129 if target_display_point.row().as_f32() < scroll_top {
7130 let mut element = self
7131 .render_edit_prediction_line_popover(
7132 "Jump to Edit",
7133 Some(IconName::ArrowUp),
7134 window,
7135 cx,
7136 )?
7137 .into_any();
7138
7139 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7140 let offset = point(
7141 (text_bounds.size.width - size.width) / 2.,
7142 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7143 );
7144
7145 let origin = text_bounds.origin + offset;
7146 element.prepaint_at(origin, window, cx);
7147 Some((element, origin))
7148 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7149 let mut element = self
7150 .render_edit_prediction_line_popover(
7151 "Jump to Edit",
7152 Some(IconName::ArrowDown),
7153 window,
7154 cx,
7155 )?
7156 .into_any();
7157
7158 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7159 let offset = point(
7160 (text_bounds.size.width - size.width) / 2.,
7161 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7162 );
7163
7164 let origin = text_bounds.origin + offset;
7165 element.prepaint_at(origin, window, cx);
7166 Some((element, origin))
7167 } else {
7168 self.render_edit_prediction_end_of_line_popover(
7169 "Jump to Edit",
7170 editor_snapshot,
7171 visible_row_range,
7172 target_display_point,
7173 line_height,
7174 scroll_pixel_position,
7175 content_origin,
7176 editor_width,
7177 window,
7178 cx,
7179 )
7180 }
7181 }
7182
7183 fn render_edit_prediction_end_of_line_popover(
7184 self: &mut Editor,
7185 label: &'static str,
7186 editor_snapshot: &EditorSnapshot,
7187 visible_row_range: Range<DisplayRow>,
7188 target_display_point: DisplayPoint,
7189 line_height: Pixels,
7190 scroll_pixel_position: gpui::Point<Pixels>,
7191 content_origin: gpui::Point<Pixels>,
7192 editor_width: Pixels,
7193 window: &mut Window,
7194 cx: &mut App,
7195 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7196 let target_line_end = DisplayPoint::new(
7197 target_display_point.row(),
7198 editor_snapshot.line_len(target_display_point.row()),
7199 );
7200
7201 let mut element = self
7202 .render_edit_prediction_line_popover(label, None, window, cx)?
7203 .into_any();
7204
7205 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7206
7207 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7208
7209 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7210 let mut origin = start_point
7211 + line_origin
7212 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7213 origin.x = origin.x.max(content_origin.x);
7214
7215 let max_x = content_origin.x + editor_width - size.width;
7216
7217 if origin.x > max_x {
7218 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7219
7220 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7221 origin.y += offset;
7222 IconName::ArrowUp
7223 } else {
7224 origin.y -= offset;
7225 IconName::ArrowDown
7226 };
7227
7228 element = self
7229 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7230 .into_any();
7231
7232 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7233
7234 origin.x = content_origin.x + editor_width - size.width - px(2.);
7235 }
7236
7237 element.prepaint_at(origin, window, cx);
7238 Some((element, origin))
7239 }
7240
7241 fn render_edit_prediction_diff_popover(
7242 self: &Editor,
7243 text_bounds: &Bounds<Pixels>,
7244 content_origin: gpui::Point<Pixels>,
7245 editor_snapshot: &EditorSnapshot,
7246 visible_row_range: Range<DisplayRow>,
7247 line_layouts: &[LineWithInvisibles],
7248 line_height: Pixels,
7249 scroll_pixel_position: gpui::Point<Pixels>,
7250 newest_selection_head: Option<DisplayPoint>,
7251 editor_width: Pixels,
7252 style: &EditorStyle,
7253 edits: &Vec<(Range<Anchor>, String)>,
7254 edit_preview: &Option<language::EditPreview>,
7255 snapshot: &language::BufferSnapshot,
7256 window: &mut Window,
7257 cx: &mut App,
7258 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7259 let edit_start = edits
7260 .first()
7261 .unwrap()
7262 .0
7263 .start
7264 .to_display_point(editor_snapshot);
7265 let edit_end = edits
7266 .last()
7267 .unwrap()
7268 .0
7269 .end
7270 .to_display_point(editor_snapshot);
7271
7272 let is_visible = visible_row_range.contains(&edit_start.row())
7273 || visible_row_range.contains(&edit_end.row());
7274 if !is_visible {
7275 return None;
7276 }
7277
7278 let highlighted_edits =
7279 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7280
7281 let styled_text = highlighted_edits.to_styled_text(&style.text);
7282 let line_count = highlighted_edits.text.lines().count();
7283
7284 const BORDER_WIDTH: Pixels = px(1.);
7285
7286 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7287 let has_keybind = keybind.is_some();
7288
7289 let mut element = h_flex()
7290 .items_start()
7291 .child(
7292 h_flex()
7293 .bg(cx.theme().colors().editor_background)
7294 .border(BORDER_WIDTH)
7295 .shadow_sm()
7296 .border_color(cx.theme().colors().border)
7297 .rounded_l_lg()
7298 .when(line_count > 1, |el| el.rounded_br_lg())
7299 .pr_1()
7300 .child(styled_text),
7301 )
7302 .child(
7303 h_flex()
7304 .h(line_height + BORDER_WIDTH * 2.)
7305 .px_1p5()
7306 .gap_1()
7307 // Workaround: For some reason, there's a gap if we don't do this
7308 .ml(-BORDER_WIDTH)
7309 .shadow(smallvec![gpui::BoxShadow {
7310 color: gpui::black().opacity(0.05),
7311 offset: point(px(1.), px(1.)),
7312 blur_radius: px(2.),
7313 spread_radius: px(0.),
7314 }])
7315 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7316 .border(BORDER_WIDTH)
7317 .border_color(cx.theme().colors().border)
7318 .rounded_r_lg()
7319 .id("edit_prediction_diff_popover_keybind")
7320 .when(!has_keybind, |el| {
7321 let status_colors = cx.theme().status();
7322
7323 el.bg(status_colors.error_background)
7324 .border_color(status_colors.error.opacity(0.6))
7325 .child(Icon::new(IconName::Info).color(Color::Error))
7326 .cursor_default()
7327 .hoverable_tooltip(move |_window, cx| {
7328 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7329 })
7330 })
7331 .children(keybind),
7332 )
7333 .into_any();
7334
7335 let longest_row =
7336 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7337 let longest_line_width = if visible_row_range.contains(&longest_row) {
7338 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7339 } else {
7340 layout_line(
7341 longest_row,
7342 editor_snapshot,
7343 style,
7344 editor_width,
7345 |_| false,
7346 window,
7347 cx,
7348 )
7349 .width
7350 };
7351
7352 let viewport_bounds =
7353 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7354 right: -EditorElement::SCROLLBAR_WIDTH,
7355 ..Default::default()
7356 });
7357
7358 let x_after_longest =
7359 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7360 - scroll_pixel_position.x;
7361
7362 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7363
7364 // Fully visible if it can be displayed within the window (allow overlapping other
7365 // panes). However, this is only allowed if the popover starts within text_bounds.
7366 let can_position_to_the_right = x_after_longest < text_bounds.right()
7367 && x_after_longest + element_bounds.width < viewport_bounds.right();
7368
7369 let mut origin = if can_position_to_the_right {
7370 point(
7371 x_after_longest,
7372 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7373 - scroll_pixel_position.y,
7374 )
7375 } else {
7376 let cursor_row = newest_selection_head.map(|head| head.row());
7377 let above_edit = edit_start
7378 .row()
7379 .0
7380 .checked_sub(line_count as u32)
7381 .map(DisplayRow);
7382 let below_edit = Some(edit_end.row() + 1);
7383 let above_cursor =
7384 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7385 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7386
7387 // Place the edit popover adjacent to the edit if there is a location
7388 // available that is onscreen and does not obscure the cursor. Otherwise,
7389 // place it adjacent to the cursor.
7390 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7391 .into_iter()
7392 .flatten()
7393 .find(|&start_row| {
7394 let end_row = start_row + line_count as u32;
7395 visible_row_range.contains(&start_row)
7396 && visible_row_range.contains(&end_row)
7397 && cursor_row.map_or(true, |cursor_row| {
7398 !((start_row..end_row).contains(&cursor_row))
7399 })
7400 })?;
7401
7402 content_origin
7403 + point(
7404 -scroll_pixel_position.x,
7405 row_target.as_f32() * line_height - scroll_pixel_position.y,
7406 )
7407 };
7408
7409 origin.x -= BORDER_WIDTH;
7410
7411 window.defer_draw(element, origin, 1);
7412
7413 // Do not return an element, since it will already be drawn due to defer_draw.
7414 None
7415 }
7416
7417 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7418 px(30.)
7419 }
7420
7421 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7422 if self.read_only(cx) {
7423 cx.theme().players().read_only()
7424 } else {
7425 self.style.as_ref().unwrap().local_player
7426 }
7427 }
7428
7429 fn render_edit_prediction_accept_keybind(
7430 &self,
7431 window: &mut Window,
7432 cx: &App,
7433 ) -> Option<AnyElement> {
7434 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7435 let accept_keystroke = accept_binding.keystroke()?;
7436
7437 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7438
7439 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7440 Color::Accent
7441 } else {
7442 Color::Muted
7443 };
7444
7445 h_flex()
7446 .px_0p5()
7447 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7448 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7449 .text_size(TextSize::XSmall.rems(cx))
7450 .child(h_flex().children(ui::render_modifiers(
7451 &accept_keystroke.modifiers,
7452 PlatformStyle::platform(),
7453 Some(modifiers_color),
7454 Some(IconSize::XSmall.rems().into()),
7455 true,
7456 )))
7457 .when(is_platform_style_mac, |parent| {
7458 parent.child(accept_keystroke.key.clone())
7459 })
7460 .when(!is_platform_style_mac, |parent| {
7461 parent.child(
7462 Key::new(
7463 util::capitalize(&accept_keystroke.key),
7464 Some(Color::Default),
7465 )
7466 .size(Some(IconSize::XSmall.rems().into())),
7467 )
7468 })
7469 .into_any()
7470 .into()
7471 }
7472
7473 fn render_edit_prediction_line_popover(
7474 &self,
7475 label: impl Into<SharedString>,
7476 icon: Option<IconName>,
7477 window: &mut Window,
7478 cx: &App,
7479 ) -> Option<Stateful<Div>> {
7480 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7481
7482 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7483 let has_keybind = keybind.is_some();
7484
7485 let result = h_flex()
7486 .id("ep-line-popover")
7487 .py_0p5()
7488 .pl_1()
7489 .pr(padding_right)
7490 .gap_1()
7491 .rounded_md()
7492 .border_1()
7493 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7494 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7495 .shadow_sm()
7496 .when(!has_keybind, |el| {
7497 let status_colors = cx.theme().status();
7498
7499 el.bg(status_colors.error_background)
7500 .border_color(status_colors.error.opacity(0.6))
7501 .pl_2()
7502 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7503 .cursor_default()
7504 .hoverable_tooltip(move |_window, cx| {
7505 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7506 })
7507 })
7508 .children(keybind)
7509 .child(
7510 Label::new(label)
7511 .size(LabelSize::Small)
7512 .when(!has_keybind, |el| {
7513 el.color(cx.theme().status().error.into()).strikethrough()
7514 }),
7515 )
7516 .when(!has_keybind, |el| {
7517 el.child(
7518 h_flex().ml_1().child(
7519 Icon::new(IconName::Info)
7520 .size(IconSize::Small)
7521 .color(cx.theme().status().error.into()),
7522 ),
7523 )
7524 })
7525 .when_some(icon, |element, icon| {
7526 element.child(
7527 div()
7528 .mt(px(1.5))
7529 .child(Icon::new(icon).size(IconSize::Small)),
7530 )
7531 });
7532
7533 Some(result)
7534 }
7535
7536 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7537 let accent_color = cx.theme().colors().text_accent;
7538 let editor_bg_color = cx.theme().colors().editor_background;
7539 editor_bg_color.blend(accent_color.opacity(0.1))
7540 }
7541
7542 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7543 let accent_color = cx.theme().colors().text_accent;
7544 let editor_bg_color = cx.theme().colors().editor_background;
7545 editor_bg_color.blend(accent_color.opacity(0.6))
7546 }
7547
7548 fn render_edit_prediction_cursor_popover(
7549 &self,
7550 min_width: Pixels,
7551 max_width: Pixels,
7552 cursor_point: Point,
7553 style: &EditorStyle,
7554 accept_keystroke: Option<&gpui::Keystroke>,
7555 _window: &Window,
7556 cx: &mut Context<Editor>,
7557 ) -> Option<AnyElement> {
7558 let provider = self.edit_prediction_provider.as_ref()?;
7559
7560 if provider.provider.needs_terms_acceptance(cx) {
7561 return Some(
7562 h_flex()
7563 .min_w(min_width)
7564 .flex_1()
7565 .px_2()
7566 .py_1()
7567 .gap_3()
7568 .elevation_2(cx)
7569 .hover(|style| style.bg(cx.theme().colors().element_hover))
7570 .id("accept-terms")
7571 .cursor_pointer()
7572 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7573 .on_click(cx.listener(|this, _event, window, cx| {
7574 cx.stop_propagation();
7575 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7576 window.dispatch_action(
7577 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7578 cx,
7579 );
7580 }))
7581 .child(
7582 h_flex()
7583 .flex_1()
7584 .gap_2()
7585 .child(Icon::new(IconName::ZedPredict))
7586 .child(Label::new("Accept Terms of Service"))
7587 .child(div().w_full())
7588 .child(
7589 Icon::new(IconName::ArrowUpRight)
7590 .color(Color::Muted)
7591 .size(IconSize::Small),
7592 )
7593 .into_any_element(),
7594 )
7595 .into_any(),
7596 );
7597 }
7598
7599 let is_refreshing = provider.provider.is_refreshing(cx);
7600
7601 fn pending_completion_container() -> Div {
7602 h_flex()
7603 .h_full()
7604 .flex_1()
7605 .gap_2()
7606 .child(Icon::new(IconName::ZedPredict))
7607 }
7608
7609 let completion = match &self.active_inline_completion {
7610 Some(prediction) => {
7611 if !self.has_visible_completions_menu() {
7612 const RADIUS: Pixels = px(6.);
7613 const BORDER_WIDTH: Pixels = px(1.);
7614
7615 return Some(
7616 h_flex()
7617 .elevation_2(cx)
7618 .border(BORDER_WIDTH)
7619 .border_color(cx.theme().colors().border)
7620 .when(accept_keystroke.is_none(), |el| {
7621 el.border_color(cx.theme().status().error)
7622 })
7623 .rounded(RADIUS)
7624 .rounded_tl(px(0.))
7625 .overflow_hidden()
7626 .child(div().px_1p5().child(match &prediction.completion {
7627 InlineCompletion::Move { target, snapshot } => {
7628 use text::ToPoint as _;
7629 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
7630 {
7631 Icon::new(IconName::ZedPredictDown)
7632 } else {
7633 Icon::new(IconName::ZedPredictUp)
7634 }
7635 }
7636 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
7637 }))
7638 .child(
7639 h_flex()
7640 .gap_1()
7641 .py_1()
7642 .px_2()
7643 .rounded_r(RADIUS - BORDER_WIDTH)
7644 .border_l_1()
7645 .border_color(cx.theme().colors().border)
7646 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7647 .when(self.edit_prediction_preview.released_too_fast(), |el| {
7648 el.child(
7649 Label::new("Hold")
7650 .size(LabelSize::Small)
7651 .when(accept_keystroke.is_none(), |el| {
7652 el.strikethrough()
7653 })
7654 .line_height_style(LineHeightStyle::UiLabel),
7655 )
7656 })
7657 .id("edit_prediction_cursor_popover_keybind")
7658 .when(accept_keystroke.is_none(), |el| {
7659 let status_colors = cx.theme().status();
7660
7661 el.bg(status_colors.error_background)
7662 .border_color(status_colors.error.opacity(0.6))
7663 .child(Icon::new(IconName::Info).color(Color::Error))
7664 .cursor_default()
7665 .hoverable_tooltip(move |_window, cx| {
7666 cx.new(|_| MissingEditPredictionKeybindingTooltip)
7667 .into()
7668 })
7669 })
7670 .when_some(
7671 accept_keystroke.as_ref(),
7672 |el, accept_keystroke| {
7673 el.child(h_flex().children(ui::render_modifiers(
7674 &accept_keystroke.modifiers,
7675 PlatformStyle::platform(),
7676 Some(Color::Default),
7677 Some(IconSize::XSmall.rems().into()),
7678 false,
7679 )))
7680 },
7681 ),
7682 )
7683 .into_any(),
7684 );
7685 }
7686
7687 self.render_edit_prediction_cursor_popover_preview(
7688 prediction,
7689 cursor_point,
7690 style,
7691 cx,
7692 )?
7693 }
7694
7695 None if is_refreshing => match &self.stale_inline_completion_in_menu {
7696 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
7697 stale_completion,
7698 cursor_point,
7699 style,
7700 cx,
7701 )?,
7702
7703 None => {
7704 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
7705 }
7706 },
7707
7708 None => pending_completion_container().child(Label::new("No Prediction")),
7709 };
7710
7711 let completion = if is_refreshing {
7712 completion
7713 .with_animation(
7714 "loading-completion",
7715 Animation::new(Duration::from_secs(2))
7716 .repeat()
7717 .with_easing(pulsating_between(0.4, 0.8)),
7718 |label, delta| label.opacity(delta),
7719 )
7720 .into_any_element()
7721 } else {
7722 completion.into_any_element()
7723 };
7724
7725 let has_completion = self.active_inline_completion.is_some();
7726
7727 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7728 Some(
7729 h_flex()
7730 .min_w(min_width)
7731 .max_w(max_width)
7732 .flex_1()
7733 .elevation_2(cx)
7734 .border_color(cx.theme().colors().border)
7735 .child(
7736 div()
7737 .flex_1()
7738 .py_1()
7739 .px_2()
7740 .overflow_hidden()
7741 .child(completion),
7742 )
7743 .when_some(accept_keystroke, |el, accept_keystroke| {
7744 if !accept_keystroke.modifiers.modified() {
7745 return el;
7746 }
7747
7748 el.child(
7749 h_flex()
7750 .h_full()
7751 .border_l_1()
7752 .rounded_r_lg()
7753 .border_color(cx.theme().colors().border)
7754 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7755 .gap_1()
7756 .py_1()
7757 .px_2()
7758 .child(
7759 h_flex()
7760 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7761 .when(is_platform_style_mac, |parent| parent.gap_1())
7762 .child(h_flex().children(ui::render_modifiers(
7763 &accept_keystroke.modifiers,
7764 PlatformStyle::platform(),
7765 Some(if !has_completion {
7766 Color::Muted
7767 } else {
7768 Color::Default
7769 }),
7770 None,
7771 false,
7772 ))),
7773 )
7774 .child(Label::new("Preview").into_any_element())
7775 .opacity(if has_completion { 1.0 } else { 0.4 }),
7776 )
7777 })
7778 .into_any(),
7779 )
7780 }
7781
7782 fn render_edit_prediction_cursor_popover_preview(
7783 &self,
7784 completion: &InlineCompletionState,
7785 cursor_point: Point,
7786 style: &EditorStyle,
7787 cx: &mut Context<Editor>,
7788 ) -> Option<Div> {
7789 use text::ToPoint as _;
7790
7791 fn render_relative_row_jump(
7792 prefix: impl Into<String>,
7793 current_row: u32,
7794 target_row: u32,
7795 ) -> Div {
7796 let (row_diff, arrow) = if target_row < current_row {
7797 (current_row - target_row, IconName::ArrowUp)
7798 } else {
7799 (target_row - current_row, IconName::ArrowDown)
7800 };
7801
7802 h_flex()
7803 .child(
7804 Label::new(format!("{}{}", prefix.into(), row_diff))
7805 .color(Color::Muted)
7806 .size(LabelSize::Small),
7807 )
7808 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
7809 }
7810
7811 match &completion.completion {
7812 InlineCompletion::Move {
7813 target, snapshot, ..
7814 } => Some(
7815 h_flex()
7816 .px_2()
7817 .gap_2()
7818 .flex_1()
7819 .child(
7820 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
7821 Icon::new(IconName::ZedPredictDown)
7822 } else {
7823 Icon::new(IconName::ZedPredictUp)
7824 },
7825 )
7826 .child(Label::new("Jump to Edit")),
7827 ),
7828
7829 InlineCompletion::Edit {
7830 edits,
7831 edit_preview,
7832 snapshot,
7833 display_mode: _,
7834 } => {
7835 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
7836
7837 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
7838 &snapshot,
7839 &edits,
7840 edit_preview.as_ref()?,
7841 true,
7842 cx,
7843 )
7844 .first_line_preview();
7845
7846 let styled_text = gpui::StyledText::new(highlighted_edits.text)
7847 .with_default_highlights(&style.text, highlighted_edits.highlights);
7848
7849 let preview = h_flex()
7850 .gap_1()
7851 .min_w_16()
7852 .child(styled_text)
7853 .when(has_more_lines, |parent| parent.child("…"));
7854
7855 let left = if first_edit_row != cursor_point.row {
7856 render_relative_row_jump("", cursor_point.row, first_edit_row)
7857 .into_any_element()
7858 } else {
7859 Icon::new(IconName::ZedPredict).into_any_element()
7860 };
7861
7862 Some(
7863 h_flex()
7864 .h_full()
7865 .flex_1()
7866 .gap_2()
7867 .pr_1()
7868 .overflow_x_hidden()
7869 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7870 .child(left)
7871 .child(preview),
7872 )
7873 }
7874 }
7875 }
7876
7877 fn render_context_menu(
7878 &self,
7879 style: &EditorStyle,
7880 max_height_in_lines: u32,
7881 window: &mut Window,
7882 cx: &mut Context<Editor>,
7883 ) -> Option<AnyElement> {
7884 let menu = self.context_menu.borrow();
7885 let menu = menu.as_ref()?;
7886 if !menu.visible() {
7887 return None;
7888 };
7889 Some(menu.render(style, max_height_in_lines, window, cx))
7890 }
7891
7892 fn render_context_menu_aside(
7893 &mut self,
7894 max_size: Size<Pixels>,
7895 window: &mut Window,
7896 cx: &mut Context<Editor>,
7897 ) -> Option<AnyElement> {
7898 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
7899 if menu.visible() {
7900 menu.render_aside(self, max_size, window, cx)
7901 } else {
7902 None
7903 }
7904 })
7905 }
7906
7907 fn hide_context_menu(
7908 &mut self,
7909 window: &mut Window,
7910 cx: &mut Context<Self>,
7911 ) -> Option<CodeContextMenu> {
7912 cx.notify();
7913 self.completion_tasks.clear();
7914 let context_menu = self.context_menu.borrow_mut().take();
7915 self.stale_inline_completion_in_menu.take();
7916 self.update_visible_inline_completion(window, cx);
7917 context_menu
7918 }
7919
7920 fn show_snippet_choices(
7921 &mut self,
7922 choices: &Vec<String>,
7923 selection: Range<Anchor>,
7924 cx: &mut Context<Self>,
7925 ) {
7926 if selection.start.buffer_id.is_none() {
7927 return;
7928 }
7929 let buffer_id = selection.start.buffer_id.unwrap();
7930 let buffer = self.buffer().read(cx).buffer(buffer_id);
7931 let id = post_inc(&mut self.next_completion_id);
7932
7933 if let Some(buffer) = buffer {
7934 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
7935 CompletionsMenu::new_snippet_choices(id, true, choices, selection, buffer),
7936 ));
7937 }
7938 }
7939
7940 pub fn insert_snippet(
7941 &mut self,
7942 insertion_ranges: &[Range<usize>],
7943 snippet: Snippet,
7944 window: &mut Window,
7945 cx: &mut Context<Self>,
7946 ) -> Result<()> {
7947 struct Tabstop<T> {
7948 is_end_tabstop: bool,
7949 ranges: Vec<Range<T>>,
7950 choices: Option<Vec<String>>,
7951 }
7952
7953 let tabstops = self.buffer.update(cx, |buffer, cx| {
7954 let snippet_text: Arc<str> = snippet.text.clone().into();
7955 let edits = insertion_ranges
7956 .iter()
7957 .cloned()
7958 .map(|range| (range, snippet_text.clone()));
7959 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
7960
7961 let snapshot = &*buffer.read(cx);
7962 let snippet = &snippet;
7963 snippet
7964 .tabstops
7965 .iter()
7966 .map(|tabstop| {
7967 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
7968 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
7969 });
7970 let mut tabstop_ranges = tabstop
7971 .ranges
7972 .iter()
7973 .flat_map(|tabstop_range| {
7974 let mut delta = 0_isize;
7975 insertion_ranges.iter().map(move |insertion_range| {
7976 let insertion_start = insertion_range.start as isize + delta;
7977 delta +=
7978 snippet.text.len() as isize - insertion_range.len() as isize;
7979
7980 let start = ((insertion_start + tabstop_range.start) as usize)
7981 .min(snapshot.len());
7982 let end = ((insertion_start + tabstop_range.end) as usize)
7983 .min(snapshot.len());
7984 snapshot.anchor_before(start)..snapshot.anchor_after(end)
7985 })
7986 })
7987 .collect::<Vec<_>>();
7988 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
7989
7990 Tabstop {
7991 is_end_tabstop,
7992 ranges: tabstop_ranges,
7993 choices: tabstop.choices.clone(),
7994 }
7995 })
7996 .collect::<Vec<_>>()
7997 });
7998 if let Some(tabstop) = tabstops.first() {
7999 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8000 s.select_ranges(tabstop.ranges.iter().cloned());
8001 });
8002
8003 if let Some(choices) = &tabstop.choices {
8004 if let Some(selection) = tabstop.ranges.first() {
8005 self.show_snippet_choices(choices, selection.clone(), cx)
8006 }
8007 }
8008
8009 // If we're already at the last tabstop and it's at the end of the snippet,
8010 // we're done, we don't need to keep the state around.
8011 if !tabstop.is_end_tabstop {
8012 let choices = tabstops
8013 .iter()
8014 .map(|tabstop| tabstop.choices.clone())
8015 .collect();
8016
8017 let ranges = tabstops
8018 .into_iter()
8019 .map(|tabstop| tabstop.ranges)
8020 .collect::<Vec<_>>();
8021
8022 self.snippet_stack.push(SnippetState {
8023 active_index: 0,
8024 ranges,
8025 choices,
8026 });
8027 }
8028
8029 // Check whether the just-entered snippet ends with an auto-closable bracket.
8030 if self.autoclose_regions.is_empty() {
8031 let snapshot = self.buffer.read(cx).snapshot(cx);
8032 for selection in &mut self.selections.all::<Point>(cx) {
8033 let selection_head = selection.head();
8034 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8035 continue;
8036 };
8037
8038 let mut bracket_pair = None;
8039 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8040 let prev_chars = snapshot
8041 .reversed_chars_at(selection_head)
8042 .collect::<String>();
8043 for (pair, enabled) in scope.brackets() {
8044 if enabled
8045 && pair.close
8046 && prev_chars.starts_with(pair.start.as_str())
8047 && next_chars.starts_with(pair.end.as_str())
8048 {
8049 bracket_pair = Some(pair.clone());
8050 break;
8051 }
8052 }
8053 if let Some(pair) = bracket_pair {
8054 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8055 let autoclose_enabled =
8056 self.use_autoclose && snapshot_settings.use_autoclose;
8057 if autoclose_enabled {
8058 let start = snapshot.anchor_after(selection_head);
8059 let end = snapshot.anchor_after(selection_head);
8060 self.autoclose_regions.push(AutocloseRegion {
8061 selection_id: selection.id,
8062 range: start..end,
8063 pair,
8064 });
8065 }
8066 }
8067 }
8068 }
8069 }
8070 Ok(())
8071 }
8072
8073 pub fn move_to_next_snippet_tabstop(
8074 &mut self,
8075 window: &mut Window,
8076 cx: &mut Context<Self>,
8077 ) -> bool {
8078 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8079 }
8080
8081 pub fn move_to_prev_snippet_tabstop(
8082 &mut self,
8083 window: &mut Window,
8084 cx: &mut Context<Self>,
8085 ) -> bool {
8086 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8087 }
8088
8089 pub fn move_to_snippet_tabstop(
8090 &mut self,
8091 bias: Bias,
8092 window: &mut Window,
8093 cx: &mut Context<Self>,
8094 ) -> bool {
8095 if let Some(mut snippet) = self.snippet_stack.pop() {
8096 match bias {
8097 Bias::Left => {
8098 if snippet.active_index > 0 {
8099 snippet.active_index -= 1;
8100 } else {
8101 self.snippet_stack.push(snippet);
8102 return false;
8103 }
8104 }
8105 Bias::Right => {
8106 if snippet.active_index + 1 < snippet.ranges.len() {
8107 snippet.active_index += 1;
8108 } else {
8109 self.snippet_stack.push(snippet);
8110 return false;
8111 }
8112 }
8113 }
8114 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8115 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8116 s.select_anchor_ranges(current_ranges.iter().cloned())
8117 });
8118
8119 if let Some(choices) = &snippet.choices[snippet.active_index] {
8120 if let Some(selection) = current_ranges.first() {
8121 self.show_snippet_choices(&choices, selection.clone(), cx);
8122 }
8123 }
8124
8125 // If snippet state is not at the last tabstop, push it back on the stack
8126 if snippet.active_index + 1 < snippet.ranges.len() {
8127 self.snippet_stack.push(snippet);
8128 }
8129 return true;
8130 }
8131 }
8132
8133 false
8134 }
8135
8136 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8137 self.transact(window, cx, |this, window, cx| {
8138 this.select_all(&SelectAll, window, cx);
8139 this.insert("", window, cx);
8140 });
8141 }
8142
8143 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8144 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8145 self.transact(window, cx, |this, window, cx| {
8146 this.select_autoclose_pair(window, cx);
8147 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8148 if !this.linked_edit_ranges.is_empty() {
8149 let selections = this.selections.all::<MultiBufferPoint>(cx);
8150 let snapshot = this.buffer.read(cx).snapshot(cx);
8151
8152 for selection in selections.iter() {
8153 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8154 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8155 if selection_start.buffer_id != selection_end.buffer_id {
8156 continue;
8157 }
8158 if let Some(ranges) =
8159 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8160 {
8161 for (buffer, entries) in ranges {
8162 linked_ranges.entry(buffer).or_default().extend(entries);
8163 }
8164 }
8165 }
8166 }
8167
8168 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8169 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8170 for selection in &mut selections {
8171 if selection.is_empty() {
8172 let old_head = selection.head();
8173 let mut new_head =
8174 movement::left(&display_map, old_head.to_display_point(&display_map))
8175 .to_point(&display_map);
8176 if let Some((buffer, line_buffer_range)) = display_map
8177 .buffer_snapshot
8178 .buffer_line_for_row(MultiBufferRow(old_head.row))
8179 {
8180 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8181 let indent_len = match indent_size.kind {
8182 IndentKind::Space => {
8183 buffer.settings_at(line_buffer_range.start, cx).tab_size
8184 }
8185 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8186 };
8187 if old_head.column <= indent_size.len && old_head.column > 0 {
8188 let indent_len = indent_len.get();
8189 new_head = cmp::min(
8190 new_head,
8191 MultiBufferPoint::new(
8192 old_head.row,
8193 ((old_head.column - 1) / indent_len) * indent_len,
8194 ),
8195 );
8196 }
8197 }
8198
8199 selection.set_head(new_head, SelectionGoal::None);
8200 }
8201 }
8202
8203 this.signature_help_state.set_backspace_pressed(true);
8204 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8205 s.select(selections)
8206 });
8207 this.insert("", window, cx);
8208 let empty_str: Arc<str> = Arc::from("");
8209 for (buffer, edits) in linked_ranges {
8210 let snapshot = buffer.read(cx).snapshot();
8211 use text::ToPoint as TP;
8212
8213 let edits = edits
8214 .into_iter()
8215 .map(|range| {
8216 let end_point = TP::to_point(&range.end, &snapshot);
8217 let mut start_point = TP::to_point(&range.start, &snapshot);
8218
8219 if end_point == start_point {
8220 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8221 .saturating_sub(1);
8222 start_point =
8223 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8224 };
8225
8226 (start_point..end_point, empty_str.clone())
8227 })
8228 .sorted_by_key(|(range, _)| range.start)
8229 .collect::<Vec<_>>();
8230 buffer.update(cx, |this, cx| {
8231 this.edit(edits, None, cx);
8232 })
8233 }
8234 this.refresh_inline_completion(true, false, window, cx);
8235 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8236 });
8237 }
8238
8239 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8240 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8241 self.transact(window, cx, |this, window, cx| {
8242 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8243 s.move_with(|map, selection| {
8244 if selection.is_empty() {
8245 let cursor = movement::right(map, selection.head());
8246 selection.end = cursor;
8247 selection.reversed = true;
8248 selection.goal = SelectionGoal::None;
8249 }
8250 })
8251 });
8252 this.insert("", window, cx);
8253 this.refresh_inline_completion(true, false, window, cx);
8254 });
8255 }
8256
8257 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8258 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8259 if self.move_to_prev_snippet_tabstop(window, cx) {
8260 return;
8261 }
8262 self.outdent(&Outdent, window, cx);
8263 }
8264
8265 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8266 if self.move_to_next_snippet_tabstop(window, cx) {
8267 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8268 return;
8269 }
8270 if self.read_only(cx) {
8271 return;
8272 }
8273 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8274 let mut selections = self.selections.all_adjusted(cx);
8275 let buffer = self.buffer.read(cx);
8276 let snapshot = buffer.snapshot(cx);
8277 let rows_iter = selections.iter().map(|s| s.head().row);
8278 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8279
8280 let mut edits = Vec::new();
8281 let mut prev_edited_row = 0;
8282 let mut row_delta = 0;
8283 for selection in &mut selections {
8284 if selection.start.row != prev_edited_row {
8285 row_delta = 0;
8286 }
8287 prev_edited_row = selection.end.row;
8288
8289 // If the selection is non-empty, then increase the indentation of the selected lines.
8290 if !selection.is_empty() {
8291 row_delta =
8292 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8293 continue;
8294 }
8295
8296 // If the selection is empty and the cursor is in the leading whitespace before the
8297 // suggested indentation, then auto-indent the line.
8298 let cursor = selection.head();
8299 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8300 if let Some(suggested_indent) =
8301 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8302 {
8303 if cursor.column < suggested_indent.len
8304 && cursor.column <= current_indent.len
8305 && current_indent.len <= suggested_indent.len
8306 {
8307 selection.start = Point::new(cursor.row, suggested_indent.len);
8308 selection.end = selection.start;
8309 if row_delta == 0 {
8310 edits.extend(Buffer::edit_for_indent_size_adjustment(
8311 cursor.row,
8312 current_indent,
8313 suggested_indent,
8314 ));
8315 row_delta = suggested_indent.len - current_indent.len;
8316 }
8317 continue;
8318 }
8319 }
8320
8321 // Otherwise, insert a hard or soft tab.
8322 let settings = buffer.language_settings_at(cursor, cx);
8323 let tab_size = if settings.hard_tabs {
8324 IndentSize::tab()
8325 } else {
8326 let tab_size = settings.tab_size.get();
8327 let indent_remainder = snapshot
8328 .text_for_range(Point::new(cursor.row, 0)..cursor)
8329 .flat_map(str::chars)
8330 .fold(row_delta % tab_size, |counter: u32, c| {
8331 if c == '\t' {
8332 0
8333 } else {
8334 (counter + 1) % tab_size
8335 }
8336 });
8337
8338 let chars_to_next_tab_stop = tab_size - indent_remainder;
8339 IndentSize::spaces(chars_to_next_tab_stop)
8340 };
8341 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8342 selection.end = selection.start;
8343 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8344 row_delta += tab_size.len;
8345 }
8346
8347 self.transact(window, cx, |this, window, cx| {
8348 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8349 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8350 s.select(selections)
8351 });
8352 this.refresh_inline_completion(true, false, window, cx);
8353 });
8354 }
8355
8356 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8357 if self.read_only(cx) {
8358 return;
8359 }
8360 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8361 let mut selections = self.selections.all::<Point>(cx);
8362 let mut prev_edited_row = 0;
8363 let mut row_delta = 0;
8364 let mut edits = Vec::new();
8365 let buffer = self.buffer.read(cx);
8366 let snapshot = buffer.snapshot(cx);
8367 for selection in &mut selections {
8368 if selection.start.row != prev_edited_row {
8369 row_delta = 0;
8370 }
8371 prev_edited_row = selection.end.row;
8372
8373 row_delta =
8374 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8375 }
8376
8377 self.transact(window, cx, |this, window, cx| {
8378 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8379 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8380 s.select(selections)
8381 });
8382 });
8383 }
8384
8385 fn indent_selection(
8386 buffer: &MultiBuffer,
8387 snapshot: &MultiBufferSnapshot,
8388 selection: &mut Selection<Point>,
8389 edits: &mut Vec<(Range<Point>, String)>,
8390 delta_for_start_row: u32,
8391 cx: &App,
8392 ) -> u32 {
8393 let settings = buffer.language_settings_at(selection.start, cx);
8394 let tab_size = settings.tab_size.get();
8395 let indent_kind = if settings.hard_tabs {
8396 IndentKind::Tab
8397 } else {
8398 IndentKind::Space
8399 };
8400 let mut start_row = selection.start.row;
8401 let mut end_row = selection.end.row + 1;
8402
8403 // If a selection ends at the beginning of a line, don't indent
8404 // that last line.
8405 if selection.end.column == 0 && selection.end.row > selection.start.row {
8406 end_row -= 1;
8407 }
8408
8409 // Avoid re-indenting a row that has already been indented by a
8410 // previous selection, but still update this selection's column
8411 // to reflect that indentation.
8412 if delta_for_start_row > 0 {
8413 start_row += 1;
8414 selection.start.column += delta_for_start_row;
8415 if selection.end.row == selection.start.row {
8416 selection.end.column += delta_for_start_row;
8417 }
8418 }
8419
8420 let mut delta_for_end_row = 0;
8421 let has_multiple_rows = start_row + 1 != end_row;
8422 for row in start_row..end_row {
8423 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8424 let indent_delta = match (current_indent.kind, indent_kind) {
8425 (IndentKind::Space, IndentKind::Space) => {
8426 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8427 IndentSize::spaces(columns_to_next_tab_stop)
8428 }
8429 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8430 (_, IndentKind::Tab) => IndentSize::tab(),
8431 };
8432
8433 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8434 0
8435 } else {
8436 selection.start.column
8437 };
8438 let row_start = Point::new(row, start);
8439 edits.push((
8440 row_start..row_start,
8441 indent_delta.chars().collect::<String>(),
8442 ));
8443
8444 // Update this selection's endpoints to reflect the indentation.
8445 if row == selection.start.row {
8446 selection.start.column += indent_delta.len;
8447 }
8448 if row == selection.end.row {
8449 selection.end.column += indent_delta.len;
8450 delta_for_end_row = indent_delta.len;
8451 }
8452 }
8453
8454 if selection.start.row == selection.end.row {
8455 delta_for_start_row + delta_for_end_row
8456 } else {
8457 delta_for_end_row
8458 }
8459 }
8460
8461 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8462 if self.read_only(cx) {
8463 return;
8464 }
8465 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8467 let selections = self.selections.all::<Point>(cx);
8468 let mut deletion_ranges = Vec::new();
8469 let mut last_outdent = None;
8470 {
8471 let buffer = self.buffer.read(cx);
8472 let snapshot = buffer.snapshot(cx);
8473 for selection in &selections {
8474 let settings = buffer.language_settings_at(selection.start, cx);
8475 let tab_size = settings.tab_size.get();
8476 let mut rows = selection.spanned_rows(false, &display_map);
8477
8478 // Avoid re-outdenting a row that has already been outdented by a
8479 // previous selection.
8480 if let Some(last_row) = last_outdent {
8481 if last_row == rows.start {
8482 rows.start = rows.start.next_row();
8483 }
8484 }
8485 let has_multiple_rows = rows.len() > 1;
8486 for row in rows.iter_rows() {
8487 let indent_size = snapshot.indent_size_for_line(row);
8488 if indent_size.len > 0 {
8489 let deletion_len = match indent_size.kind {
8490 IndentKind::Space => {
8491 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8492 if columns_to_prev_tab_stop == 0 {
8493 tab_size
8494 } else {
8495 columns_to_prev_tab_stop
8496 }
8497 }
8498 IndentKind::Tab => 1,
8499 };
8500 let start = if has_multiple_rows
8501 || deletion_len > selection.start.column
8502 || indent_size.len < selection.start.column
8503 {
8504 0
8505 } else {
8506 selection.start.column - deletion_len
8507 };
8508 deletion_ranges.push(
8509 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8510 );
8511 last_outdent = Some(row);
8512 }
8513 }
8514 }
8515 }
8516
8517 self.transact(window, cx, |this, window, cx| {
8518 this.buffer.update(cx, |buffer, cx| {
8519 let empty_str: Arc<str> = Arc::default();
8520 buffer.edit(
8521 deletion_ranges
8522 .into_iter()
8523 .map(|range| (range, empty_str.clone())),
8524 None,
8525 cx,
8526 );
8527 });
8528 let selections = this.selections.all::<usize>(cx);
8529 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8530 s.select(selections)
8531 });
8532 });
8533 }
8534
8535 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8536 if self.read_only(cx) {
8537 return;
8538 }
8539 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8540 let selections = self
8541 .selections
8542 .all::<usize>(cx)
8543 .into_iter()
8544 .map(|s| s.range());
8545
8546 self.transact(window, cx, |this, window, cx| {
8547 this.buffer.update(cx, |buffer, cx| {
8548 buffer.autoindent_ranges(selections, cx);
8549 });
8550 let selections = this.selections.all::<usize>(cx);
8551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8552 s.select(selections)
8553 });
8554 });
8555 }
8556
8557 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8558 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8559 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8560 let selections = self.selections.all::<Point>(cx);
8561
8562 let mut new_cursors = Vec::new();
8563 let mut edit_ranges = Vec::new();
8564 let mut selections = selections.iter().peekable();
8565 while let Some(selection) = selections.next() {
8566 let mut rows = selection.spanned_rows(false, &display_map);
8567 let goal_display_column = selection.head().to_display_point(&display_map).column();
8568
8569 // Accumulate contiguous regions of rows that we want to delete.
8570 while let Some(next_selection) = selections.peek() {
8571 let next_rows = next_selection.spanned_rows(false, &display_map);
8572 if next_rows.start <= rows.end {
8573 rows.end = next_rows.end;
8574 selections.next().unwrap();
8575 } else {
8576 break;
8577 }
8578 }
8579
8580 let buffer = &display_map.buffer_snapshot;
8581 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8582 let edit_end;
8583 let cursor_buffer_row;
8584 if buffer.max_point().row >= rows.end.0 {
8585 // If there's a line after the range, delete the \n from the end of the row range
8586 // and position the cursor on the next line.
8587 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8588 cursor_buffer_row = rows.end;
8589 } else {
8590 // If there isn't a line after the range, delete the \n from the line before the
8591 // start of the row range and position the cursor there.
8592 edit_start = edit_start.saturating_sub(1);
8593 edit_end = buffer.len();
8594 cursor_buffer_row = rows.start.previous_row();
8595 }
8596
8597 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
8598 *cursor.column_mut() =
8599 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
8600
8601 new_cursors.push((
8602 selection.id,
8603 buffer.anchor_after(cursor.to_point(&display_map)),
8604 ));
8605 edit_ranges.push(edit_start..edit_end);
8606 }
8607
8608 self.transact(window, cx, |this, window, cx| {
8609 let buffer = this.buffer.update(cx, |buffer, cx| {
8610 let empty_str: Arc<str> = Arc::default();
8611 buffer.edit(
8612 edit_ranges
8613 .into_iter()
8614 .map(|range| (range, empty_str.clone())),
8615 None,
8616 cx,
8617 );
8618 buffer.snapshot(cx)
8619 });
8620 let new_selections = new_cursors
8621 .into_iter()
8622 .map(|(id, cursor)| {
8623 let cursor = cursor.to_point(&buffer);
8624 Selection {
8625 id,
8626 start: cursor,
8627 end: cursor,
8628 reversed: false,
8629 goal: SelectionGoal::None,
8630 }
8631 })
8632 .collect();
8633
8634 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8635 s.select(new_selections);
8636 });
8637 });
8638 }
8639
8640 pub fn join_lines_impl(
8641 &mut self,
8642 insert_whitespace: bool,
8643 window: &mut Window,
8644 cx: &mut Context<Self>,
8645 ) {
8646 if self.read_only(cx) {
8647 return;
8648 }
8649 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
8650 for selection in self.selections.all::<Point>(cx) {
8651 let start = MultiBufferRow(selection.start.row);
8652 // Treat single line selections as if they include the next line. Otherwise this action
8653 // would do nothing for single line selections individual cursors.
8654 let end = if selection.start.row == selection.end.row {
8655 MultiBufferRow(selection.start.row + 1)
8656 } else {
8657 MultiBufferRow(selection.end.row)
8658 };
8659
8660 if let Some(last_row_range) = row_ranges.last_mut() {
8661 if start <= last_row_range.end {
8662 last_row_range.end = end;
8663 continue;
8664 }
8665 }
8666 row_ranges.push(start..end);
8667 }
8668
8669 let snapshot = self.buffer.read(cx).snapshot(cx);
8670 let mut cursor_positions = Vec::new();
8671 for row_range in &row_ranges {
8672 let anchor = snapshot.anchor_before(Point::new(
8673 row_range.end.previous_row().0,
8674 snapshot.line_len(row_range.end.previous_row()),
8675 ));
8676 cursor_positions.push(anchor..anchor);
8677 }
8678
8679 self.transact(window, cx, |this, window, cx| {
8680 for row_range in row_ranges.into_iter().rev() {
8681 for row in row_range.iter_rows().rev() {
8682 let end_of_line = Point::new(row.0, snapshot.line_len(row));
8683 let next_line_row = row.next_row();
8684 let indent = snapshot.indent_size_for_line(next_line_row);
8685 let start_of_next_line = Point::new(next_line_row.0, indent.len);
8686
8687 let replace =
8688 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
8689 " "
8690 } else {
8691 ""
8692 };
8693
8694 this.buffer.update(cx, |buffer, cx| {
8695 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
8696 });
8697 }
8698 }
8699
8700 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8701 s.select_anchor_ranges(cursor_positions)
8702 });
8703 });
8704 }
8705
8706 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
8707 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8708 self.join_lines_impl(true, window, cx);
8709 }
8710
8711 pub fn sort_lines_case_sensitive(
8712 &mut self,
8713 _: &SortLinesCaseSensitive,
8714 window: &mut Window,
8715 cx: &mut Context<Self>,
8716 ) {
8717 self.manipulate_lines(window, cx, |lines| lines.sort())
8718 }
8719
8720 pub fn sort_lines_case_insensitive(
8721 &mut self,
8722 _: &SortLinesCaseInsensitive,
8723 window: &mut Window,
8724 cx: &mut Context<Self>,
8725 ) {
8726 self.manipulate_lines(window, cx, |lines| {
8727 lines.sort_by_key(|line| line.to_lowercase())
8728 })
8729 }
8730
8731 pub fn unique_lines_case_insensitive(
8732 &mut self,
8733 _: &UniqueLinesCaseInsensitive,
8734 window: &mut Window,
8735 cx: &mut Context<Self>,
8736 ) {
8737 self.manipulate_lines(window, cx, |lines| {
8738 let mut seen = HashSet::default();
8739 lines.retain(|line| seen.insert(line.to_lowercase()));
8740 })
8741 }
8742
8743 pub fn unique_lines_case_sensitive(
8744 &mut self,
8745 _: &UniqueLinesCaseSensitive,
8746 window: &mut Window,
8747 cx: &mut Context<Self>,
8748 ) {
8749 self.manipulate_lines(window, cx, |lines| {
8750 let mut seen = HashSet::default();
8751 lines.retain(|line| seen.insert(*line));
8752 })
8753 }
8754
8755 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
8756 let Some(project) = self.project.clone() else {
8757 return;
8758 };
8759 self.reload(project, window, cx)
8760 .detach_and_notify_err(window, cx);
8761 }
8762
8763 pub fn restore_file(
8764 &mut self,
8765 _: &::git::RestoreFile,
8766 window: &mut Window,
8767 cx: &mut Context<Self>,
8768 ) {
8769 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8770 let mut buffer_ids = HashSet::default();
8771 let snapshot = self.buffer().read(cx).snapshot(cx);
8772 for selection in self.selections.all::<usize>(cx) {
8773 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
8774 }
8775
8776 let buffer = self.buffer().read(cx);
8777 let ranges = buffer_ids
8778 .into_iter()
8779 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
8780 .collect::<Vec<_>>();
8781
8782 self.restore_hunks_in_ranges(ranges, window, cx);
8783 }
8784
8785 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
8786 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8787 let selections = self
8788 .selections
8789 .all(cx)
8790 .into_iter()
8791 .map(|s| s.range())
8792 .collect();
8793 self.restore_hunks_in_ranges(selections, window, cx);
8794 }
8795
8796 pub fn restore_hunks_in_ranges(
8797 &mut self,
8798 ranges: Vec<Range<Point>>,
8799 window: &mut Window,
8800 cx: &mut Context<Editor>,
8801 ) {
8802 let mut revert_changes = HashMap::default();
8803 let chunk_by = self
8804 .snapshot(window, cx)
8805 .hunks_for_ranges(ranges)
8806 .into_iter()
8807 .chunk_by(|hunk| hunk.buffer_id);
8808 for (buffer_id, hunks) in &chunk_by {
8809 let hunks = hunks.collect::<Vec<_>>();
8810 for hunk in &hunks {
8811 self.prepare_restore_change(&mut revert_changes, hunk, cx);
8812 }
8813 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
8814 }
8815 drop(chunk_by);
8816 if !revert_changes.is_empty() {
8817 self.transact(window, cx, |editor, window, cx| {
8818 editor.restore(revert_changes, window, cx);
8819 });
8820 }
8821 }
8822
8823 pub fn open_active_item_in_terminal(
8824 &mut self,
8825 _: &OpenInTerminal,
8826 window: &mut Window,
8827 cx: &mut Context<Self>,
8828 ) {
8829 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
8830 let project_path = buffer.read(cx).project_path(cx)?;
8831 let project = self.project.as_ref()?.read(cx);
8832 let entry = project.entry_for_path(&project_path, cx)?;
8833 let parent = match &entry.canonical_path {
8834 Some(canonical_path) => canonical_path.to_path_buf(),
8835 None => project.absolute_path(&project_path, cx)?,
8836 }
8837 .parent()?
8838 .to_path_buf();
8839 Some(parent)
8840 }) {
8841 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
8842 }
8843 }
8844
8845 fn set_breakpoint_context_menu(
8846 &mut self,
8847 display_row: DisplayRow,
8848 position: Option<Anchor>,
8849 clicked_point: gpui::Point<Pixels>,
8850 window: &mut Window,
8851 cx: &mut Context<Self>,
8852 ) {
8853 if !cx.has_flag::<Debugger>() {
8854 return;
8855 }
8856 let source = self
8857 .buffer
8858 .read(cx)
8859 .snapshot(cx)
8860 .anchor_before(Point::new(display_row.0, 0u32));
8861
8862 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
8863
8864 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
8865 self,
8866 source,
8867 clicked_point,
8868 None,
8869 context_menu,
8870 window,
8871 cx,
8872 );
8873 }
8874
8875 fn add_edit_breakpoint_block(
8876 &mut self,
8877 anchor: Anchor,
8878 breakpoint: &Breakpoint,
8879 edit_action: BreakpointPromptEditAction,
8880 window: &mut Window,
8881 cx: &mut Context<Self>,
8882 ) {
8883 let weak_editor = cx.weak_entity();
8884 let bp_prompt = cx.new(|cx| {
8885 BreakpointPromptEditor::new(
8886 weak_editor,
8887 anchor,
8888 breakpoint.clone(),
8889 edit_action,
8890 window,
8891 cx,
8892 )
8893 });
8894
8895 let height = bp_prompt.update(cx, |this, cx| {
8896 this.prompt
8897 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
8898 });
8899 let cloned_prompt = bp_prompt.clone();
8900 let blocks = vec![BlockProperties {
8901 style: BlockStyle::Sticky,
8902 placement: BlockPlacement::Above(anchor),
8903 height: Some(height),
8904 render: Arc::new(move |cx| {
8905 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
8906 cloned_prompt.clone().into_any_element()
8907 }),
8908 priority: 0,
8909 }];
8910
8911 let focus_handle = bp_prompt.focus_handle(cx);
8912 window.focus(&focus_handle);
8913
8914 let block_ids = self.insert_blocks(blocks, None, cx);
8915 bp_prompt.update(cx, |prompt, _| {
8916 prompt.add_block_ids(block_ids);
8917 });
8918 }
8919
8920 pub(crate) fn breakpoint_at_row(
8921 &self,
8922 row: u32,
8923 window: &mut Window,
8924 cx: &mut Context<Self>,
8925 ) -> Option<(Anchor, Breakpoint)> {
8926 let snapshot = self.snapshot(window, cx);
8927 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
8928
8929 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
8930 }
8931
8932 pub(crate) fn breakpoint_at_anchor(
8933 &self,
8934 breakpoint_position: Anchor,
8935 snapshot: &EditorSnapshot,
8936 cx: &mut Context<Self>,
8937 ) -> Option<(Anchor, Breakpoint)> {
8938 let project = self.project.clone()?;
8939
8940 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
8941 snapshot
8942 .buffer_snapshot
8943 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
8944 })?;
8945
8946 let enclosing_excerpt = breakpoint_position.excerpt_id;
8947 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
8948 let buffer_snapshot = buffer.read(cx).snapshot();
8949
8950 let row = buffer_snapshot
8951 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
8952 .row;
8953
8954 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
8955 let anchor_end = snapshot
8956 .buffer_snapshot
8957 .anchor_after(Point::new(row, line_len));
8958
8959 let bp = self
8960 .breakpoint_store
8961 .as_ref()?
8962 .read_with(cx, |breakpoint_store, cx| {
8963 breakpoint_store
8964 .breakpoints(
8965 &buffer,
8966 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
8967 &buffer_snapshot,
8968 cx,
8969 )
8970 .next()
8971 .and_then(|(anchor, bp)| {
8972 let breakpoint_row = buffer_snapshot
8973 .summary_for_anchor::<text::PointUtf16>(anchor)
8974 .row;
8975
8976 if breakpoint_row == row {
8977 snapshot
8978 .buffer_snapshot
8979 .anchor_in_excerpt(enclosing_excerpt, *anchor)
8980 .map(|anchor| (anchor, bp.clone()))
8981 } else {
8982 None
8983 }
8984 })
8985 });
8986 bp
8987 }
8988
8989 pub fn edit_log_breakpoint(
8990 &mut self,
8991 _: &EditLogBreakpoint,
8992 window: &mut Window,
8993 cx: &mut Context<Self>,
8994 ) {
8995 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
8996 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
8997 message: None,
8998 state: BreakpointState::Enabled,
8999 condition: None,
9000 hit_condition: None,
9001 });
9002
9003 self.add_edit_breakpoint_block(
9004 anchor,
9005 &breakpoint,
9006 BreakpointPromptEditAction::Log,
9007 window,
9008 cx,
9009 );
9010 }
9011 }
9012
9013 fn breakpoints_at_cursors(
9014 &self,
9015 window: &mut Window,
9016 cx: &mut Context<Self>,
9017 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9018 let snapshot = self.snapshot(window, cx);
9019 let cursors = self
9020 .selections
9021 .disjoint_anchors()
9022 .into_iter()
9023 .map(|selection| {
9024 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9025
9026 let breakpoint_position = self
9027 .breakpoint_at_row(cursor_position.row, window, cx)
9028 .map(|bp| bp.0)
9029 .unwrap_or_else(|| {
9030 snapshot
9031 .display_snapshot
9032 .buffer_snapshot
9033 .anchor_after(Point::new(cursor_position.row, 0))
9034 });
9035
9036 let breakpoint = self
9037 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9038 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9039
9040 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9041 })
9042 // 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.
9043 .collect::<HashMap<Anchor, _>>();
9044
9045 cursors.into_iter().collect()
9046 }
9047
9048 pub fn enable_breakpoint(
9049 &mut self,
9050 _: &crate::actions::EnableBreakpoint,
9051 window: &mut Window,
9052 cx: &mut Context<Self>,
9053 ) {
9054 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9055 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9056 continue;
9057 };
9058 self.edit_breakpoint_at_anchor(
9059 anchor,
9060 breakpoint,
9061 BreakpointEditAction::InvertState,
9062 cx,
9063 );
9064 }
9065 }
9066
9067 pub fn disable_breakpoint(
9068 &mut self,
9069 _: &crate::actions::DisableBreakpoint,
9070 window: &mut Window,
9071 cx: &mut Context<Self>,
9072 ) {
9073 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9074 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9075 continue;
9076 };
9077 self.edit_breakpoint_at_anchor(
9078 anchor,
9079 breakpoint,
9080 BreakpointEditAction::InvertState,
9081 cx,
9082 );
9083 }
9084 }
9085
9086 pub fn toggle_breakpoint(
9087 &mut self,
9088 _: &crate::actions::ToggleBreakpoint,
9089 window: &mut Window,
9090 cx: &mut Context<Self>,
9091 ) {
9092 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9093 if let Some(breakpoint) = breakpoint {
9094 self.edit_breakpoint_at_anchor(
9095 anchor,
9096 breakpoint,
9097 BreakpointEditAction::Toggle,
9098 cx,
9099 );
9100 } else {
9101 self.edit_breakpoint_at_anchor(
9102 anchor,
9103 Breakpoint::new_standard(),
9104 BreakpointEditAction::Toggle,
9105 cx,
9106 );
9107 }
9108 }
9109 }
9110
9111 pub fn edit_breakpoint_at_anchor(
9112 &mut self,
9113 breakpoint_position: Anchor,
9114 breakpoint: Breakpoint,
9115 edit_action: BreakpointEditAction,
9116 cx: &mut Context<Self>,
9117 ) {
9118 let Some(breakpoint_store) = &self.breakpoint_store else {
9119 return;
9120 };
9121
9122 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9123 if breakpoint_position == Anchor::min() {
9124 self.buffer()
9125 .read(cx)
9126 .excerpt_buffer_ids()
9127 .into_iter()
9128 .next()
9129 } else {
9130 None
9131 }
9132 }) else {
9133 return;
9134 };
9135
9136 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9137 return;
9138 };
9139
9140 breakpoint_store.update(cx, |breakpoint_store, cx| {
9141 breakpoint_store.toggle_breakpoint(
9142 buffer,
9143 (breakpoint_position.text_anchor, breakpoint),
9144 edit_action,
9145 cx,
9146 );
9147 });
9148
9149 cx.notify();
9150 }
9151
9152 #[cfg(any(test, feature = "test-support"))]
9153 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9154 self.breakpoint_store.clone()
9155 }
9156
9157 pub fn prepare_restore_change(
9158 &self,
9159 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9160 hunk: &MultiBufferDiffHunk,
9161 cx: &mut App,
9162 ) -> Option<()> {
9163 if hunk.is_created_file() {
9164 return None;
9165 }
9166 let buffer = self.buffer.read(cx);
9167 let diff = buffer.diff_for(hunk.buffer_id)?;
9168 let buffer = buffer.buffer(hunk.buffer_id)?;
9169 let buffer = buffer.read(cx);
9170 let original_text = diff
9171 .read(cx)
9172 .base_text()
9173 .as_rope()
9174 .slice(hunk.diff_base_byte_range.clone());
9175 let buffer_snapshot = buffer.snapshot();
9176 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9177 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9178 probe
9179 .0
9180 .start
9181 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9182 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9183 }) {
9184 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9185 Some(())
9186 } else {
9187 None
9188 }
9189 }
9190
9191 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9192 self.manipulate_lines(window, cx, |lines| lines.reverse())
9193 }
9194
9195 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9196 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9197 }
9198
9199 fn manipulate_lines<Fn>(
9200 &mut self,
9201 window: &mut Window,
9202 cx: &mut Context<Self>,
9203 mut callback: Fn,
9204 ) where
9205 Fn: FnMut(&mut Vec<&str>),
9206 {
9207 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9208
9209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9210 let buffer = self.buffer.read(cx).snapshot(cx);
9211
9212 let mut edits = Vec::new();
9213
9214 let selections = self.selections.all::<Point>(cx);
9215 let mut selections = selections.iter().peekable();
9216 let mut contiguous_row_selections = Vec::new();
9217 let mut new_selections = Vec::new();
9218 let mut added_lines = 0;
9219 let mut removed_lines = 0;
9220
9221 while let Some(selection) = selections.next() {
9222 let (start_row, end_row) = consume_contiguous_rows(
9223 &mut contiguous_row_selections,
9224 selection,
9225 &display_map,
9226 &mut selections,
9227 );
9228
9229 let start_point = Point::new(start_row.0, 0);
9230 let end_point = Point::new(
9231 end_row.previous_row().0,
9232 buffer.line_len(end_row.previous_row()),
9233 );
9234 let text = buffer
9235 .text_for_range(start_point..end_point)
9236 .collect::<String>();
9237
9238 let mut lines = text.split('\n').collect_vec();
9239
9240 let lines_before = lines.len();
9241 callback(&mut lines);
9242 let lines_after = lines.len();
9243
9244 edits.push((start_point..end_point, lines.join("\n")));
9245
9246 // Selections must change based on added and removed line count
9247 let start_row =
9248 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9249 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9250 new_selections.push(Selection {
9251 id: selection.id,
9252 start: start_row,
9253 end: end_row,
9254 goal: SelectionGoal::None,
9255 reversed: selection.reversed,
9256 });
9257
9258 if lines_after > lines_before {
9259 added_lines += lines_after - lines_before;
9260 } else if lines_before > lines_after {
9261 removed_lines += lines_before - lines_after;
9262 }
9263 }
9264
9265 self.transact(window, cx, |this, window, cx| {
9266 let buffer = this.buffer.update(cx, |buffer, cx| {
9267 buffer.edit(edits, None, cx);
9268 buffer.snapshot(cx)
9269 });
9270
9271 // Recalculate offsets on newly edited buffer
9272 let new_selections = new_selections
9273 .iter()
9274 .map(|s| {
9275 let start_point = Point::new(s.start.0, 0);
9276 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9277 Selection {
9278 id: s.id,
9279 start: buffer.point_to_offset(start_point),
9280 end: buffer.point_to_offset(end_point),
9281 goal: s.goal,
9282 reversed: s.reversed,
9283 }
9284 })
9285 .collect();
9286
9287 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9288 s.select(new_selections);
9289 });
9290
9291 this.request_autoscroll(Autoscroll::fit(), cx);
9292 });
9293 }
9294
9295 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9296 self.manipulate_text(window, cx, |text| {
9297 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9298 if has_upper_case_characters {
9299 text.to_lowercase()
9300 } else {
9301 text.to_uppercase()
9302 }
9303 })
9304 }
9305
9306 pub fn convert_to_upper_case(
9307 &mut self,
9308 _: &ConvertToUpperCase,
9309 window: &mut Window,
9310 cx: &mut Context<Self>,
9311 ) {
9312 self.manipulate_text(window, cx, |text| text.to_uppercase())
9313 }
9314
9315 pub fn convert_to_lower_case(
9316 &mut self,
9317 _: &ConvertToLowerCase,
9318 window: &mut Window,
9319 cx: &mut Context<Self>,
9320 ) {
9321 self.manipulate_text(window, cx, |text| text.to_lowercase())
9322 }
9323
9324 pub fn convert_to_title_case(
9325 &mut self,
9326 _: &ConvertToTitleCase,
9327 window: &mut Window,
9328 cx: &mut Context<Self>,
9329 ) {
9330 self.manipulate_text(window, cx, |text| {
9331 text.split('\n')
9332 .map(|line| line.to_case(Case::Title))
9333 .join("\n")
9334 })
9335 }
9336
9337 pub fn convert_to_snake_case(
9338 &mut self,
9339 _: &ConvertToSnakeCase,
9340 window: &mut Window,
9341 cx: &mut Context<Self>,
9342 ) {
9343 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9344 }
9345
9346 pub fn convert_to_kebab_case(
9347 &mut self,
9348 _: &ConvertToKebabCase,
9349 window: &mut Window,
9350 cx: &mut Context<Self>,
9351 ) {
9352 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9353 }
9354
9355 pub fn convert_to_upper_camel_case(
9356 &mut self,
9357 _: &ConvertToUpperCamelCase,
9358 window: &mut Window,
9359 cx: &mut Context<Self>,
9360 ) {
9361 self.manipulate_text(window, cx, |text| {
9362 text.split('\n')
9363 .map(|line| line.to_case(Case::UpperCamel))
9364 .join("\n")
9365 })
9366 }
9367
9368 pub fn convert_to_lower_camel_case(
9369 &mut self,
9370 _: &ConvertToLowerCamelCase,
9371 window: &mut Window,
9372 cx: &mut Context<Self>,
9373 ) {
9374 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9375 }
9376
9377 pub fn convert_to_opposite_case(
9378 &mut self,
9379 _: &ConvertToOppositeCase,
9380 window: &mut Window,
9381 cx: &mut Context<Self>,
9382 ) {
9383 self.manipulate_text(window, cx, |text| {
9384 text.chars()
9385 .fold(String::with_capacity(text.len()), |mut t, c| {
9386 if c.is_uppercase() {
9387 t.extend(c.to_lowercase());
9388 } else {
9389 t.extend(c.to_uppercase());
9390 }
9391 t
9392 })
9393 })
9394 }
9395
9396 pub fn convert_to_rot13(
9397 &mut self,
9398 _: &ConvertToRot13,
9399 window: &mut Window,
9400 cx: &mut Context<Self>,
9401 ) {
9402 self.manipulate_text(window, cx, |text| {
9403 text.chars()
9404 .map(|c| match c {
9405 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9406 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9407 _ => c,
9408 })
9409 .collect()
9410 })
9411 }
9412
9413 pub fn convert_to_rot47(
9414 &mut self,
9415 _: &ConvertToRot47,
9416 window: &mut Window,
9417 cx: &mut Context<Self>,
9418 ) {
9419 self.manipulate_text(window, cx, |text| {
9420 text.chars()
9421 .map(|c| {
9422 let code_point = c as u32;
9423 if code_point >= 33 && code_point <= 126 {
9424 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9425 }
9426 c
9427 })
9428 .collect()
9429 })
9430 }
9431
9432 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9433 where
9434 Fn: FnMut(&str) -> String,
9435 {
9436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9437 let buffer = self.buffer.read(cx).snapshot(cx);
9438
9439 let mut new_selections = Vec::new();
9440 let mut edits = Vec::new();
9441 let mut selection_adjustment = 0i32;
9442
9443 for selection in self.selections.all::<usize>(cx) {
9444 let selection_is_empty = selection.is_empty();
9445
9446 let (start, end) = if selection_is_empty {
9447 let word_range = movement::surrounding_word(
9448 &display_map,
9449 selection.start.to_display_point(&display_map),
9450 );
9451 let start = word_range.start.to_offset(&display_map, Bias::Left);
9452 let end = word_range.end.to_offset(&display_map, Bias::Left);
9453 (start, end)
9454 } else {
9455 (selection.start, selection.end)
9456 };
9457
9458 let text = buffer.text_for_range(start..end).collect::<String>();
9459 let old_length = text.len() as i32;
9460 let text = callback(&text);
9461
9462 new_selections.push(Selection {
9463 start: (start as i32 - selection_adjustment) as usize,
9464 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9465 goal: SelectionGoal::None,
9466 ..selection
9467 });
9468
9469 selection_adjustment += old_length - text.len() as i32;
9470
9471 edits.push((start..end, text));
9472 }
9473
9474 self.transact(window, cx, |this, window, cx| {
9475 this.buffer.update(cx, |buffer, cx| {
9476 buffer.edit(edits, None, cx);
9477 });
9478
9479 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9480 s.select(new_selections);
9481 });
9482
9483 this.request_autoscroll(Autoscroll::fit(), cx);
9484 });
9485 }
9486
9487 pub fn duplicate(
9488 &mut self,
9489 upwards: bool,
9490 whole_lines: bool,
9491 window: &mut Window,
9492 cx: &mut Context<Self>,
9493 ) {
9494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9495
9496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9497 let buffer = &display_map.buffer_snapshot;
9498 let selections = self.selections.all::<Point>(cx);
9499
9500 let mut edits = Vec::new();
9501 let mut selections_iter = selections.iter().peekable();
9502 while let Some(selection) = selections_iter.next() {
9503 let mut rows = selection.spanned_rows(false, &display_map);
9504 // duplicate line-wise
9505 if whole_lines || selection.start == selection.end {
9506 // Avoid duplicating the same lines twice.
9507 while let Some(next_selection) = selections_iter.peek() {
9508 let next_rows = next_selection.spanned_rows(false, &display_map);
9509 if next_rows.start < rows.end {
9510 rows.end = next_rows.end;
9511 selections_iter.next().unwrap();
9512 } else {
9513 break;
9514 }
9515 }
9516
9517 // Copy the text from the selected row region and splice it either at the start
9518 // or end of the region.
9519 let start = Point::new(rows.start.0, 0);
9520 let end = Point::new(
9521 rows.end.previous_row().0,
9522 buffer.line_len(rows.end.previous_row()),
9523 );
9524 let text = buffer
9525 .text_for_range(start..end)
9526 .chain(Some("\n"))
9527 .collect::<String>();
9528 let insert_location = if upwards {
9529 Point::new(rows.end.0, 0)
9530 } else {
9531 start
9532 };
9533 edits.push((insert_location..insert_location, text));
9534 } else {
9535 // duplicate character-wise
9536 let start = selection.start;
9537 let end = selection.end;
9538 let text = buffer.text_for_range(start..end).collect::<String>();
9539 edits.push((selection.end..selection.end, text));
9540 }
9541 }
9542
9543 self.transact(window, cx, |this, _, cx| {
9544 this.buffer.update(cx, |buffer, cx| {
9545 buffer.edit(edits, None, cx);
9546 });
9547
9548 this.request_autoscroll(Autoscroll::fit(), cx);
9549 });
9550 }
9551
9552 pub fn duplicate_line_up(
9553 &mut self,
9554 _: &DuplicateLineUp,
9555 window: &mut Window,
9556 cx: &mut Context<Self>,
9557 ) {
9558 self.duplicate(true, true, window, cx);
9559 }
9560
9561 pub fn duplicate_line_down(
9562 &mut self,
9563 _: &DuplicateLineDown,
9564 window: &mut Window,
9565 cx: &mut Context<Self>,
9566 ) {
9567 self.duplicate(false, true, window, cx);
9568 }
9569
9570 pub fn duplicate_selection(
9571 &mut self,
9572 _: &DuplicateSelection,
9573 window: &mut Window,
9574 cx: &mut Context<Self>,
9575 ) {
9576 self.duplicate(false, false, window, cx);
9577 }
9578
9579 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9580 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9581
9582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9583 let buffer = self.buffer.read(cx).snapshot(cx);
9584
9585 let mut edits = Vec::new();
9586 let mut unfold_ranges = Vec::new();
9587 let mut refold_creases = Vec::new();
9588
9589 let selections = self.selections.all::<Point>(cx);
9590 let mut selections = selections.iter().peekable();
9591 let mut contiguous_row_selections = Vec::new();
9592 let mut new_selections = Vec::new();
9593
9594 while let Some(selection) = selections.next() {
9595 // Find all the selections that span a contiguous row range
9596 let (start_row, end_row) = consume_contiguous_rows(
9597 &mut contiguous_row_selections,
9598 selection,
9599 &display_map,
9600 &mut selections,
9601 );
9602
9603 // Move the text spanned by the row range to be before the line preceding the row range
9604 if start_row.0 > 0 {
9605 let range_to_move = Point::new(
9606 start_row.previous_row().0,
9607 buffer.line_len(start_row.previous_row()),
9608 )
9609 ..Point::new(
9610 end_row.previous_row().0,
9611 buffer.line_len(end_row.previous_row()),
9612 );
9613 let insertion_point = display_map
9614 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
9615 .0;
9616
9617 // Don't move lines across excerpts
9618 if buffer
9619 .excerpt_containing(insertion_point..range_to_move.end)
9620 .is_some()
9621 {
9622 let text = buffer
9623 .text_for_range(range_to_move.clone())
9624 .flat_map(|s| s.chars())
9625 .skip(1)
9626 .chain(['\n'])
9627 .collect::<String>();
9628
9629 edits.push((
9630 buffer.anchor_after(range_to_move.start)
9631 ..buffer.anchor_before(range_to_move.end),
9632 String::new(),
9633 ));
9634 let insertion_anchor = buffer.anchor_after(insertion_point);
9635 edits.push((insertion_anchor..insertion_anchor, text));
9636
9637 let row_delta = range_to_move.start.row - insertion_point.row + 1;
9638
9639 // Move selections up
9640 new_selections.extend(contiguous_row_selections.drain(..).map(
9641 |mut selection| {
9642 selection.start.row -= row_delta;
9643 selection.end.row -= row_delta;
9644 selection
9645 },
9646 ));
9647
9648 // Move folds up
9649 unfold_ranges.push(range_to_move.clone());
9650 for fold in display_map.folds_in_range(
9651 buffer.anchor_before(range_to_move.start)
9652 ..buffer.anchor_after(range_to_move.end),
9653 ) {
9654 let mut start = fold.range.start.to_point(&buffer);
9655 let mut end = fold.range.end.to_point(&buffer);
9656 start.row -= row_delta;
9657 end.row -= row_delta;
9658 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9659 }
9660 }
9661 }
9662
9663 // If we didn't move line(s), preserve the existing selections
9664 new_selections.append(&mut contiguous_row_selections);
9665 }
9666
9667 self.transact(window, cx, |this, window, cx| {
9668 this.unfold_ranges(&unfold_ranges, true, true, cx);
9669 this.buffer.update(cx, |buffer, cx| {
9670 for (range, text) in edits {
9671 buffer.edit([(range, text)], None, cx);
9672 }
9673 });
9674 this.fold_creases(refold_creases, true, window, cx);
9675 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9676 s.select(new_selections);
9677 })
9678 });
9679 }
9680
9681 pub fn move_line_down(
9682 &mut self,
9683 _: &MoveLineDown,
9684 window: &mut Window,
9685 cx: &mut Context<Self>,
9686 ) {
9687 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9688
9689 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9690 let buffer = self.buffer.read(cx).snapshot(cx);
9691
9692 let mut edits = Vec::new();
9693 let mut unfold_ranges = Vec::new();
9694 let mut refold_creases = Vec::new();
9695
9696 let selections = self.selections.all::<Point>(cx);
9697 let mut selections = selections.iter().peekable();
9698 let mut contiguous_row_selections = Vec::new();
9699 let mut new_selections = Vec::new();
9700
9701 while let Some(selection) = selections.next() {
9702 // Find all the selections that span a contiguous row range
9703 let (start_row, end_row) = consume_contiguous_rows(
9704 &mut contiguous_row_selections,
9705 selection,
9706 &display_map,
9707 &mut selections,
9708 );
9709
9710 // Move the text spanned by the row range to be after the last line of the row range
9711 if end_row.0 <= buffer.max_point().row {
9712 let range_to_move =
9713 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
9714 let insertion_point = display_map
9715 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
9716 .0;
9717
9718 // Don't move lines across excerpt boundaries
9719 if buffer
9720 .excerpt_containing(range_to_move.start..insertion_point)
9721 .is_some()
9722 {
9723 let mut text = String::from("\n");
9724 text.extend(buffer.text_for_range(range_to_move.clone()));
9725 text.pop(); // Drop trailing newline
9726 edits.push((
9727 buffer.anchor_after(range_to_move.start)
9728 ..buffer.anchor_before(range_to_move.end),
9729 String::new(),
9730 ));
9731 let insertion_anchor = buffer.anchor_after(insertion_point);
9732 edits.push((insertion_anchor..insertion_anchor, text));
9733
9734 let row_delta = insertion_point.row - range_to_move.end.row + 1;
9735
9736 // Move selections down
9737 new_selections.extend(contiguous_row_selections.drain(..).map(
9738 |mut selection| {
9739 selection.start.row += row_delta;
9740 selection.end.row += row_delta;
9741 selection
9742 },
9743 ));
9744
9745 // Move folds down
9746 unfold_ranges.push(range_to_move.clone());
9747 for fold in display_map.folds_in_range(
9748 buffer.anchor_before(range_to_move.start)
9749 ..buffer.anchor_after(range_to_move.end),
9750 ) {
9751 let mut start = fold.range.start.to_point(&buffer);
9752 let mut end = fold.range.end.to_point(&buffer);
9753 start.row += row_delta;
9754 end.row += row_delta;
9755 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
9756 }
9757 }
9758 }
9759
9760 // If we didn't move line(s), preserve the existing selections
9761 new_selections.append(&mut contiguous_row_selections);
9762 }
9763
9764 self.transact(window, cx, |this, window, cx| {
9765 this.unfold_ranges(&unfold_ranges, true, true, cx);
9766 this.buffer.update(cx, |buffer, cx| {
9767 for (range, text) in edits {
9768 buffer.edit([(range, text)], None, cx);
9769 }
9770 });
9771 this.fold_creases(refold_creases, true, window, cx);
9772 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9773 s.select(new_selections)
9774 });
9775 });
9776 }
9777
9778 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
9779 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9780 let text_layout_details = &self.text_layout_details(window);
9781 self.transact(window, cx, |this, window, cx| {
9782 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9783 let mut edits: Vec<(Range<usize>, String)> = Default::default();
9784 s.move_with(|display_map, selection| {
9785 if !selection.is_empty() {
9786 return;
9787 }
9788
9789 let mut head = selection.head();
9790 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
9791 if head.column() == display_map.line_len(head.row()) {
9792 transpose_offset = display_map
9793 .buffer_snapshot
9794 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9795 }
9796
9797 if transpose_offset == 0 {
9798 return;
9799 }
9800
9801 *head.column_mut() += 1;
9802 head = display_map.clip_point(head, Bias::Right);
9803 let goal = SelectionGoal::HorizontalPosition(
9804 display_map
9805 .x_for_display_point(head, text_layout_details)
9806 .into(),
9807 );
9808 selection.collapse_to(head, goal);
9809
9810 let transpose_start = display_map
9811 .buffer_snapshot
9812 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
9813 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
9814 let transpose_end = display_map
9815 .buffer_snapshot
9816 .clip_offset(transpose_offset + 1, Bias::Right);
9817 if let Some(ch) =
9818 display_map.buffer_snapshot.chars_at(transpose_start).next()
9819 {
9820 edits.push((transpose_start..transpose_offset, String::new()));
9821 edits.push((transpose_end..transpose_end, ch.to_string()));
9822 }
9823 }
9824 });
9825 edits
9826 });
9827 this.buffer
9828 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
9829 let selections = this.selections.all::<usize>(cx);
9830 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9831 s.select(selections);
9832 });
9833 });
9834 }
9835
9836 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
9837 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9838 self.rewrap_impl(RewrapOptions::default(), cx)
9839 }
9840
9841 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
9842 let buffer = self.buffer.read(cx).snapshot(cx);
9843 let selections = self.selections.all::<Point>(cx);
9844 let mut selections = selections.iter().peekable();
9845
9846 let mut edits = Vec::new();
9847 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
9848
9849 while let Some(selection) = selections.next() {
9850 let mut start_row = selection.start.row;
9851 let mut end_row = selection.end.row;
9852
9853 // Skip selections that overlap with a range that has already been rewrapped.
9854 let selection_range = start_row..end_row;
9855 if rewrapped_row_ranges
9856 .iter()
9857 .any(|range| range.overlaps(&selection_range))
9858 {
9859 continue;
9860 }
9861
9862 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
9863
9864 // Since not all lines in the selection may be at the same indent
9865 // level, choose the indent size that is the most common between all
9866 // of the lines.
9867 //
9868 // If there is a tie, we use the deepest indent.
9869 let (indent_size, indent_end) = {
9870 let mut indent_size_occurrences = HashMap::default();
9871 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
9872
9873 for row in start_row..=end_row {
9874 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
9875 rows_by_indent_size.entry(indent).or_default().push(row);
9876 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
9877 }
9878
9879 let indent_size = indent_size_occurrences
9880 .into_iter()
9881 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
9882 .map(|(indent, _)| indent)
9883 .unwrap_or_default();
9884 let row = rows_by_indent_size[&indent_size][0];
9885 let indent_end = Point::new(row, indent_size.len);
9886
9887 (indent_size, indent_end)
9888 };
9889
9890 let mut line_prefix = indent_size.chars().collect::<String>();
9891
9892 let mut inside_comment = false;
9893 if let Some(comment_prefix) =
9894 buffer
9895 .language_scope_at(selection.head())
9896 .and_then(|language| {
9897 language
9898 .line_comment_prefixes()
9899 .iter()
9900 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
9901 .cloned()
9902 })
9903 {
9904 line_prefix.push_str(&comment_prefix);
9905 inside_comment = true;
9906 }
9907
9908 let language_settings = buffer.language_settings_at(selection.head(), cx);
9909 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
9910 RewrapBehavior::InComments => inside_comment,
9911 RewrapBehavior::InSelections => !selection.is_empty(),
9912 RewrapBehavior::Anywhere => true,
9913 };
9914
9915 let should_rewrap = options.override_language_settings
9916 || allow_rewrap_based_on_language
9917 || self.hard_wrap.is_some();
9918 if !should_rewrap {
9919 continue;
9920 }
9921
9922 if selection.is_empty() {
9923 'expand_upwards: while start_row > 0 {
9924 let prev_row = start_row - 1;
9925 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
9926 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
9927 {
9928 start_row = prev_row;
9929 } else {
9930 break 'expand_upwards;
9931 }
9932 }
9933
9934 'expand_downwards: while end_row < buffer.max_point().row {
9935 let next_row = end_row + 1;
9936 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
9937 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
9938 {
9939 end_row = next_row;
9940 } else {
9941 break 'expand_downwards;
9942 }
9943 }
9944 }
9945
9946 let start = Point::new(start_row, 0);
9947 let start_offset = start.to_offset(&buffer);
9948 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
9949 let selection_text = buffer.text_for_range(start..end).collect::<String>();
9950 let Some(lines_without_prefixes) = selection_text
9951 .lines()
9952 .map(|line| {
9953 line.strip_prefix(&line_prefix)
9954 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
9955 .ok_or_else(|| {
9956 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
9957 })
9958 })
9959 .collect::<Result<Vec<_>, _>>()
9960 .log_err()
9961 else {
9962 continue;
9963 };
9964
9965 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
9966 buffer
9967 .language_settings_at(Point::new(start_row, 0), cx)
9968 .preferred_line_length as usize
9969 });
9970 let wrapped_text = wrap_with_prefix(
9971 line_prefix,
9972 lines_without_prefixes.join("\n"),
9973 wrap_column,
9974 tab_size,
9975 options.preserve_existing_whitespace,
9976 );
9977
9978 // TODO: should always use char-based diff while still supporting cursor behavior that
9979 // matches vim.
9980 let mut diff_options = DiffOptions::default();
9981 if options.override_language_settings {
9982 diff_options.max_word_diff_len = 0;
9983 diff_options.max_word_diff_line_count = 0;
9984 } else {
9985 diff_options.max_word_diff_len = usize::MAX;
9986 diff_options.max_word_diff_line_count = usize::MAX;
9987 }
9988
9989 for (old_range, new_text) in
9990 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
9991 {
9992 let edit_start = buffer.anchor_after(start_offset + old_range.start);
9993 let edit_end = buffer.anchor_after(start_offset + old_range.end);
9994 edits.push((edit_start..edit_end, new_text));
9995 }
9996
9997 rewrapped_row_ranges.push(start_row..=end_row);
9998 }
9999
10000 self.buffer
10001 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10002 }
10003
10004 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10005 let mut text = String::new();
10006 let buffer = self.buffer.read(cx).snapshot(cx);
10007 let mut selections = self.selections.all::<Point>(cx);
10008 let mut clipboard_selections = Vec::with_capacity(selections.len());
10009 {
10010 let max_point = buffer.max_point();
10011 let mut is_first = true;
10012 for selection in &mut selections {
10013 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10014 if is_entire_line {
10015 selection.start = Point::new(selection.start.row, 0);
10016 if !selection.is_empty() && selection.end.column == 0 {
10017 selection.end = cmp::min(max_point, selection.end);
10018 } else {
10019 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10020 }
10021 selection.goal = SelectionGoal::None;
10022 }
10023 if is_first {
10024 is_first = false;
10025 } else {
10026 text += "\n";
10027 }
10028 let mut len = 0;
10029 for chunk in buffer.text_for_range(selection.start..selection.end) {
10030 text.push_str(chunk);
10031 len += chunk.len();
10032 }
10033 clipboard_selections.push(ClipboardSelection {
10034 len,
10035 is_entire_line,
10036 first_line_indent: buffer
10037 .indent_size_for_line(MultiBufferRow(selection.start.row))
10038 .len,
10039 });
10040 }
10041 }
10042
10043 self.transact(window, cx, |this, window, cx| {
10044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10045 s.select(selections);
10046 });
10047 this.insert("", window, cx);
10048 });
10049 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10050 }
10051
10052 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10053 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10054 let item = self.cut_common(window, cx);
10055 cx.write_to_clipboard(item);
10056 }
10057
10058 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10059 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10060 self.change_selections(None, window, cx, |s| {
10061 s.move_with(|snapshot, sel| {
10062 if sel.is_empty() {
10063 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10064 }
10065 });
10066 });
10067 let item = self.cut_common(window, cx);
10068 cx.set_global(KillRing(item))
10069 }
10070
10071 pub fn kill_ring_yank(
10072 &mut self,
10073 _: &KillRingYank,
10074 window: &mut Window,
10075 cx: &mut Context<Self>,
10076 ) {
10077 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10078 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10079 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10080 (kill_ring.text().to_string(), kill_ring.metadata_json())
10081 } else {
10082 return;
10083 }
10084 } else {
10085 return;
10086 };
10087 self.do_paste(&text, metadata, false, window, cx);
10088 }
10089
10090 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10091 self.do_copy(true, cx);
10092 }
10093
10094 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10095 self.do_copy(false, cx);
10096 }
10097
10098 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10099 let selections = self.selections.all::<Point>(cx);
10100 let buffer = self.buffer.read(cx).read(cx);
10101 let mut text = String::new();
10102
10103 let mut clipboard_selections = Vec::with_capacity(selections.len());
10104 {
10105 let max_point = buffer.max_point();
10106 let mut is_first = true;
10107 for selection in &selections {
10108 let mut start = selection.start;
10109 let mut end = selection.end;
10110 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10111 if is_entire_line {
10112 start = Point::new(start.row, 0);
10113 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10114 }
10115
10116 let mut trimmed_selections = Vec::new();
10117 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10118 let row = MultiBufferRow(start.row);
10119 let first_indent = buffer.indent_size_for_line(row);
10120 if first_indent.len == 0 || start.column > first_indent.len {
10121 trimmed_selections.push(start..end);
10122 } else {
10123 trimmed_selections.push(
10124 Point::new(row.0, first_indent.len)
10125 ..Point::new(row.0, buffer.line_len(row)),
10126 );
10127 for row in start.row + 1..=end.row {
10128 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10129 if row_indent_size.len >= first_indent.len {
10130 trimmed_selections.push(
10131 Point::new(row, first_indent.len)
10132 ..Point::new(row, buffer.line_len(MultiBufferRow(row))),
10133 );
10134 } else {
10135 trimmed_selections.clear();
10136 trimmed_selections.push(start..end);
10137 break;
10138 }
10139 }
10140 }
10141 } else {
10142 trimmed_selections.push(start..end);
10143 }
10144
10145 for trimmed_range in trimmed_selections {
10146 if is_first {
10147 is_first = false;
10148 } else {
10149 text += "\n";
10150 }
10151 let mut len = 0;
10152 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10153 text.push_str(chunk);
10154 len += chunk.len();
10155 }
10156 clipboard_selections.push(ClipboardSelection {
10157 len,
10158 is_entire_line,
10159 first_line_indent: buffer
10160 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10161 .len,
10162 });
10163 }
10164 }
10165 }
10166
10167 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10168 text,
10169 clipboard_selections,
10170 ));
10171 }
10172
10173 pub fn do_paste(
10174 &mut self,
10175 text: &String,
10176 clipboard_selections: Option<Vec<ClipboardSelection>>,
10177 handle_entire_lines: bool,
10178 window: &mut Window,
10179 cx: &mut Context<Self>,
10180 ) {
10181 if self.read_only(cx) {
10182 return;
10183 }
10184
10185 let clipboard_text = Cow::Borrowed(text);
10186
10187 self.transact(window, cx, |this, window, cx| {
10188 if let Some(mut clipboard_selections) = clipboard_selections {
10189 let old_selections = this.selections.all::<usize>(cx);
10190 let all_selections_were_entire_line =
10191 clipboard_selections.iter().all(|s| s.is_entire_line);
10192 let first_selection_indent_column =
10193 clipboard_selections.first().map(|s| s.first_line_indent);
10194 if clipboard_selections.len() != old_selections.len() {
10195 clipboard_selections.drain(..);
10196 }
10197 let cursor_offset = this.selections.last::<usize>(cx).head();
10198 let mut auto_indent_on_paste = true;
10199
10200 this.buffer.update(cx, |buffer, cx| {
10201 let snapshot = buffer.read(cx);
10202 auto_indent_on_paste = snapshot
10203 .language_settings_at(cursor_offset, cx)
10204 .auto_indent_on_paste;
10205
10206 let mut start_offset = 0;
10207 let mut edits = Vec::new();
10208 let mut original_indent_columns = Vec::new();
10209 for (ix, selection) in old_selections.iter().enumerate() {
10210 let to_insert;
10211 let entire_line;
10212 let original_indent_column;
10213 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10214 let end_offset = start_offset + clipboard_selection.len;
10215 to_insert = &clipboard_text[start_offset..end_offset];
10216 entire_line = clipboard_selection.is_entire_line;
10217 start_offset = end_offset + 1;
10218 original_indent_column = Some(clipboard_selection.first_line_indent);
10219 } else {
10220 to_insert = clipboard_text.as_str();
10221 entire_line = all_selections_were_entire_line;
10222 original_indent_column = first_selection_indent_column
10223 }
10224
10225 // If the corresponding selection was empty when this slice of the
10226 // clipboard text was written, then the entire line containing the
10227 // selection was copied. If this selection is also currently empty,
10228 // then paste the line before the current line of the buffer.
10229 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10230 let column = selection.start.to_point(&snapshot).column as usize;
10231 let line_start = selection.start - column;
10232 line_start..line_start
10233 } else {
10234 selection.range()
10235 };
10236
10237 edits.push((range, to_insert));
10238 original_indent_columns.push(original_indent_column);
10239 }
10240 drop(snapshot);
10241
10242 buffer.edit(
10243 edits,
10244 if auto_indent_on_paste {
10245 Some(AutoindentMode::Block {
10246 original_indent_columns,
10247 })
10248 } else {
10249 None
10250 },
10251 cx,
10252 );
10253 });
10254
10255 let selections = this.selections.all::<usize>(cx);
10256 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10257 s.select(selections)
10258 });
10259 } else {
10260 this.insert(&clipboard_text, window, cx);
10261 }
10262 });
10263 }
10264
10265 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10266 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10267 if let Some(item) = cx.read_from_clipboard() {
10268 let entries = item.entries();
10269
10270 match entries.first() {
10271 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10272 // of all the pasted entries.
10273 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10274 .do_paste(
10275 clipboard_string.text(),
10276 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10277 true,
10278 window,
10279 cx,
10280 ),
10281 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10282 }
10283 }
10284 }
10285
10286 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10287 if self.read_only(cx) {
10288 return;
10289 }
10290
10291 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10292
10293 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10294 if let Some((selections, _)) =
10295 self.selection_history.transaction(transaction_id).cloned()
10296 {
10297 self.change_selections(None, window, cx, |s| {
10298 s.select_anchors(selections.to_vec());
10299 });
10300 } else {
10301 log::error!(
10302 "No entry in selection_history found for undo. \
10303 This may correspond to a bug where undo does not update the selection. \
10304 If this is occurring, please add details to \
10305 https://github.com/zed-industries/zed/issues/22692"
10306 );
10307 }
10308 self.request_autoscroll(Autoscroll::fit(), cx);
10309 self.unmark_text(window, cx);
10310 self.refresh_inline_completion(true, false, window, cx);
10311 cx.emit(EditorEvent::Edited { transaction_id });
10312 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10313 }
10314 }
10315
10316 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10317 if self.read_only(cx) {
10318 return;
10319 }
10320
10321 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10322
10323 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10324 if let Some((_, Some(selections))) =
10325 self.selection_history.transaction(transaction_id).cloned()
10326 {
10327 self.change_selections(None, window, cx, |s| {
10328 s.select_anchors(selections.to_vec());
10329 });
10330 } else {
10331 log::error!(
10332 "No entry in selection_history found for redo. \
10333 This may correspond to a bug where undo does not update the selection. \
10334 If this is occurring, please add details to \
10335 https://github.com/zed-industries/zed/issues/22692"
10336 );
10337 }
10338 self.request_autoscroll(Autoscroll::fit(), cx);
10339 self.unmark_text(window, cx);
10340 self.refresh_inline_completion(true, false, window, cx);
10341 cx.emit(EditorEvent::Edited { transaction_id });
10342 }
10343 }
10344
10345 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10346 self.buffer
10347 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10348 }
10349
10350 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10351 self.buffer
10352 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10353 }
10354
10355 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10357 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10358 s.move_with(|map, selection| {
10359 let cursor = if selection.is_empty() {
10360 movement::left(map, selection.start)
10361 } else {
10362 selection.start
10363 };
10364 selection.collapse_to(cursor, SelectionGoal::None);
10365 });
10366 })
10367 }
10368
10369 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10370 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10372 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10373 })
10374 }
10375
10376 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10377 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10378 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10379 s.move_with(|map, selection| {
10380 let cursor = if selection.is_empty() {
10381 movement::right(map, selection.end)
10382 } else {
10383 selection.end
10384 };
10385 selection.collapse_to(cursor, SelectionGoal::None)
10386 });
10387 })
10388 }
10389
10390 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10391 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10392 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10393 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10394 })
10395 }
10396
10397 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10398 if self.take_rename(true, window, cx).is_some() {
10399 return;
10400 }
10401
10402 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10403 cx.propagate();
10404 return;
10405 }
10406
10407 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10408
10409 let text_layout_details = &self.text_layout_details(window);
10410 let selection_count = self.selections.count();
10411 let first_selection = self.selections.first_anchor();
10412
10413 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10414 s.move_with(|map, selection| {
10415 if !selection.is_empty() {
10416 selection.goal = SelectionGoal::None;
10417 }
10418 let (cursor, goal) = movement::up(
10419 map,
10420 selection.start,
10421 selection.goal,
10422 false,
10423 text_layout_details,
10424 );
10425 selection.collapse_to(cursor, goal);
10426 });
10427 });
10428
10429 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10430 {
10431 cx.propagate();
10432 }
10433 }
10434
10435 pub fn move_up_by_lines(
10436 &mut self,
10437 action: &MoveUpByLines,
10438 window: &mut Window,
10439 cx: &mut Context<Self>,
10440 ) {
10441 if self.take_rename(true, window, cx).is_some() {
10442 return;
10443 }
10444
10445 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10446 cx.propagate();
10447 return;
10448 }
10449
10450 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10451
10452 let text_layout_details = &self.text_layout_details(window);
10453
10454 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10455 s.move_with(|map, selection| {
10456 if !selection.is_empty() {
10457 selection.goal = SelectionGoal::None;
10458 }
10459 let (cursor, goal) = movement::up_by_rows(
10460 map,
10461 selection.start,
10462 action.lines,
10463 selection.goal,
10464 false,
10465 text_layout_details,
10466 );
10467 selection.collapse_to(cursor, goal);
10468 });
10469 })
10470 }
10471
10472 pub fn move_down_by_lines(
10473 &mut self,
10474 action: &MoveDownByLines,
10475 window: &mut Window,
10476 cx: &mut Context<Self>,
10477 ) {
10478 if self.take_rename(true, window, cx).is_some() {
10479 return;
10480 }
10481
10482 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10483 cx.propagate();
10484 return;
10485 }
10486
10487 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10488
10489 let text_layout_details = &self.text_layout_details(window);
10490
10491 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10492 s.move_with(|map, selection| {
10493 if !selection.is_empty() {
10494 selection.goal = SelectionGoal::None;
10495 }
10496 let (cursor, goal) = movement::down_by_rows(
10497 map,
10498 selection.start,
10499 action.lines,
10500 selection.goal,
10501 false,
10502 text_layout_details,
10503 );
10504 selection.collapse_to(cursor, goal);
10505 });
10506 })
10507 }
10508
10509 pub fn select_down_by_lines(
10510 &mut self,
10511 action: &SelectDownByLines,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10516 let text_layout_details = &self.text_layout_details(window);
10517 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10518 s.move_heads_with(|map, head, goal| {
10519 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10520 })
10521 })
10522 }
10523
10524 pub fn select_up_by_lines(
10525 &mut self,
10526 action: &SelectUpByLines,
10527 window: &mut Window,
10528 cx: &mut Context<Self>,
10529 ) {
10530 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10531 let text_layout_details = &self.text_layout_details(window);
10532 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10533 s.move_heads_with(|map, head, goal| {
10534 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10535 })
10536 })
10537 }
10538
10539 pub fn select_page_up(
10540 &mut self,
10541 _: &SelectPageUp,
10542 window: &mut Window,
10543 cx: &mut Context<Self>,
10544 ) {
10545 let Some(row_count) = self.visible_row_count() else {
10546 return;
10547 };
10548
10549 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10550
10551 let text_layout_details = &self.text_layout_details(window);
10552
10553 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10554 s.move_heads_with(|map, head, goal| {
10555 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10556 })
10557 })
10558 }
10559
10560 pub fn move_page_up(
10561 &mut self,
10562 action: &MovePageUp,
10563 window: &mut Window,
10564 cx: &mut Context<Self>,
10565 ) {
10566 if self.take_rename(true, window, cx).is_some() {
10567 return;
10568 }
10569
10570 if self
10571 .context_menu
10572 .borrow_mut()
10573 .as_mut()
10574 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10575 .unwrap_or(false)
10576 {
10577 return;
10578 }
10579
10580 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10581 cx.propagate();
10582 return;
10583 }
10584
10585 let Some(row_count) = self.visible_row_count() else {
10586 return;
10587 };
10588
10589 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10590
10591 let autoscroll = if action.center_cursor {
10592 Autoscroll::center()
10593 } else {
10594 Autoscroll::fit()
10595 };
10596
10597 let text_layout_details = &self.text_layout_details(window);
10598
10599 self.change_selections(Some(autoscroll), window, cx, |s| {
10600 s.move_with(|map, selection| {
10601 if !selection.is_empty() {
10602 selection.goal = SelectionGoal::None;
10603 }
10604 let (cursor, goal) = movement::up_by_rows(
10605 map,
10606 selection.end,
10607 row_count,
10608 selection.goal,
10609 false,
10610 text_layout_details,
10611 );
10612 selection.collapse_to(cursor, goal);
10613 });
10614 });
10615 }
10616
10617 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
10618 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10619 let text_layout_details = &self.text_layout_details(window);
10620 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10621 s.move_heads_with(|map, head, goal| {
10622 movement::up(map, head, goal, false, text_layout_details)
10623 })
10624 })
10625 }
10626
10627 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
10628 self.take_rename(true, window, cx);
10629
10630 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10631 cx.propagate();
10632 return;
10633 }
10634
10635 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10636
10637 let text_layout_details = &self.text_layout_details(window);
10638 let selection_count = self.selections.count();
10639 let first_selection = self.selections.first_anchor();
10640
10641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10642 s.move_with(|map, selection| {
10643 if !selection.is_empty() {
10644 selection.goal = SelectionGoal::None;
10645 }
10646 let (cursor, goal) = movement::down(
10647 map,
10648 selection.end,
10649 selection.goal,
10650 false,
10651 text_layout_details,
10652 );
10653 selection.collapse_to(cursor, goal);
10654 });
10655 });
10656
10657 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10658 {
10659 cx.propagate();
10660 }
10661 }
10662
10663 pub fn select_page_down(
10664 &mut self,
10665 _: &SelectPageDown,
10666 window: &mut Window,
10667 cx: &mut Context<Self>,
10668 ) {
10669 let Some(row_count) = self.visible_row_count() else {
10670 return;
10671 };
10672
10673 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10674
10675 let text_layout_details = &self.text_layout_details(window);
10676
10677 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10678 s.move_heads_with(|map, head, goal| {
10679 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
10680 })
10681 })
10682 }
10683
10684 pub fn move_page_down(
10685 &mut self,
10686 action: &MovePageDown,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 if self.take_rename(true, window, cx).is_some() {
10691 return;
10692 }
10693
10694 if self
10695 .context_menu
10696 .borrow_mut()
10697 .as_mut()
10698 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
10699 .unwrap_or(false)
10700 {
10701 return;
10702 }
10703
10704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10705 cx.propagate();
10706 return;
10707 }
10708
10709 let Some(row_count) = self.visible_row_count() else {
10710 return;
10711 };
10712
10713 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10714
10715 let autoscroll = if action.center_cursor {
10716 Autoscroll::center()
10717 } else {
10718 Autoscroll::fit()
10719 };
10720
10721 let text_layout_details = &self.text_layout_details(window);
10722 self.change_selections(Some(autoscroll), window, cx, |s| {
10723 s.move_with(|map, selection| {
10724 if !selection.is_empty() {
10725 selection.goal = SelectionGoal::None;
10726 }
10727 let (cursor, goal) = movement::down_by_rows(
10728 map,
10729 selection.end,
10730 row_count,
10731 selection.goal,
10732 false,
10733 text_layout_details,
10734 );
10735 selection.collapse_to(cursor, goal);
10736 });
10737 });
10738 }
10739
10740 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
10741 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10742 let text_layout_details = &self.text_layout_details(window);
10743 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10744 s.move_heads_with(|map, head, goal| {
10745 movement::down(map, head, goal, false, text_layout_details)
10746 })
10747 });
10748 }
10749
10750 pub fn context_menu_first(
10751 &mut self,
10752 _: &ContextMenuFirst,
10753 _window: &mut Window,
10754 cx: &mut Context<Self>,
10755 ) {
10756 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10757 context_menu.select_first(self.completion_provider.as_deref(), cx);
10758 }
10759 }
10760
10761 pub fn context_menu_prev(
10762 &mut self,
10763 _: &ContextMenuPrevious,
10764 _window: &mut Window,
10765 cx: &mut Context<Self>,
10766 ) {
10767 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10768 context_menu.select_prev(self.completion_provider.as_deref(), cx);
10769 }
10770 }
10771
10772 pub fn context_menu_next(
10773 &mut self,
10774 _: &ContextMenuNext,
10775 _window: &mut Window,
10776 cx: &mut Context<Self>,
10777 ) {
10778 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10779 context_menu.select_next(self.completion_provider.as_deref(), cx);
10780 }
10781 }
10782
10783 pub fn context_menu_last(
10784 &mut self,
10785 _: &ContextMenuLast,
10786 _window: &mut Window,
10787 cx: &mut Context<Self>,
10788 ) {
10789 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
10790 context_menu.select_last(self.completion_provider.as_deref(), cx);
10791 }
10792 }
10793
10794 pub fn move_to_previous_word_start(
10795 &mut self,
10796 _: &MoveToPreviousWordStart,
10797 window: &mut Window,
10798 cx: &mut Context<Self>,
10799 ) {
10800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10802 s.move_cursors_with(|map, head, _| {
10803 (
10804 movement::previous_word_start(map, head),
10805 SelectionGoal::None,
10806 )
10807 });
10808 })
10809 }
10810
10811 pub fn move_to_previous_subword_start(
10812 &mut self,
10813 _: &MoveToPreviousSubwordStart,
10814 window: &mut Window,
10815 cx: &mut Context<Self>,
10816 ) {
10817 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10818 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10819 s.move_cursors_with(|map, head, _| {
10820 (
10821 movement::previous_subword_start(map, head),
10822 SelectionGoal::None,
10823 )
10824 });
10825 })
10826 }
10827
10828 pub fn select_to_previous_word_start(
10829 &mut self,
10830 _: &SelectToPreviousWordStart,
10831 window: &mut Window,
10832 cx: &mut Context<Self>,
10833 ) {
10834 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10835 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10836 s.move_heads_with(|map, head, _| {
10837 (
10838 movement::previous_word_start(map, head),
10839 SelectionGoal::None,
10840 )
10841 });
10842 })
10843 }
10844
10845 pub fn select_to_previous_subword_start(
10846 &mut self,
10847 _: &SelectToPreviousSubwordStart,
10848 window: &mut Window,
10849 cx: &mut Context<Self>,
10850 ) {
10851 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10853 s.move_heads_with(|map, head, _| {
10854 (
10855 movement::previous_subword_start(map, head),
10856 SelectionGoal::None,
10857 )
10858 });
10859 })
10860 }
10861
10862 pub fn delete_to_previous_word_start(
10863 &mut self,
10864 action: &DeleteToPreviousWordStart,
10865 window: &mut Window,
10866 cx: &mut Context<Self>,
10867 ) {
10868 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10869 self.transact(window, cx, |this, window, cx| {
10870 this.select_autoclose_pair(window, cx);
10871 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10872 s.move_with(|map, selection| {
10873 if selection.is_empty() {
10874 let cursor = if action.ignore_newlines {
10875 movement::previous_word_start(map, selection.head())
10876 } else {
10877 movement::previous_word_start_or_newline(map, selection.head())
10878 };
10879 selection.set_head(cursor, SelectionGoal::None);
10880 }
10881 });
10882 });
10883 this.insert("", window, cx);
10884 });
10885 }
10886
10887 pub fn delete_to_previous_subword_start(
10888 &mut self,
10889 _: &DeleteToPreviousSubwordStart,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) {
10893 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10894 self.transact(window, cx, |this, window, cx| {
10895 this.select_autoclose_pair(window, cx);
10896 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10897 s.move_with(|map, selection| {
10898 if selection.is_empty() {
10899 let cursor = movement::previous_subword_start(map, selection.head());
10900 selection.set_head(cursor, SelectionGoal::None);
10901 }
10902 });
10903 });
10904 this.insert("", window, cx);
10905 });
10906 }
10907
10908 pub fn move_to_next_word_end(
10909 &mut self,
10910 _: &MoveToNextWordEnd,
10911 window: &mut Window,
10912 cx: &mut Context<Self>,
10913 ) {
10914 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10915 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10916 s.move_cursors_with(|map, head, _| {
10917 (movement::next_word_end(map, head), SelectionGoal::None)
10918 });
10919 })
10920 }
10921
10922 pub fn move_to_next_subword_end(
10923 &mut self,
10924 _: &MoveToNextSubwordEnd,
10925 window: &mut Window,
10926 cx: &mut Context<Self>,
10927 ) {
10928 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10929 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10930 s.move_cursors_with(|map, head, _| {
10931 (movement::next_subword_end(map, head), SelectionGoal::None)
10932 });
10933 })
10934 }
10935
10936 pub fn select_to_next_word_end(
10937 &mut self,
10938 _: &SelectToNextWordEnd,
10939 window: &mut Window,
10940 cx: &mut Context<Self>,
10941 ) {
10942 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10943 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10944 s.move_heads_with(|map, head, _| {
10945 (movement::next_word_end(map, head), SelectionGoal::None)
10946 });
10947 })
10948 }
10949
10950 pub fn select_to_next_subword_end(
10951 &mut self,
10952 _: &SelectToNextSubwordEnd,
10953 window: &mut Window,
10954 cx: &mut Context<Self>,
10955 ) {
10956 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10957 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10958 s.move_heads_with(|map, head, _| {
10959 (movement::next_subword_end(map, head), SelectionGoal::None)
10960 });
10961 })
10962 }
10963
10964 pub fn delete_to_next_word_end(
10965 &mut self,
10966 action: &DeleteToNextWordEnd,
10967 window: &mut Window,
10968 cx: &mut Context<Self>,
10969 ) {
10970 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10971 self.transact(window, cx, |this, window, cx| {
10972 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10973 s.move_with(|map, selection| {
10974 if selection.is_empty() {
10975 let cursor = if action.ignore_newlines {
10976 movement::next_word_end(map, selection.head())
10977 } else {
10978 movement::next_word_end_or_newline(map, selection.head())
10979 };
10980 selection.set_head(cursor, SelectionGoal::None);
10981 }
10982 });
10983 });
10984 this.insert("", window, cx);
10985 });
10986 }
10987
10988 pub fn delete_to_next_subword_end(
10989 &mut self,
10990 _: &DeleteToNextSubwordEnd,
10991 window: &mut Window,
10992 cx: &mut Context<Self>,
10993 ) {
10994 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10995 self.transact(window, cx, |this, window, cx| {
10996 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10997 s.move_with(|map, selection| {
10998 if selection.is_empty() {
10999 let cursor = movement::next_subword_end(map, selection.head());
11000 selection.set_head(cursor, SelectionGoal::None);
11001 }
11002 });
11003 });
11004 this.insert("", window, cx);
11005 });
11006 }
11007
11008 pub fn move_to_beginning_of_line(
11009 &mut self,
11010 action: &MoveToBeginningOfLine,
11011 window: &mut Window,
11012 cx: &mut Context<Self>,
11013 ) {
11014 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11016 s.move_cursors_with(|map, head, _| {
11017 (
11018 movement::indented_line_beginning(
11019 map,
11020 head,
11021 action.stop_at_soft_wraps,
11022 action.stop_at_indent,
11023 ),
11024 SelectionGoal::None,
11025 )
11026 });
11027 })
11028 }
11029
11030 pub fn select_to_beginning_of_line(
11031 &mut self,
11032 action: &SelectToBeginningOfLine,
11033 window: &mut Window,
11034 cx: &mut Context<Self>,
11035 ) {
11036 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11038 s.move_heads_with(|map, head, _| {
11039 (
11040 movement::indented_line_beginning(
11041 map,
11042 head,
11043 action.stop_at_soft_wraps,
11044 action.stop_at_indent,
11045 ),
11046 SelectionGoal::None,
11047 )
11048 });
11049 });
11050 }
11051
11052 pub fn delete_to_beginning_of_line(
11053 &mut self,
11054 action: &DeleteToBeginningOfLine,
11055 window: &mut Window,
11056 cx: &mut Context<Self>,
11057 ) {
11058 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11059 self.transact(window, cx, |this, window, cx| {
11060 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11061 s.move_with(|_, selection| {
11062 selection.reversed = true;
11063 });
11064 });
11065
11066 this.select_to_beginning_of_line(
11067 &SelectToBeginningOfLine {
11068 stop_at_soft_wraps: false,
11069 stop_at_indent: action.stop_at_indent,
11070 },
11071 window,
11072 cx,
11073 );
11074 this.backspace(&Backspace, window, cx);
11075 });
11076 }
11077
11078 pub fn move_to_end_of_line(
11079 &mut self,
11080 action: &MoveToEndOfLine,
11081 window: &mut Window,
11082 cx: &mut Context<Self>,
11083 ) {
11084 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11085 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11086 s.move_cursors_with(|map, head, _| {
11087 (
11088 movement::line_end(map, head, action.stop_at_soft_wraps),
11089 SelectionGoal::None,
11090 )
11091 });
11092 })
11093 }
11094
11095 pub fn select_to_end_of_line(
11096 &mut self,
11097 action: &SelectToEndOfLine,
11098 window: &mut Window,
11099 cx: &mut Context<Self>,
11100 ) {
11101 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11102 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11103 s.move_heads_with(|map, head, _| {
11104 (
11105 movement::line_end(map, head, action.stop_at_soft_wraps),
11106 SelectionGoal::None,
11107 )
11108 });
11109 })
11110 }
11111
11112 pub fn delete_to_end_of_line(
11113 &mut self,
11114 _: &DeleteToEndOfLine,
11115 window: &mut Window,
11116 cx: &mut Context<Self>,
11117 ) {
11118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11119 self.transact(window, cx, |this, window, cx| {
11120 this.select_to_end_of_line(
11121 &SelectToEndOfLine {
11122 stop_at_soft_wraps: false,
11123 },
11124 window,
11125 cx,
11126 );
11127 this.delete(&Delete, window, cx);
11128 });
11129 }
11130
11131 pub fn cut_to_end_of_line(
11132 &mut self,
11133 _: &CutToEndOfLine,
11134 window: &mut Window,
11135 cx: &mut Context<Self>,
11136 ) {
11137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11138 self.transact(window, cx, |this, window, cx| {
11139 this.select_to_end_of_line(
11140 &SelectToEndOfLine {
11141 stop_at_soft_wraps: false,
11142 },
11143 window,
11144 cx,
11145 );
11146 this.cut(&Cut, window, cx);
11147 });
11148 }
11149
11150 pub fn move_to_start_of_paragraph(
11151 &mut self,
11152 _: &MoveToStartOfParagraph,
11153 window: &mut Window,
11154 cx: &mut Context<Self>,
11155 ) {
11156 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11157 cx.propagate();
11158 return;
11159 }
11160 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11161 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11162 s.move_with(|map, selection| {
11163 selection.collapse_to(
11164 movement::start_of_paragraph(map, selection.head(), 1),
11165 SelectionGoal::None,
11166 )
11167 });
11168 })
11169 }
11170
11171 pub fn move_to_end_of_paragraph(
11172 &mut self,
11173 _: &MoveToEndOfParagraph,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11178 cx.propagate();
11179 return;
11180 }
11181 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11183 s.move_with(|map, selection| {
11184 selection.collapse_to(
11185 movement::end_of_paragraph(map, selection.head(), 1),
11186 SelectionGoal::None,
11187 )
11188 });
11189 })
11190 }
11191
11192 pub fn select_to_start_of_paragraph(
11193 &mut self,
11194 _: &SelectToStartOfParagraph,
11195 window: &mut Window,
11196 cx: &mut Context<Self>,
11197 ) {
11198 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11199 cx.propagate();
11200 return;
11201 }
11202 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11203 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11204 s.move_heads_with(|map, head, _| {
11205 (
11206 movement::start_of_paragraph(map, head, 1),
11207 SelectionGoal::None,
11208 )
11209 });
11210 })
11211 }
11212
11213 pub fn select_to_end_of_paragraph(
11214 &mut self,
11215 _: &SelectToEndOfParagraph,
11216 window: &mut Window,
11217 cx: &mut Context<Self>,
11218 ) {
11219 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11220 cx.propagate();
11221 return;
11222 }
11223 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11225 s.move_heads_with(|map, head, _| {
11226 (
11227 movement::end_of_paragraph(map, head, 1),
11228 SelectionGoal::None,
11229 )
11230 });
11231 })
11232 }
11233
11234 pub fn move_to_start_of_excerpt(
11235 &mut self,
11236 _: &MoveToStartOfExcerpt,
11237 window: &mut Window,
11238 cx: &mut Context<Self>,
11239 ) {
11240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11241 cx.propagate();
11242 return;
11243 }
11244 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11245 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11246 s.move_with(|map, selection| {
11247 selection.collapse_to(
11248 movement::start_of_excerpt(
11249 map,
11250 selection.head(),
11251 workspace::searchable::Direction::Prev,
11252 ),
11253 SelectionGoal::None,
11254 )
11255 });
11256 })
11257 }
11258
11259 pub fn move_to_start_of_next_excerpt(
11260 &mut self,
11261 _: &MoveToStartOfNextExcerpt,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11266 cx.propagate();
11267 return;
11268 }
11269
11270 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11271 s.move_with(|map, selection| {
11272 selection.collapse_to(
11273 movement::start_of_excerpt(
11274 map,
11275 selection.head(),
11276 workspace::searchable::Direction::Next,
11277 ),
11278 SelectionGoal::None,
11279 )
11280 });
11281 })
11282 }
11283
11284 pub fn move_to_end_of_excerpt(
11285 &mut self,
11286 _: &MoveToEndOfExcerpt,
11287 window: &mut Window,
11288 cx: &mut Context<Self>,
11289 ) {
11290 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11291 cx.propagate();
11292 return;
11293 }
11294 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11295 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11296 s.move_with(|map, selection| {
11297 selection.collapse_to(
11298 movement::end_of_excerpt(
11299 map,
11300 selection.head(),
11301 workspace::searchable::Direction::Next,
11302 ),
11303 SelectionGoal::None,
11304 )
11305 });
11306 })
11307 }
11308
11309 pub fn move_to_end_of_previous_excerpt(
11310 &mut self,
11311 _: &MoveToEndOfPreviousExcerpt,
11312 window: &mut Window,
11313 cx: &mut Context<Self>,
11314 ) {
11315 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11316 cx.propagate();
11317 return;
11318 }
11319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11320 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11321 s.move_with(|map, selection| {
11322 selection.collapse_to(
11323 movement::end_of_excerpt(
11324 map,
11325 selection.head(),
11326 workspace::searchable::Direction::Prev,
11327 ),
11328 SelectionGoal::None,
11329 )
11330 });
11331 })
11332 }
11333
11334 pub fn select_to_start_of_excerpt(
11335 &mut self,
11336 _: &SelectToStartOfExcerpt,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11341 cx.propagate();
11342 return;
11343 }
11344 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11345 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11346 s.move_heads_with(|map, head, _| {
11347 (
11348 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11349 SelectionGoal::None,
11350 )
11351 });
11352 })
11353 }
11354
11355 pub fn select_to_start_of_next_excerpt(
11356 &mut self,
11357 _: &SelectToStartOfNextExcerpt,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 ) {
11361 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11362 cx.propagate();
11363 return;
11364 }
11365 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11367 s.move_heads_with(|map, head, _| {
11368 (
11369 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11370 SelectionGoal::None,
11371 )
11372 });
11373 })
11374 }
11375
11376 pub fn select_to_end_of_excerpt(
11377 &mut self,
11378 _: &SelectToEndOfExcerpt,
11379 window: &mut Window,
11380 cx: &mut Context<Self>,
11381 ) {
11382 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11383 cx.propagate();
11384 return;
11385 }
11386 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11387 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11388 s.move_heads_with(|map, head, _| {
11389 (
11390 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11391 SelectionGoal::None,
11392 )
11393 });
11394 })
11395 }
11396
11397 pub fn select_to_end_of_previous_excerpt(
11398 &mut self,
11399 _: &SelectToEndOfPreviousExcerpt,
11400 window: &mut Window,
11401 cx: &mut Context<Self>,
11402 ) {
11403 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11404 cx.propagate();
11405 return;
11406 }
11407 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11408 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11409 s.move_heads_with(|map, head, _| {
11410 (
11411 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11412 SelectionGoal::None,
11413 )
11414 });
11415 })
11416 }
11417
11418 pub fn move_to_beginning(
11419 &mut self,
11420 _: &MoveToBeginning,
11421 window: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) {
11424 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11425 cx.propagate();
11426 return;
11427 }
11428 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11429 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11430 s.select_ranges(vec![0..0]);
11431 });
11432 }
11433
11434 pub fn select_to_beginning(
11435 &mut self,
11436 _: &SelectToBeginning,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 let mut selection = self.selections.last::<Point>(cx);
11441 selection.set_head(Point::zero(), SelectionGoal::None);
11442 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11443 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11444 s.select(vec![selection]);
11445 });
11446 }
11447
11448 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11449 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11450 cx.propagate();
11451 return;
11452 }
11453 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11454 let cursor = self.buffer.read(cx).read(cx).len();
11455 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11456 s.select_ranges(vec![cursor..cursor])
11457 });
11458 }
11459
11460 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11461 self.nav_history = nav_history;
11462 }
11463
11464 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11465 self.nav_history.as_ref()
11466 }
11467
11468 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11469 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11470 }
11471
11472 fn push_to_nav_history(
11473 &mut self,
11474 cursor_anchor: Anchor,
11475 new_position: Option<Point>,
11476 is_deactivate: bool,
11477 cx: &mut Context<Self>,
11478 ) {
11479 if let Some(nav_history) = self.nav_history.as_mut() {
11480 let buffer = self.buffer.read(cx).read(cx);
11481 let cursor_position = cursor_anchor.to_point(&buffer);
11482 let scroll_state = self.scroll_manager.anchor();
11483 let scroll_top_row = scroll_state.top_row(&buffer);
11484 drop(buffer);
11485
11486 if let Some(new_position) = new_position {
11487 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11488 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11489 return;
11490 }
11491 }
11492
11493 nav_history.push(
11494 Some(NavigationData {
11495 cursor_anchor,
11496 cursor_position,
11497 scroll_anchor: scroll_state,
11498 scroll_top_row,
11499 }),
11500 cx,
11501 );
11502 cx.emit(EditorEvent::PushedToNavHistory {
11503 anchor: cursor_anchor,
11504 is_deactivate,
11505 })
11506 }
11507 }
11508
11509 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11511 let buffer = self.buffer.read(cx).snapshot(cx);
11512 let mut selection = self.selections.first::<usize>(cx);
11513 selection.set_head(buffer.len(), SelectionGoal::None);
11514 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11515 s.select(vec![selection]);
11516 });
11517 }
11518
11519 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11520 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11521 let end = self.buffer.read(cx).read(cx).len();
11522 self.change_selections(None, window, cx, |s| {
11523 s.select_ranges(vec![0..end]);
11524 });
11525 }
11526
11527 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11528 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11530 let mut selections = self.selections.all::<Point>(cx);
11531 let max_point = display_map.buffer_snapshot.max_point();
11532 for selection in &mut selections {
11533 let rows = selection.spanned_rows(true, &display_map);
11534 selection.start = Point::new(rows.start.0, 0);
11535 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11536 selection.reversed = false;
11537 }
11538 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11539 s.select(selections);
11540 });
11541 }
11542
11543 pub fn split_selection_into_lines(
11544 &mut self,
11545 _: &SplitSelectionIntoLines,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 let selections = self
11550 .selections
11551 .all::<Point>(cx)
11552 .into_iter()
11553 .map(|selection| selection.start..selection.end)
11554 .collect::<Vec<_>>();
11555 self.unfold_ranges(&selections, true, true, cx);
11556
11557 let mut new_selection_ranges = Vec::new();
11558 {
11559 let buffer = self.buffer.read(cx).read(cx);
11560 for selection in selections {
11561 for row in selection.start.row..selection.end.row {
11562 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11563 new_selection_ranges.push(cursor..cursor);
11564 }
11565
11566 let is_multiline_selection = selection.start.row != selection.end.row;
11567 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11568 // so this action feels more ergonomic when paired with other selection operations
11569 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11570 if !should_skip_last {
11571 new_selection_ranges.push(selection.end..selection.end);
11572 }
11573 }
11574 }
11575 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11576 s.select_ranges(new_selection_ranges);
11577 });
11578 }
11579
11580 pub fn add_selection_above(
11581 &mut self,
11582 _: &AddSelectionAbove,
11583 window: &mut Window,
11584 cx: &mut Context<Self>,
11585 ) {
11586 self.add_selection(true, window, cx);
11587 }
11588
11589 pub fn add_selection_below(
11590 &mut self,
11591 _: &AddSelectionBelow,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) {
11595 self.add_selection(false, window, cx);
11596 }
11597
11598 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
11599 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11600
11601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11602 let mut selections = self.selections.all::<Point>(cx);
11603 let text_layout_details = self.text_layout_details(window);
11604 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
11605 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
11606 let range = oldest_selection.display_range(&display_map).sorted();
11607
11608 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
11609 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
11610 let positions = start_x.min(end_x)..start_x.max(end_x);
11611
11612 selections.clear();
11613 let mut stack = Vec::new();
11614 for row in range.start.row().0..=range.end.row().0 {
11615 if let Some(selection) = self.selections.build_columnar_selection(
11616 &display_map,
11617 DisplayRow(row),
11618 &positions,
11619 oldest_selection.reversed,
11620 &text_layout_details,
11621 ) {
11622 stack.push(selection.id);
11623 selections.push(selection);
11624 }
11625 }
11626
11627 if above {
11628 stack.reverse();
11629 }
11630
11631 AddSelectionsState { above, stack }
11632 });
11633
11634 let last_added_selection = *state.stack.last().unwrap();
11635 let mut new_selections = Vec::new();
11636 if above == state.above {
11637 let end_row = if above {
11638 DisplayRow(0)
11639 } else {
11640 display_map.max_point().row()
11641 };
11642
11643 'outer: for selection in selections {
11644 if selection.id == last_added_selection {
11645 let range = selection.display_range(&display_map).sorted();
11646 debug_assert_eq!(range.start.row(), range.end.row());
11647 let mut row = range.start.row();
11648 let positions =
11649 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
11650 px(start)..px(end)
11651 } else {
11652 let start_x =
11653 display_map.x_for_display_point(range.start, &text_layout_details);
11654 let end_x =
11655 display_map.x_for_display_point(range.end, &text_layout_details);
11656 start_x.min(end_x)..start_x.max(end_x)
11657 };
11658
11659 while row != end_row {
11660 if above {
11661 row.0 -= 1;
11662 } else {
11663 row.0 += 1;
11664 }
11665
11666 if let Some(new_selection) = self.selections.build_columnar_selection(
11667 &display_map,
11668 row,
11669 &positions,
11670 selection.reversed,
11671 &text_layout_details,
11672 ) {
11673 state.stack.push(new_selection.id);
11674 if above {
11675 new_selections.push(new_selection);
11676 new_selections.push(selection);
11677 } else {
11678 new_selections.push(selection);
11679 new_selections.push(new_selection);
11680 }
11681
11682 continue 'outer;
11683 }
11684 }
11685 }
11686
11687 new_selections.push(selection);
11688 }
11689 } else {
11690 new_selections = selections;
11691 new_selections.retain(|s| s.id != last_added_selection);
11692 state.stack.pop();
11693 }
11694
11695 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11696 s.select(new_selections);
11697 });
11698 if state.stack.len() > 1 {
11699 self.add_selections_state = Some(state);
11700 }
11701 }
11702
11703 pub fn select_next_match_internal(
11704 &mut self,
11705 display_map: &DisplaySnapshot,
11706 replace_newest: bool,
11707 autoscroll: Option<Autoscroll>,
11708 window: &mut Window,
11709 cx: &mut Context<Self>,
11710 ) -> Result<()> {
11711 fn select_next_match_ranges(
11712 this: &mut Editor,
11713 range: Range<usize>,
11714 replace_newest: bool,
11715 auto_scroll: Option<Autoscroll>,
11716 window: &mut Window,
11717 cx: &mut Context<Editor>,
11718 ) {
11719 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
11720 this.change_selections(auto_scroll, window, cx, |s| {
11721 if replace_newest {
11722 s.delete(s.newest_anchor().id);
11723 }
11724 s.insert_range(range.clone());
11725 });
11726 }
11727
11728 let buffer = &display_map.buffer_snapshot;
11729 let mut selections = self.selections.all::<usize>(cx);
11730 if let Some(mut select_next_state) = self.select_next_state.take() {
11731 let query = &select_next_state.query;
11732 if !select_next_state.done {
11733 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11734 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11735 let mut next_selected_range = None;
11736
11737 let bytes_after_last_selection =
11738 buffer.bytes_in_range(last_selection.end..buffer.len());
11739 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
11740 let query_matches = query
11741 .stream_find_iter(bytes_after_last_selection)
11742 .map(|result| (last_selection.end, result))
11743 .chain(
11744 query
11745 .stream_find_iter(bytes_before_first_selection)
11746 .map(|result| (0, result)),
11747 );
11748
11749 for (start_offset, query_match) in query_matches {
11750 let query_match = query_match.unwrap(); // can only fail due to I/O
11751 let offset_range =
11752 start_offset + query_match.start()..start_offset + query_match.end();
11753 let display_range = offset_range.start.to_display_point(display_map)
11754 ..offset_range.end.to_display_point(display_map);
11755
11756 if !select_next_state.wordwise
11757 || (!movement::is_inside_word(display_map, display_range.start)
11758 && !movement::is_inside_word(display_map, display_range.end))
11759 {
11760 // TODO: This is n^2, because we might check all the selections
11761 if !selections
11762 .iter()
11763 .any(|selection| selection.range().overlaps(&offset_range))
11764 {
11765 next_selected_range = Some(offset_range);
11766 break;
11767 }
11768 }
11769 }
11770
11771 if let Some(next_selected_range) = next_selected_range {
11772 select_next_match_ranges(
11773 self,
11774 next_selected_range,
11775 replace_newest,
11776 autoscroll,
11777 window,
11778 cx,
11779 );
11780 } else {
11781 select_next_state.done = true;
11782 }
11783 }
11784
11785 self.select_next_state = Some(select_next_state);
11786 } else {
11787 let mut only_carets = true;
11788 let mut same_text_selected = true;
11789 let mut selected_text = None;
11790
11791 let mut selections_iter = selections.iter().peekable();
11792 while let Some(selection) = selections_iter.next() {
11793 if selection.start != selection.end {
11794 only_carets = false;
11795 }
11796
11797 if same_text_selected {
11798 if selected_text.is_none() {
11799 selected_text =
11800 Some(buffer.text_for_range(selection.range()).collect::<String>());
11801 }
11802
11803 if let Some(next_selection) = selections_iter.peek() {
11804 if next_selection.range().len() == selection.range().len() {
11805 let next_selected_text = buffer
11806 .text_for_range(next_selection.range())
11807 .collect::<String>();
11808 if Some(next_selected_text) != selected_text {
11809 same_text_selected = false;
11810 selected_text = None;
11811 }
11812 } else {
11813 same_text_selected = false;
11814 selected_text = None;
11815 }
11816 }
11817 }
11818 }
11819
11820 if only_carets {
11821 for selection in &mut selections {
11822 let word_range = movement::surrounding_word(
11823 display_map,
11824 selection.start.to_display_point(display_map),
11825 );
11826 selection.start = word_range.start.to_offset(display_map, Bias::Left);
11827 selection.end = word_range.end.to_offset(display_map, Bias::Left);
11828 selection.goal = SelectionGoal::None;
11829 selection.reversed = false;
11830 select_next_match_ranges(
11831 self,
11832 selection.start..selection.end,
11833 replace_newest,
11834 autoscroll,
11835 window,
11836 cx,
11837 );
11838 }
11839
11840 if selections.len() == 1 {
11841 let selection = selections
11842 .last()
11843 .expect("ensured that there's only one selection");
11844 let query = buffer
11845 .text_for_range(selection.start..selection.end)
11846 .collect::<String>();
11847 let is_empty = query.is_empty();
11848 let select_state = SelectNextState {
11849 query: AhoCorasick::new(&[query])?,
11850 wordwise: true,
11851 done: is_empty,
11852 };
11853 self.select_next_state = Some(select_state);
11854 } else {
11855 self.select_next_state = None;
11856 }
11857 } else if let Some(selected_text) = selected_text {
11858 self.select_next_state = Some(SelectNextState {
11859 query: AhoCorasick::new(&[selected_text])?,
11860 wordwise: false,
11861 done: false,
11862 });
11863 self.select_next_match_internal(
11864 display_map,
11865 replace_newest,
11866 autoscroll,
11867 window,
11868 cx,
11869 )?;
11870 }
11871 }
11872 Ok(())
11873 }
11874
11875 pub fn select_all_matches(
11876 &mut self,
11877 _action: &SelectAllMatches,
11878 window: &mut Window,
11879 cx: &mut Context<Self>,
11880 ) -> Result<()> {
11881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11882
11883 self.push_to_selection_history();
11884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11885
11886 self.select_next_match_internal(&display_map, false, None, window, cx)?;
11887 let Some(select_next_state) = self.select_next_state.as_mut() else {
11888 return Ok(());
11889 };
11890 if select_next_state.done {
11891 return Ok(());
11892 }
11893
11894 let mut new_selections = Vec::new();
11895
11896 let reversed = self.selections.oldest::<usize>(cx).reversed;
11897 let buffer = &display_map.buffer_snapshot;
11898 let query_matches = select_next_state
11899 .query
11900 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
11901
11902 for query_match in query_matches.into_iter() {
11903 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
11904 let offset_range = if reversed {
11905 query_match.end()..query_match.start()
11906 } else {
11907 query_match.start()..query_match.end()
11908 };
11909 let display_range = offset_range.start.to_display_point(&display_map)
11910 ..offset_range.end.to_display_point(&display_map);
11911
11912 if !select_next_state.wordwise
11913 || (!movement::is_inside_word(&display_map, display_range.start)
11914 && !movement::is_inside_word(&display_map, display_range.end))
11915 {
11916 new_selections.push(offset_range.start..offset_range.end);
11917 }
11918 }
11919
11920 select_next_state.done = true;
11921 self.unfold_ranges(&new_selections.clone(), false, false, cx);
11922 self.change_selections(None, window, cx, |selections| {
11923 selections.select_ranges(new_selections)
11924 });
11925
11926 Ok(())
11927 }
11928
11929 pub fn select_next(
11930 &mut self,
11931 action: &SelectNext,
11932 window: &mut Window,
11933 cx: &mut Context<Self>,
11934 ) -> Result<()> {
11935 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11936 self.push_to_selection_history();
11937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11938 self.select_next_match_internal(
11939 &display_map,
11940 action.replace_newest,
11941 Some(Autoscroll::newest()),
11942 window,
11943 cx,
11944 )?;
11945 Ok(())
11946 }
11947
11948 pub fn select_previous(
11949 &mut self,
11950 action: &SelectPrevious,
11951 window: &mut Window,
11952 cx: &mut Context<Self>,
11953 ) -> Result<()> {
11954 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11955 self.push_to_selection_history();
11956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11957 let buffer = &display_map.buffer_snapshot;
11958 let mut selections = self.selections.all::<usize>(cx);
11959 if let Some(mut select_prev_state) = self.select_prev_state.take() {
11960 let query = &select_prev_state.query;
11961 if !select_prev_state.done {
11962 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
11963 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
11964 let mut next_selected_range = None;
11965 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
11966 let bytes_before_last_selection =
11967 buffer.reversed_bytes_in_range(0..last_selection.start);
11968 let bytes_after_first_selection =
11969 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
11970 let query_matches = query
11971 .stream_find_iter(bytes_before_last_selection)
11972 .map(|result| (last_selection.start, result))
11973 .chain(
11974 query
11975 .stream_find_iter(bytes_after_first_selection)
11976 .map(|result| (buffer.len(), result)),
11977 );
11978 for (end_offset, query_match) in query_matches {
11979 let query_match = query_match.unwrap(); // can only fail due to I/O
11980 let offset_range =
11981 end_offset - query_match.end()..end_offset - query_match.start();
11982 let display_range = offset_range.start.to_display_point(&display_map)
11983 ..offset_range.end.to_display_point(&display_map);
11984
11985 if !select_prev_state.wordwise
11986 || (!movement::is_inside_word(&display_map, display_range.start)
11987 && !movement::is_inside_word(&display_map, display_range.end))
11988 {
11989 next_selected_range = Some(offset_range);
11990 break;
11991 }
11992 }
11993
11994 if let Some(next_selected_range) = next_selected_range {
11995 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
11996 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
11997 if action.replace_newest {
11998 s.delete(s.newest_anchor().id);
11999 }
12000 s.insert_range(next_selected_range);
12001 });
12002 } else {
12003 select_prev_state.done = true;
12004 }
12005 }
12006
12007 self.select_prev_state = Some(select_prev_state);
12008 } else {
12009 let mut only_carets = true;
12010 let mut same_text_selected = true;
12011 let mut selected_text = None;
12012
12013 let mut selections_iter = selections.iter().peekable();
12014 while let Some(selection) = selections_iter.next() {
12015 if selection.start != selection.end {
12016 only_carets = false;
12017 }
12018
12019 if same_text_selected {
12020 if selected_text.is_none() {
12021 selected_text =
12022 Some(buffer.text_for_range(selection.range()).collect::<String>());
12023 }
12024
12025 if let Some(next_selection) = selections_iter.peek() {
12026 if next_selection.range().len() == selection.range().len() {
12027 let next_selected_text = buffer
12028 .text_for_range(next_selection.range())
12029 .collect::<String>();
12030 if Some(next_selected_text) != selected_text {
12031 same_text_selected = false;
12032 selected_text = None;
12033 }
12034 } else {
12035 same_text_selected = false;
12036 selected_text = None;
12037 }
12038 }
12039 }
12040 }
12041
12042 if only_carets {
12043 for selection in &mut selections {
12044 let word_range = movement::surrounding_word(
12045 &display_map,
12046 selection.start.to_display_point(&display_map),
12047 );
12048 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12049 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12050 selection.goal = SelectionGoal::None;
12051 selection.reversed = false;
12052 }
12053 if selections.len() == 1 {
12054 let selection = selections
12055 .last()
12056 .expect("ensured that there's only one selection");
12057 let query = buffer
12058 .text_for_range(selection.start..selection.end)
12059 .collect::<String>();
12060 let is_empty = query.is_empty();
12061 let select_state = SelectNextState {
12062 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12063 wordwise: true,
12064 done: is_empty,
12065 };
12066 self.select_prev_state = Some(select_state);
12067 } else {
12068 self.select_prev_state = None;
12069 }
12070
12071 self.unfold_ranges(
12072 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12073 false,
12074 true,
12075 cx,
12076 );
12077 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12078 s.select(selections);
12079 });
12080 } else if let Some(selected_text) = selected_text {
12081 self.select_prev_state = Some(SelectNextState {
12082 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12083 wordwise: false,
12084 done: false,
12085 });
12086 self.select_previous(action, window, cx)?;
12087 }
12088 }
12089 Ok(())
12090 }
12091
12092 pub fn find_next_match(
12093 &mut self,
12094 _: &FindNextMatch,
12095 window: &mut Window,
12096 cx: &mut Context<Self>,
12097 ) -> Result<()> {
12098 let selections = self.selections.disjoint_anchors();
12099 match selections.first() {
12100 Some(first) if selections.len() >= 2 => {
12101 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12102 s.select_ranges([first.range()]);
12103 });
12104 }
12105 _ => self.select_next(
12106 &SelectNext {
12107 replace_newest: true,
12108 },
12109 window,
12110 cx,
12111 )?,
12112 }
12113 Ok(())
12114 }
12115
12116 pub fn find_previous_match(
12117 &mut self,
12118 _: &FindPreviousMatch,
12119 window: &mut Window,
12120 cx: &mut Context<Self>,
12121 ) -> Result<()> {
12122 let selections = self.selections.disjoint_anchors();
12123 match selections.last() {
12124 Some(last) if selections.len() >= 2 => {
12125 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12126 s.select_ranges([last.range()]);
12127 });
12128 }
12129 _ => self.select_previous(
12130 &SelectPrevious {
12131 replace_newest: true,
12132 },
12133 window,
12134 cx,
12135 )?,
12136 }
12137 Ok(())
12138 }
12139
12140 pub fn toggle_comments(
12141 &mut self,
12142 action: &ToggleComments,
12143 window: &mut Window,
12144 cx: &mut Context<Self>,
12145 ) {
12146 if self.read_only(cx) {
12147 return;
12148 }
12149 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12150 let text_layout_details = &self.text_layout_details(window);
12151 self.transact(window, cx, |this, window, cx| {
12152 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12153 let mut edits = Vec::new();
12154 let mut selection_edit_ranges = Vec::new();
12155 let mut last_toggled_row = None;
12156 let snapshot = this.buffer.read(cx).read(cx);
12157 let empty_str: Arc<str> = Arc::default();
12158 let mut suffixes_inserted = Vec::new();
12159 let ignore_indent = action.ignore_indent;
12160
12161 fn comment_prefix_range(
12162 snapshot: &MultiBufferSnapshot,
12163 row: MultiBufferRow,
12164 comment_prefix: &str,
12165 comment_prefix_whitespace: &str,
12166 ignore_indent: bool,
12167 ) -> Range<Point> {
12168 let indent_size = if ignore_indent {
12169 0
12170 } else {
12171 snapshot.indent_size_for_line(row).len
12172 };
12173
12174 let start = Point::new(row.0, indent_size);
12175
12176 let mut line_bytes = snapshot
12177 .bytes_in_range(start..snapshot.max_point())
12178 .flatten()
12179 .copied();
12180
12181 // If this line currently begins with the line comment prefix, then record
12182 // the range containing the prefix.
12183 if line_bytes
12184 .by_ref()
12185 .take(comment_prefix.len())
12186 .eq(comment_prefix.bytes())
12187 {
12188 // Include any whitespace that matches the comment prefix.
12189 let matching_whitespace_len = line_bytes
12190 .zip(comment_prefix_whitespace.bytes())
12191 .take_while(|(a, b)| a == b)
12192 .count() as u32;
12193 let end = Point::new(
12194 start.row,
12195 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12196 );
12197 start..end
12198 } else {
12199 start..start
12200 }
12201 }
12202
12203 fn comment_suffix_range(
12204 snapshot: &MultiBufferSnapshot,
12205 row: MultiBufferRow,
12206 comment_suffix: &str,
12207 comment_suffix_has_leading_space: bool,
12208 ) -> Range<Point> {
12209 let end = Point::new(row.0, snapshot.line_len(row));
12210 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12211
12212 let mut line_end_bytes = snapshot
12213 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12214 .flatten()
12215 .copied();
12216
12217 let leading_space_len = if suffix_start_column > 0
12218 && line_end_bytes.next() == Some(b' ')
12219 && comment_suffix_has_leading_space
12220 {
12221 1
12222 } else {
12223 0
12224 };
12225
12226 // If this line currently begins with the line comment prefix, then record
12227 // the range containing the prefix.
12228 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12229 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12230 start..end
12231 } else {
12232 end..end
12233 }
12234 }
12235
12236 // TODO: Handle selections that cross excerpts
12237 for selection in &mut selections {
12238 let start_column = snapshot
12239 .indent_size_for_line(MultiBufferRow(selection.start.row))
12240 .len;
12241 let language = if let Some(language) =
12242 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12243 {
12244 language
12245 } else {
12246 continue;
12247 };
12248
12249 selection_edit_ranges.clear();
12250
12251 // If multiple selections contain a given row, avoid processing that
12252 // row more than once.
12253 let mut start_row = MultiBufferRow(selection.start.row);
12254 if last_toggled_row == Some(start_row) {
12255 start_row = start_row.next_row();
12256 }
12257 let end_row =
12258 if selection.end.row > selection.start.row && selection.end.column == 0 {
12259 MultiBufferRow(selection.end.row - 1)
12260 } else {
12261 MultiBufferRow(selection.end.row)
12262 };
12263 last_toggled_row = Some(end_row);
12264
12265 if start_row > end_row {
12266 continue;
12267 }
12268
12269 // If the language has line comments, toggle those.
12270 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12271
12272 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12273 if ignore_indent {
12274 full_comment_prefixes = full_comment_prefixes
12275 .into_iter()
12276 .map(|s| Arc::from(s.trim_end()))
12277 .collect();
12278 }
12279
12280 if !full_comment_prefixes.is_empty() {
12281 let first_prefix = full_comment_prefixes
12282 .first()
12283 .expect("prefixes is non-empty");
12284 let prefix_trimmed_lengths = full_comment_prefixes
12285 .iter()
12286 .map(|p| p.trim_end_matches(' ').len())
12287 .collect::<SmallVec<[usize; 4]>>();
12288
12289 let mut all_selection_lines_are_comments = true;
12290
12291 for row in start_row.0..=end_row.0 {
12292 let row = MultiBufferRow(row);
12293 if start_row < end_row && snapshot.is_line_blank(row) {
12294 continue;
12295 }
12296
12297 let prefix_range = full_comment_prefixes
12298 .iter()
12299 .zip(prefix_trimmed_lengths.iter().copied())
12300 .map(|(prefix, trimmed_prefix_len)| {
12301 comment_prefix_range(
12302 snapshot.deref(),
12303 row,
12304 &prefix[..trimmed_prefix_len],
12305 &prefix[trimmed_prefix_len..],
12306 ignore_indent,
12307 )
12308 })
12309 .max_by_key(|range| range.end.column - range.start.column)
12310 .expect("prefixes is non-empty");
12311
12312 if prefix_range.is_empty() {
12313 all_selection_lines_are_comments = false;
12314 }
12315
12316 selection_edit_ranges.push(prefix_range);
12317 }
12318
12319 if all_selection_lines_are_comments {
12320 edits.extend(
12321 selection_edit_ranges
12322 .iter()
12323 .cloned()
12324 .map(|range| (range, empty_str.clone())),
12325 );
12326 } else {
12327 let min_column = selection_edit_ranges
12328 .iter()
12329 .map(|range| range.start.column)
12330 .min()
12331 .unwrap_or(0);
12332 edits.extend(selection_edit_ranges.iter().map(|range| {
12333 let position = Point::new(range.start.row, min_column);
12334 (position..position, first_prefix.clone())
12335 }));
12336 }
12337 } else if let Some((full_comment_prefix, comment_suffix)) =
12338 language.block_comment_delimiters()
12339 {
12340 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12341 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12342 let prefix_range = comment_prefix_range(
12343 snapshot.deref(),
12344 start_row,
12345 comment_prefix,
12346 comment_prefix_whitespace,
12347 ignore_indent,
12348 );
12349 let suffix_range = comment_suffix_range(
12350 snapshot.deref(),
12351 end_row,
12352 comment_suffix.trim_start_matches(' '),
12353 comment_suffix.starts_with(' '),
12354 );
12355
12356 if prefix_range.is_empty() || suffix_range.is_empty() {
12357 edits.push((
12358 prefix_range.start..prefix_range.start,
12359 full_comment_prefix.clone(),
12360 ));
12361 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12362 suffixes_inserted.push((end_row, comment_suffix.len()));
12363 } else {
12364 edits.push((prefix_range, empty_str.clone()));
12365 edits.push((suffix_range, empty_str.clone()));
12366 }
12367 } else {
12368 continue;
12369 }
12370 }
12371
12372 drop(snapshot);
12373 this.buffer.update(cx, |buffer, cx| {
12374 buffer.edit(edits, None, cx);
12375 });
12376
12377 // Adjust selections so that they end before any comment suffixes that
12378 // were inserted.
12379 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12380 let mut selections = this.selections.all::<Point>(cx);
12381 let snapshot = this.buffer.read(cx).read(cx);
12382 for selection in &mut selections {
12383 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12384 match row.cmp(&MultiBufferRow(selection.end.row)) {
12385 Ordering::Less => {
12386 suffixes_inserted.next();
12387 continue;
12388 }
12389 Ordering::Greater => break,
12390 Ordering::Equal => {
12391 if selection.end.column == snapshot.line_len(row) {
12392 if selection.is_empty() {
12393 selection.start.column -= suffix_len as u32;
12394 }
12395 selection.end.column -= suffix_len as u32;
12396 }
12397 break;
12398 }
12399 }
12400 }
12401 }
12402
12403 drop(snapshot);
12404 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12405 s.select(selections)
12406 });
12407
12408 let selections = this.selections.all::<Point>(cx);
12409 let selections_on_single_row = selections.windows(2).all(|selections| {
12410 selections[0].start.row == selections[1].start.row
12411 && selections[0].end.row == selections[1].end.row
12412 && selections[0].start.row == selections[0].end.row
12413 });
12414 let selections_selecting = selections
12415 .iter()
12416 .any(|selection| selection.start != selection.end);
12417 let advance_downwards = action.advance_downwards
12418 && selections_on_single_row
12419 && !selections_selecting
12420 && !matches!(this.mode, EditorMode::SingleLine { .. });
12421
12422 if advance_downwards {
12423 let snapshot = this.buffer.read(cx).snapshot(cx);
12424
12425 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12426 s.move_cursors_with(|display_snapshot, display_point, _| {
12427 let mut point = display_point.to_point(display_snapshot);
12428 point.row += 1;
12429 point = snapshot.clip_point(point, Bias::Left);
12430 let display_point = point.to_display_point(display_snapshot);
12431 let goal = SelectionGoal::HorizontalPosition(
12432 display_snapshot
12433 .x_for_display_point(display_point, text_layout_details)
12434 .into(),
12435 );
12436 (display_point, goal)
12437 })
12438 });
12439 }
12440 });
12441 }
12442
12443 pub fn select_enclosing_symbol(
12444 &mut self,
12445 _: &SelectEnclosingSymbol,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12450
12451 let buffer = self.buffer.read(cx).snapshot(cx);
12452 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12453
12454 fn update_selection(
12455 selection: &Selection<usize>,
12456 buffer_snap: &MultiBufferSnapshot,
12457 ) -> Option<Selection<usize>> {
12458 let cursor = selection.head();
12459 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12460 for symbol in symbols.iter().rev() {
12461 let start = symbol.range.start.to_offset(buffer_snap);
12462 let end = symbol.range.end.to_offset(buffer_snap);
12463 let new_range = start..end;
12464 if start < selection.start || end > selection.end {
12465 return Some(Selection {
12466 id: selection.id,
12467 start: new_range.start,
12468 end: new_range.end,
12469 goal: SelectionGoal::None,
12470 reversed: selection.reversed,
12471 });
12472 }
12473 }
12474 None
12475 }
12476
12477 let mut selected_larger_symbol = false;
12478 let new_selections = old_selections
12479 .iter()
12480 .map(|selection| match update_selection(selection, &buffer) {
12481 Some(new_selection) => {
12482 if new_selection.range() != selection.range() {
12483 selected_larger_symbol = true;
12484 }
12485 new_selection
12486 }
12487 None => selection.clone(),
12488 })
12489 .collect::<Vec<_>>();
12490
12491 if selected_larger_symbol {
12492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12493 s.select(new_selections);
12494 });
12495 }
12496 }
12497
12498 pub fn select_larger_syntax_node(
12499 &mut self,
12500 _: &SelectLargerSyntaxNode,
12501 window: &mut Window,
12502 cx: &mut Context<Self>,
12503 ) {
12504 let Some(visible_row_count) = self.visible_row_count() else {
12505 return;
12506 };
12507 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12508 if old_selections.is_empty() {
12509 return;
12510 }
12511
12512 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12513
12514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12515 let buffer = self.buffer.read(cx).snapshot(cx);
12516
12517 let mut selected_larger_node = false;
12518 let mut new_selections = old_selections
12519 .iter()
12520 .map(|selection| {
12521 let old_range = selection.start..selection.end;
12522 let mut new_range = old_range.clone();
12523 let mut new_node = None;
12524 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12525 {
12526 new_node = Some(node);
12527 new_range = match containing_range {
12528 MultiOrSingleBufferOffsetRange::Single(_) => break,
12529 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12530 };
12531 if !display_map.intersects_fold(new_range.start)
12532 && !display_map.intersects_fold(new_range.end)
12533 {
12534 break;
12535 }
12536 }
12537
12538 if let Some(node) = new_node {
12539 // Log the ancestor, to support using this action as a way to explore TreeSitter
12540 // nodes. Parent and grandparent are also logged because this operation will not
12541 // visit nodes that have the same range as their parent.
12542 log::info!("Node: {node:?}");
12543 let parent = node.parent();
12544 log::info!("Parent: {parent:?}");
12545 let grandparent = parent.and_then(|x| x.parent());
12546 log::info!("Grandparent: {grandparent:?}");
12547 }
12548
12549 selected_larger_node |= new_range != old_range;
12550 Selection {
12551 id: selection.id,
12552 start: new_range.start,
12553 end: new_range.end,
12554 goal: SelectionGoal::None,
12555 reversed: selection.reversed,
12556 }
12557 })
12558 .collect::<Vec<_>>();
12559
12560 if !selected_larger_node {
12561 return; // don't put this call in the history
12562 }
12563
12564 // scroll based on transformation done to the last selection created by the user
12565 let (last_old, last_new) = old_selections
12566 .last()
12567 .zip(new_selections.last().cloned())
12568 .expect("old_selections isn't empty");
12569
12570 // revert selection
12571 let is_selection_reversed = {
12572 let should_newest_selection_be_reversed = last_old.start != last_new.start;
12573 new_selections.last_mut().expect("checked above").reversed =
12574 should_newest_selection_be_reversed;
12575 should_newest_selection_be_reversed
12576 };
12577
12578 if selected_larger_node {
12579 self.select_syntax_node_history.disable_clearing = true;
12580 self.change_selections(None, window, cx, |s| {
12581 s.select(new_selections.clone());
12582 });
12583 self.select_syntax_node_history.disable_clearing = false;
12584 }
12585
12586 let start_row = last_new.start.to_display_point(&display_map).row().0;
12587 let end_row = last_new.end.to_display_point(&display_map).row().0;
12588 let selection_height = end_row - start_row + 1;
12589 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
12590
12591 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
12592 let scroll_behavior = if fits_on_the_screen {
12593 self.request_autoscroll(Autoscroll::fit(), cx);
12594 SelectSyntaxNodeScrollBehavior::FitSelection
12595 } else if is_selection_reversed {
12596 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12597 SelectSyntaxNodeScrollBehavior::CursorTop
12598 } else {
12599 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12600 SelectSyntaxNodeScrollBehavior::CursorBottom
12601 };
12602
12603 self.select_syntax_node_history.push((
12604 old_selections,
12605 scroll_behavior,
12606 is_selection_reversed,
12607 ));
12608 }
12609
12610 pub fn select_smaller_syntax_node(
12611 &mut self,
12612 _: &SelectSmallerSyntaxNode,
12613 window: &mut Window,
12614 cx: &mut Context<Self>,
12615 ) {
12616 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12617
12618 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
12619 self.select_syntax_node_history.pop()
12620 {
12621 if let Some(selection) = selections.last_mut() {
12622 selection.reversed = is_selection_reversed;
12623 }
12624
12625 self.select_syntax_node_history.disable_clearing = true;
12626 self.change_selections(None, window, cx, |s| {
12627 s.select(selections.to_vec());
12628 });
12629 self.select_syntax_node_history.disable_clearing = false;
12630
12631 match scroll_behavior {
12632 SelectSyntaxNodeScrollBehavior::CursorTop => {
12633 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
12634 }
12635 SelectSyntaxNodeScrollBehavior::FitSelection => {
12636 self.request_autoscroll(Autoscroll::fit(), cx);
12637 }
12638 SelectSyntaxNodeScrollBehavior::CursorBottom => {
12639 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
12640 }
12641 }
12642 }
12643 }
12644
12645 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
12646 if !EditorSettings::get_global(cx).gutter.runnables {
12647 self.clear_tasks();
12648 return Task::ready(());
12649 }
12650 let project = self.project.as_ref().map(Entity::downgrade);
12651 let task_sources = self.lsp_task_sources(cx);
12652 cx.spawn_in(window, async move |editor, cx| {
12653 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
12654 let Some(project) = project.and_then(|p| p.upgrade()) else {
12655 return;
12656 };
12657 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
12658 this.display_map.update(cx, |map, cx| map.snapshot(cx))
12659 }) else {
12660 return;
12661 };
12662
12663 let hide_runnables = project
12664 .update(cx, |project, cx| {
12665 // Do not display any test indicators in non-dev server remote projects.
12666 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
12667 })
12668 .unwrap_or(true);
12669 if hide_runnables {
12670 return;
12671 }
12672 let new_rows =
12673 cx.background_spawn({
12674 let snapshot = display_snapshot.clone();
12675 async move {
12676 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
12677 }
12678 })
12679 .await;
12680 let Ok(lsp_tasks) =
12681 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
12682 else {
12683 return;
12684 };
12685 let lsp_tasks = lsp_tasks.await;
12686
12687 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
12688 lsp_tasks
12689 .into_iter()
12690 .flat_map(|(kind, tasks)| {
12691 tasks.into_iter().filter_map(move |(location, task)| {
12692 Some((kind.clone(), location?, task))
12693 })
12694 })
12695 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
12696 let buffer = location.target.buffer;
12697 let buffer_snapshot = buffer.read(cx).snapshot();
12698 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
12699 |(excerpt_id, snapshot, _)| {
12700 if snapshot.remote_id() == buffer_snapshot.remote_id() {
12701 display_snapshot
12702 .buffer_snapshot
12703 .anchor_in_excerpt(excerpt_id, location.target.range.start)
12704 } else {
12705 None
12706 }
12707 },
12708 );
12709 if let Some(offset) = offset {
12710 let task_buffer_range =
12711 location.target.range.to_point(&buffer_snapshot);
12712 let context_buffer_range =
12713 task_buffer_range.to_offset(&buffer_snapshot);
12714 let context_range = BufferOffset(context_buffer_range.start)
12715 ..BufferOffset(context_buffer_range.end);
12716
12717 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
12718 .or_insert_with(|| RunnableTasks {
12719 templates: Vec::new(),
12720 offset,
12721 column: task_buffer_range.start.column,
12722 extra_variables: HashMap::default(),
12723 context_range,
12724 })
12725 .templates
12726 .push((kind, task.original_task().clone()));
12727 }
12728
12729 acc
12730 })
12731 }) else {
12732 return;
12733 };
12734
12735 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
12736 editor
12737 .update(cx, |editor, _| {
12738 editor.clear_tasks();
12739 for (key, mut value) in rows {
12740 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
12741 value.templates.extend(lsp_tasks.templates);
12742 }
12743
12744 editor.insert_tasks(key, value);
12745 }
12746 for (key, value) in lsp_tasks_by_rows {
12747 editor.insert_tasks(key, value);
12748 }
12749 })
12750 .ok();
12751 })
12752 }
12753 fn fetch_runnable_ranges(
12754 snapshot: &DisplaySnapshot,
12755 range: Range<Anchor>,
12756 ) -> Vec<language::RunnableRange> {
12757 snapshot.buffer_snapshot.runnable_ranges(range).collect()
12758 }
12759
12760 fn runnable_rows(
12761 project: Entity<Project>,
12762 snapshot: DisplaySnapshot,
12763 runnable_ranges: Vec<RunnableRange>,
12764 mut cx: AsyncWindowContext,
12765 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
12766 runnable_ranges
12767 .into_iter()
12768 .filter_map(|mut runnable| {
12769 let tasks = cx
12770 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
12771 .ok()?;
12772 if tasks.is_empty() {
12773 return None;
12774 }
12775
12776 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
12777
12778 let row = snapshot
12779 .buffer_snapshot
12780 .buffer_line_for_row(MultiBufferRow(point.row))?
12781 .1
12782 .start
12783 .row;
12784
12785 let context_range =
12786 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
12787 Some((
12788 (runnable.buffer_id, row),
12789 RunnableTasks {
12790 templates: tasks,
12791 offset: snapshot
12792 .buffer_snapshot
12793 .anchor_before(runnable.run_range.start),
12794 context_range,
12795 column: point.column,
12796 extra_variables: runnable.extra_captures,
12797 },
12798 ))
12799 })
12800 .collect()
12801 }
12802
12803 fn templates_with_tags(
12804 project: &Entity<Project>,
12805 runnable: &mut Runnable,
12806 cx: &mut App,
12807 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
12808 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
12809 let (worktree_id, file) = project
12810 .buffer_for_id(runnable.buffer, cx)
12811 .and_then(|buffer| buffer.read(cx).file())
12812 .map(|file| (file.worktree_id(cx), file.clone()))
12813 .unzip();
12814
12815 (
12816 project.task_store().read(cx).task_inventory().cloned(),
12817 worktree_id,
12818 file,
12819 )
12820 });
12821
12822 let mut templates_with_tags = mem::take(&mut runnable.tags)
12823 .into_iter()
12824 .flat_map(|RunnableTag(tag)| {
12825 inventory
12826 .as_ref()
12827 .into_iter()
12828 .flat_map(|inventory| {
12829 inventory.read(cx).list_tasks(
12830 file.clone(),
12831 Some(runnable.language.clone()),
12832 worktree_id,
12833 cx,
12834 )
12835 })
12836 .filter(move |(_, template)| {
12837 template.tags.iter().any(|source_tag| source_tag == &tag)
12838 })
12839 })
12840 .sorted_by_key(|(kind, _)| kind.to_owned())
12841 .collect::<Vec<_>>();
12842 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
12843 // Strongest source wins; if we have worktree tag binding, prefer that to
12844 // global and language bindings;
12845 // if we have a global binding, prefer that to language binding.
12846 let first_mismatch = templates_with_tags
12847 .iter()
12848 .position(|(tag_source, _)| tag_source != leading_tag_source);
12849 if let Some(index) = first_mismatch {
12850 templates_with_tags.truncate(index);
12851 }
12852 }
12853
12854 templates_with_tags
12855 }
12856
12857 pub fn move_to_enclosing_bracket(
12858 &mut self,
12859 _: &MoveToEnclosingBracket,
12860 window: &mut Window,
12861 cx: &mut Context<Self>,
12862 ) {
12863 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12865 s.move_offsets_with(|snapshot, selection| {
12866 let Some(enclosing_bracket_ranges) =
12867 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
12868 else {
12869 return;
12870 };
12871
12872 let mut best_length = usize::MAX;
12873 let mut best_inside = false;
12874 let mut best_in_bracket_range = false;
12875 let mut best_destination = None;
12876 for (open, close) in enclosing_bracket_ranges {
12877 let close = close.to_inclusive();
12878 let length = close.end() - open.start;
12879 let inside = selection.start >= open.end && selection.end <= *close.start();
12880 let in_bracket_range = open.to_inclusive().contains(&selection.head())
12881 || close.contains(&selection.head());
12882
12883 // If best is next to a bracket and current isn't, skip
12884 if !in_bracket_range && best_in_bracket_range {
12885 continue;
12886 }
12887
12888 // Prefer smaller lengths unless best is inside and current isn't
12889 if length > best_length && (best_inside || !inside) {
12890 continue;
12891 }
12892
12893 best_length = length;
12894 best_inside = inside;
12895 best_in_bracket_range = in_bracket_range;
12896 best_destination = Some(
12897 if close.contains(&selection.start) && close.contains(&selection.end) {
12898 if inside { open.end } else { open.start }
12899 } else if inside {
12900 *close.start()
12901 } else {
12902 *close.end()
12903 },
12904 );
12905 }
12906
12907 if let Some(destination) = best_destination {
12908 selection.collapse_to(destination, SelectionGoal::None);
12909 }
12910 })
12911 });
12912 }
12913
12914 pub fn undo_selection(
12915 &mut self,
12916 _: &UndoSelection,
12917 window: &mut Window,
12918 cx: &mut Context<Self>,
12919 ) {
12920 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12921 self.end_selection(window, cx);
12922 self.selection_history.mode = SelectionHistoryMode::Undoing;
12923 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
12924 self.change_selections(None, window, cx, |s| {
12925 s.select_anchors(entry.selections.to_vec())
12926 });
12927 self.select_next_state = entry.select_next_state;
12928 self.select_prev_state = entry.select_prev_state;
12929 self.add_selections_state = entry.add_selections_state;
12930 self.request_autoscroll(Autoscroll::newest(), cx);
12931 }
12932 self.selection_history.mode = SelectionHistoryMode::Normal;
12933 }
12934
12935 pub fn redo_selection(
12936 &mut self,
12937 _: &RedoSelection,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) {
12941 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12942 self.end_selection(window, cx);
12943 self.selection_history.mode = SelectionHistoryMode::Redoing;
12944 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
12945 self.change_selections(None, window, cx, |s| {
12946 s.select_anchors(entry.selections.to_vec())
12947 });
12948 self.select_next_state = entry.select_next_state;
12949 self.select_prev_state = entry.select_prev_state;
12950 self.add_selections_state = entry.add_selections_state;
12951 self.request_autoscroll(Autoscroll::newest(), cx);
12952 }
12953 self.selection_history.mode = SelectionHistoryMode::Normal;
12954 }
12955
12956 pub fn expand_excerpts(
12957 &mut self,
12958 action: &ExpandExcerpts,
12959 _: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) {
12962 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
12963 }
12964
12965 pub fn expand_excerpts_down(
12966 &mut self,
12967 action: &ExpandExcerptsDown,
12968 _: &mut Window,
12969 cx: &mut Context<Self>,
12970 ) {
12971 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
12972 }
12973
12974 pub fn expand_excerpts_up(
12975 &mut self,
12976 action: &ExpandExcerptsUp,
12977 _: &mut Window,
12978 cx: &mut Context<Self>,
12979 ) {
12980 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
12981 }
12982
12983 pub fn expand_excerpts_for_direction(
12984 &mut self,
12985 lines: u32,
12986 direction: ExpandExcerptDirection,
12987
12988 cx: &mut Context<Self>,
12989 ) {
12990 let selections = self.selections.disjoint_anchors();
12991
12992 let lines = if lines == 0 {
12993 EditorSettings::get_global(cx).expand_excerpt_lines
12994 } else {
12995 lines
12996 };
12997
12998 self.buffer.update(cx, |buffer, cx| {
12999 let snapshot = buffer.snapshot(cx);
13000 let mut excerpt_ids = selections
13001 .iter()
13002 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13003 .collect::<Vec<_>>();
13004 excerpt_ids.sort();
13005 excerpt_ids.dedup();
13006 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13007 })
13008 }
13009
13010 pub fn expand_excerpt(
13011 &mut self,
13012 excerpt: ExcerptId,
13013 direction: ExpandExcerptDirection,
13014 window: &mut Window,
13015 cx: &mut Context<Self>,
13016 ) {
13017 let current_scroll_position = self.scroll_position(cx);
13018 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13019 let mut should_scroll_up = false;
13020
13021 if direction == ExpandExcerptDirection::Down {
13022 let multi_buffer = self.buffer.read(cx);
13023 let snapshot = multi_buffer.snapshot(cx);
13024 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13025 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13026 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13027 let buffer_snapshot = buffer.read(cx).snapshot();
13028 let excerpt_end_row =
13029 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13030 let last_row = buffer_snapshot.max_point().row;
13031 let lines_below = last_row.saturating_sub(excerpt_end_row);
13032 should_scroll_up = lines_below >= lines_to_expand;
13033 }
13034 }
13035 }
13036 }
13037
13038 self.buffer.update(cx, |buffer, cx| {
13039 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13040 });
13041
13042 if should_scroll_up {
13043 let new_scroll_position =
13044 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13045 self.set_scroll_position(new_scroll_position, window, cx);
13046 }
13047 }
13048
13049 pub fn go_to_singleton_buffer_point(
13050 &mut self,
13051 point: Point,
13052 window: &mut Window,
13053 cx: &mut Context<Self>,
13054 ) {
13055 self.go_to_singleton_buffer_range(point..point, window, cx);
13056 }
13057
13058 pub fn go_to_singleton_buffer_range(
13059 &mut self,
13060 range: Range<Point>,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 let multibuffer = self.buffer().read(cx);
13065 let Some(buffer) = multibuffer.as_singleton() else {
13066 return;
13067 };
13068 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13069 return;
13070 };
13071 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13072 return;
13073 };
13074 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13075 s.select_anchor_ranges([start..end])
13076 });
13077 }
13078
13079 pub fn go_to_diagnostic(
13080 &mut self,
13081 _: &GoToDiagnostic,
13082 window: &mut Window,
13083 cx: &mut Context<Self>,
13084 ) {
13085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13086 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13087 }
13088
13089 pub fn go_to_prev_diagnostic(
13090 &mut self,
13091 _: &GoToPreviousDiagnostic,
13092 window: &mut Window,
13093 cx: &mut Context<Self>,
13094 ) {
13095 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13096 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13097 }
13098
13099 pub fn go_to_diagnostic_impl(
13100 &mut self,
13101 direction: Direction,
13102 window: &mut Window,
13103 cx: &mut Context<Self>,
13104 ) {
13105 let buffer = self.buffer.read(cx).snapshot(cx);
13106 let selection = self.selections.newest::<usize>(cx);
13107
13108 let mut active_group_id = None;
13109 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13110 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13111 active_group_id = Some(active_group.group_id);
13112 }
13113 }
13114
13115 fn filtered(
13116 snapshot: EditorSnapshot,
13117 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13118 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13119 diagnostics
13120 .filter(|entry| entry.range.start != entry.range.end)
13121 .filter(|entry| !entry.diagnostic.is_unnecessary)
13122 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13123 }
13124
13125 let snapshot = self.snapshot(window, cx);
13126 let before = filtered(
13127 snapshot.clone(),
13128 buffer
13129 .diagnostics_in_range(0..selection.start)
13130 .filter(|entry| entry.range.start <= selection.start),
13131 );
13132 let after = filtered(
13133 snapshot,
13134 buffer
13135 .diagnostics_in_range(selection.start..buffer.len())
13136 .filter(|entry| entry.range.start >= selection.start),
13137 );
13138
13139 let mut found: Option<DiagnosticEntry<usize>> = None;
13140 if direction == Direction::Prev {
13141 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13142 {
13143 for diagnostic in prev_diagnostics.into_iter().rev() {
13144 if diagnostic.range.start != selection.start
13145 || active_group_id
13146 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13147 {
13148 found = Some(diagnostic);
13149 break 'outer;
13150 }
13151 }
13152 }
13153 } else {
13154 for diagnostic in after.chain(before) {
13155 if diagnostic.range.start != selection.start
13156 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13157 {
13158 found = Some(diagnostic);
13159 break;
13160 }
13161 }
13162 }
13163 let Some(next_diagnostic) = found else {
13164 return;
13165 };
13166
13167 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13168 return;
13169 };
13170 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13171 s.select_ranges(vec![
13172 next_diagnostic.range.start..next_diagnostic.range.start,
13173 ])
13174 });
13175 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13176 self.refresh_inline_completion(false, true, window, cx);
13177 }
13178
13179 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13181 let snapshot = self.snapshot(window, cx);
13182 let selection = self.selections.newest::<Point>(cx);
13183 self.go_to_hunk_before_or_after_position(
13184 &snapshot,
13185 selection.head(),
13186 Direction::Next,
13187 window,
13188 cx,
13189 );
13190 }
13191
13192 pub fn go_to_hunk_before_or_after_position(
13193 &mut self,
13194 snapshot: &EditorSnapshot,
13195 position: Point,
13196 direction: Direction,
13197 window: &mut Window,
13198 cx: &mut Context<Editor>,
13199 ) {
13200 let row = if direction == Direction::Next {
13201 self.hunk_after_position(snapshot, position)
13202 .map(|hunk| hunk.row_range.start)
13203 } else {
13204 self.hunk_before_position(snapshot, position)
13205 };
13206
13207 if let Some(row) = row {
13208 let destination = Point::new(row.0, 0);
13209 let autoscroll = Autoscroll::center();
13210
13211 self.unfold_ranges(&[destination..destination], false, false, cx);
13212 self.change_selections(Some(autoscroll), window, cx, |s| {
13213 s.select_ranges([destination..destination]);
13214 });
13215 }
13216 }
13217
13218 fn hunk_after_position(
13219 &mut self,
13220 snapshot: &EditorSnapshot,
13221 position: Point,
13222 ) -> Option<MultiBufferDiffHunk> {
13223 snapshot
13224 .buffer_snapshot
13225 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13226 .find(|hunk| hunk.row_range.start.0 > position.row)
13227 .or_else(|| {
13228 snapshot
13229 .buffer_snapshot
13230 .diff_hunks_in_range(Point::zero()..position)
13231 .find(|hunk| hunk.row_range.end.0 < position.row)
13232 })
13233 }
13234
13235 fn go_to_prev_hunk(
13236 &mut self,
13237 _: &GoToPreviousHunk,
13238 window: &mut Window,
13239 cx: &mut Context<Self>,
13240 ) {
13241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13242 let snapshot = self.snapshot(window, cx);
13243 let selection = self.selections.newest::<Point>(cx);
13244 self.go_to_hunk_before_or_after_position(
13245 &snapshot,
13246 selection.head(),
13247 Direction::Prev,
13248 window,
13249 cx,
13250 );
13251 }
13252
13253 fn hunk_before_position(
13254 &mut self,
13255 snapshot: &EditorSnapshot,
13256 position: Point,
13257 ) -> Option<MultiBufferRow> {
13258 snapshot
13259 .buffer_snapshot
13260 .diff_hunk_before(position)
13261 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13262 }
13263
13264 fn go_to_line<T: 'static>(
13265 &mut self,
13266 position: Anchor,
13267 highlight_color: Option<Hsla>,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) {
13271 let snapshot = self.snapshot(window, cx).display_snapshot;
13272 let position = position.to_point(&snapshot.buffer_snapshot);
13273 let start = snapshot
13274 .buffer_snapshot
13275 .clip_point(Point::new(position.row, 0), Bias::Left);
13276 let end = start + Point::new(1, 0);
13277 let start = snapshot.buffer_snapshot.anchor_before(start);
13278 let end = snapshot.buffer_snapshot.anchor_before(end);
13279
13280 self.highlight_rows::<T>(
13281 start..end,
13282 highlight_color
13283 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13284 false,
13285 cx,
13286 );
13287 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13288 }
13289
13290 pub fn go_to_definition(
13291 &mut self,
13292 _: &GoToDefinition,
13293 window: &mut Window,
13294 cx: &mut Context<Self>,
13295 ) -> Task<Result<Navigated>> {
13296 let definition =
13297 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13298 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13299 cx.spawn_in(window, async move |editor, cx| {
13300 if definition.await? == Navigated::Yes {
13301 return Ok(Navigated::Yes);
13302 }
13303 match fallback_strategy {
13304 GoToDefinitionFallback::None => Ok(Navigated::No),
13305 GoToDefinitionFallback::FindAllReferences => {
13306 match editor.update_in(cx, |editor, window, cx| {
13307 editor.find_all_references(&FindAllReferences, window, cx)
13308 })? {
13309 Some(references) => references.await,
13310 None => Ok(Navigated::No),
13311 }
13312 }
13313 }
13314 })
13315 }
13316
13317 pub fn go_to_declaration(
13318 &mut self,
13319 _: &GoToDeclaration,
13320 window: &mut Window,
13321 cx: &mut Context<Self>,
13322 ) -> Task<Result<Navigated>> {
13323 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13324 }
13325
13326 pub fn go_to_declaration_split(
13327 &mut self,
13328 _: &GoToDeclaration,
13329 window: &mut Window,
13330 cx: &mut Context<Self>,
13331 ) -> Task<Result<Navigated>> {
13332 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13333 }
13334
13335 pub fn go_to_implementation(
13336 &mut self,
13337 _: &GoToImplementation,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) -> Task<Result<Navigated>> {
13341 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13342 }
13343
13344 pub fn go_to_implementation_split(
13345 &mut self,
13346 _: &GoToImplementationSplit,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) -> Task<Result<Navigated>> {
13350 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13351 }
13352
13353 pub fn go_to_type_definition(
13354 &mut self,
13355 _: &GoToTypeDefinition,
13356 window: &mut Window,
13357 cx: &mut Context<Self>,
13358 ) -> Task<Result<Navigated>> {
13359 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13360 }
13361
13362 pub fn go_to_definition_split(
13363 &mut self,
13364 _: &GoToDefinitionSplit,
13365 window: &mut Window,
13366 cx: &mut Context<Self>,
13367 ) -> Task<Result<Navigated>> {
13368 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13369 }
13370
13371 pub fn go_to_type_definition_split(
13372 &mut self,
13373 _: &GoToTypeDefinitionSplit,
13374 window: &mut Window,
13375 cx: &mut Context<Self>,
13376 ) -> Task<Result<Navigated>> {
13377 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13378 }
13379
13380 fn go_to_definition_of_kind(
13381 &mut self,
13382 kind: GotoDefinitionKind,
13383 split: bool,
13384 window: &mut Window,
13385 cx: &mut Context<Self>,
13386 ) -> Task<Result<Navigated>> {
13387 let Some(provider) = self.semantics_provider.clone() else {
13388 return Task::ready(Ok(Navigated::No));
13389 };
13390 let head = self.selections.newest::<usize>(cx).head();
13391 let buffer = self.buffer.read(cx);
13392 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13393 text_anchor
13394 } else {
13395 return Task::ready(Ok(Navigated::No));
13396 };
13397
13398 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13399 return Task::ready(Ok(Navigated::No));
13400 };
13401
13402 cx.spawn_in(window, async move |editor, cx| {
13403 let definitions = definitions.await?;
13404 let navigated = editor
13405 .update_in(cx, |editor, window, cx| {
13406 editor.navigate_to_hover_links(
13407 Some(kind),
13408 definitions
13409 .into_iter()
13410 .filter(|location| {
13411 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13412 })
13413 .map(HoverLink::Text)
13414 .collect::<Vec<_>>(),
13415 split,
13416 window,
13417 cx,
13418 )
13419 })?
13420 .await?;
13421 anyhow::Ok(navigated)
13422 })
13423 }
13424
13425 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13426 let selection = self.selections.newest_anchor();
13427 let head = selection.head();
13428 let tail = selection.tail();
13429
13430 let Some((buffer, start_position)) =
13431 self.buffer.read(cx).text_anchor_for_position(head, cx)
13432 else {
13433 return;
13434 };
13435
13436 let end_position = if head != tail {
13437 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13438 return;
13439 };
13440 Some(pos)
13441 } else {
13442 None
13443 };
13444
13445 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13446 let url = if let Some(end_pos) = end_position {
13447 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13448 } else {
13449 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13450 };
13451
13452 if let Some(url) = url {
13453 editor.update(cx, |_, cx| {
13454 cx.open_url(&url);
13455 })
13456 } else {
13457 Ok(())
13458 }
13459 });
13460
13461 url_finder.detach();
13462 }
13463
13464 pub fn open_selected_filename(
13465 &mut self,
13466 _: &OpenSelectedFilename,
13467 window: &mut Window,
13468 cx: &mut Context<Self>,
13469 ) {
13470 let Some(workspace) = self.workspace() else {
13471 return;
13472 };
13473
13474 let position = self.selections.newest_anchor().head();
13475
13476 let Some((buffer, buffer_position)) =
13477 self.buffer.read(cx).text_anchor_for_position(position, cx)
13478 else {
13479 return;
13480 };
13481
13482 let project = self.project.clone();
13483
13484 cx.spawn_in(window, async move |_, cx| {
13485 let result = find_file(&buffer, project, buffer_position, cx).await;
13486
13487 if let Some((_, path)) = result {
13488 workspace
13489 .update_in(cx, |workspace, window, cx| {
13490 workspace.open_resolved_path(path, window, cx)
13491 })?
13492 .await?;
13493 }
13494 anyhow::Ok(())
13495 })
13496 .detach();
13497 }
13498
13499 pub(crate) fn navigate_to_hover_links(
13500 &mut self,
13501 kind: Option<GotoDefinitionKind>,
13502 mut definitions: Vec<HoverLink>,
13503 split: bool,
13504 window: &mut Window,
13505 cx: &mut Context<Editor>,
13506 ) -> Task<Result<Navigated>> {
13507 // If there is one definition, just open it directly
13508 if definitions.len() == 1 {
13509 let definition = definitions.pop().unwrap();
13510
13511 enum TargetTaskResult {
13512 Location(Option<Location>),
13513 AlreadyNavigated,
13514 }
13515
13516 let target_task = match definition {
13517 HoverLink::Text(link) => {
13518 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
13519 }
13520 HoverLink::InlayHint(lsp_location, server_id) => {
13521 let computation =
13522 self.compute_target_location(lsp_location, server_id, window, cx);
13523 cx.background_spawn(async move {
13524 let location = computation.await?;
13525 Ok(TargetTaskResult::Location(location))
13526 })
13527 }
13528 HoverLink::Url(url) => {
13529 cx.open_url(&url);
13530 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
13531 }
13532 HoverLink::File(path) => {
13533 if let Some(workspace) = self.workspace() {
13534 cx.spawn_in(window, async move |_, cx| {
13535 workspace
13536 .update_in(cx, |workspace, window, cx| {
13537 workspace.open_resolved_path(path, window, cx)
13538 })?
13539 .await
13540 .map(|_| TargetTaskResult::AlreadyNavigated)
13541 })
13542 } else {
13543 Task::ready(Ok(TargetTaskResult::Location(None)))
13544 }
13545 }
13546 };
13547 cx.spawn_in(window, async move |editor, cx| {
13548 let target = match target_task.await.context("target resolution task")? {
13549 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
13550 TargetTaskResult::Location(None) => return Ok(Navigated::No),
13551 TargetTaskResult::Location(Some(target)) => target,
13552 };
13553
13554 editor.update_in(cx, |editor, window, cx| {
13555 let Some(workspace) = editor.workspace() else {
13556 return Navigated::No;
13557 };
13558 let pane = workspace.read(cx).active_pane().clone();
13559
13560 let range = target.range.to_point(target.buffer.read(cx));
13561 let range = editor.range_for_match(&range);
13562 let range = collapse_multiline_range(range);
13563
13564 if !split
13565 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
13566 {
13567 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
13568 } else {
13569 window.defer(cx, move |window, cx| {
13570 let target_editor: Entity<Self> =
13571 workspace.update(cx, |workspace, cx| {
13572 let pane = if split {
13573 workspace.adjacent_pane(window, cx)
13574 } else {
13575 workspace.active_pane().clone()
13576 };
13577
13578 workspace.open_project_item(
13579 pane,
13580 target.buffer.clone(),
13581 true,
13582 true,
13583 window,
13584 cx,
13585 )
13586 });
13587 target_editor.update(cx, |target_editor, cx| {
13588 // When selecting a definition in a different buffer, disable the nav history
13589 // to avoid creating a history entry at the previous cursor location.
13590 pane.update(cx, |pane, _| pane.disable_history());
13591 target_editor.go_to_singleton_buffer_range(range, window, cx);
13592 pane.update(cx, |pane, _| pane.enable_history());
13593 });
13594 });
13595 }
13596 Navigated::Yes
13597 })
13598 })
13599 } else if !definitions.is_empty() {
13600 cx.spawn_in(window, async move |editor, cx| {
13601 let (title, location_tasks, workspace) = editor
13602 .update_in(cx, |editor, window, cx| {
13603 let tab_kind = match kind {
13604 Some(GotoDefinitionKind::Implementation) => "Implementations",
13605 _ => "Definitions",
13606 };
13607 let title = definitions
13608 .iter()
13609 .find_map(|definition| match definition {
13610 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
13611 let buffer = origin.buffer.read(cx);
13612 format!(
13613 "{} for {}",
13614 tab_kind,
13615 buffer
13616 .text_for_range(origin.range.clone())
13617 .collect::<String>()
13618 )
13619 }),
13620 HoverLink::InlayHint(_, _) => None,
13621 HoverLink::Url(_) => None,
13622 HoverLink::File(_) => None,
13623 })
13624 .unwrap_or(tab_kind.to_string());
13625 let location_tasks = definitions
13626 .into_iter()
13627 .map(|definition| match definition {
13628 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
13629 HoverLink::InlayHint(lsp_location, server_id) => editor
13630 .compute_target_location(lsp_location, server_id, window, cx),
13631 HoverLink::Url(_) => Task::ready(Ok(None)),
13632 HoverLink::File(_) => Task::ready(Ok(None)),
13633 })
13634 .collect::<Vec<_>>();
13635 (title, location_tasks, editor.workspace().clone())
13636 })
13637 .context("location tasks preparation")?;
13638
13639 let locations = future::join_all(location_tasks)
13640 .await
13641 .into_iter()
13642 .filter_map(|location| location.transpose())
13643 .collect::<Result<_>>()
13644 .context("location tasks")?;
13645
13646 let Some(workspace) = workspace else {
13647 return Ok(Navigated::No);
13648 };
13649 let opened = workspace
13650 .update_in(cx, |workspace, window, cx| {
13651 Self::open_locations_in_multibuffer(
13652 workspace,
13653 locations,
13654 title,
13655 split,
13656 MultibufferSelectionMode::First,
13657 window,
13658 cx,
13659 )
13660 })
13661 .ok();
13662
13663 anyhow::Ok(Navigated::from_bool(opened.is_some()))
13664 })
13665 } else {
13666 Task::ready(Ok(Navigated::No))
13667 }
13668 }
13669
13670 fn compute_target_location(
13671 &self,
13672 lsp_location: lsp::Location,
13673 server_id: LanguageServerId,
13674 window: &mut Window,
13675 cx: &mut Context<Self>,
13676 ) -> Task<anyhow::Result<Option<Location>>> {
13677 let Some(project) = self.project.clone() else {
13678 return Task::ready(Ok(None));
13679 };
13680
13681 cx.spawn_in(window, async move |editor, cx| {
13682 let location_task = editor.update(cx, |_, cx| {
13683 project.update(cx, |project, cx| {
13684 let language_server_name = project
13685 .language_server_statuses(cx)
13686 .find(|(id, _)| server_id == *id)
13687 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
13688 language_server_name.map(|language_server_name| {
13689 project.open_local_buffer_via_lsp(
13690 lsp_location.uri.clone(),
13691 server_id,
13692 language_server_name,
13693 cx,
13694 )
13695 })
13696 })
13697 })?;
13698 let location = match location_task {
13699 Some(task) => Some({
13700 let target_buffer_handle = task.await.context("open local buffer")?;
13701 let range = target_buffer_handle.update(cx, |target_buffer, _| {
13702 let target_start = target_buffer
13703 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
13704 let target_end = target_buffer
13705 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
13706 target_buffer.anchor_after(target_start)
13707 ..target_buffer.anchor_before(target_end)
13708 })?;
13709 Location {
13710 buffer: target_buffer_handle,
13711 range,
13712 }
13713 }),
13714 None => None,
13715 };
13716 Ok(location)
13717 })
13718 }
13719
13720 pub fn find_all_references(
13721 &mut self,
13722 _: &FindAllReferences,
13723 window: &mut Window,
13724 cx: &mut Context<Self>,
13725 ) -> Option<Task<Result<Navigated>>> {
13726 let selection = self.selections.newest::<usize>(cx);
13727 let multi_buffer = self.buffer.read(cx);
13728 let head = selection.head();
13729
13730 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
13731 let head_anchor = multi_buffer_snapshot.anchor_at(
13732 head,
13733 if head < selection.tail() {
13734 Bias::Right
13735 } else {
13736 Bias::Left
13737 },
13738 );
13739
13740 match self
13741 .find_all_references_task_sources
13742 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13743 {
13744 Ok(_) => {
13745 log::info!(
13746 "Ignoring repeated FindAllReferences invocation with the position of already running task"
13747 );
13748 return None;
13749 }
13750 Err(i) => {
13751 self.find_all_references_task_sources.insert(i, head_anchor);
13752 }
13753 }
13754
13755 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
13756 let workspace = self.workspace()?;
13757 let project = workspace.read(cx).project().clone();
13758 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
13759 Some(cx.spawn_in(window, async move |editor, cx| {
13760 let _cleanup = cx.on_drop(&editor, move |editor, _| {
13761 if let Ok(i) = editor
13762 .find_all_references_task_sources
13763 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
13764 {
13765 editor.find_all_references_task_sources.remove(i);
13766 }
13767 });
13768
13769 let locations = references.await?;
13770 if locations.is_empty() {
13771 return anyhow::Ok(Navigated::No);
13772 }
13773
13774 workspace.update_in(cx, |workspace, window, cx| {
13775 let title = locations
13776 .first()
13777 .as_ref()
13778 .map(|location| {
13779 let buffer = location.buffer.read(cx);
13780 format!(
13781 "References to `{}`",
13782 buffer
13783 .text_for_range(location.range.clone())
13784 .collect::<String>()
13785 )
13786 })
13787 .unwrap();
13788 Self::open_locations_in_multibuffer(
13789 workspace,
13790 locations,
13791 title,
13792 false,
13793 MultibufferSelectionMode::First,
13794 window,
13795 cx,
13796 );
13797 Navigated::Yes
13798 })
13799 }))
13800 }
13801
13802 /// Opens a multibuffer with the given project locations in it
13803 pub fn open_locations_in_multibuffer(
13804 workspace: &mut Workspace,
13805 mut locations: Vec<Location>,
13806 title: String,
13807 split: bool,
13808 multibuffer_selection_mode: MultibufferSelectionMode,
13809 window: &mut Window,
13810 cx: &mut Context<Workspace>,
13811 ) {
13812 // If there are multiple definitions, open them in a multibuffer
13813 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
13814 let mut locations = locations.into_iter().peekable();
13815 let mut ranges: Vec<Range<Anchor>> = Vec::new();
13816 let capability = workspace.project().read(cx).capability();
13817
13818 let excerpt_buffer = cx.new(|cx| {
13819 let mut multibuffer = MultiBuffer::new(capability);
13820 while let Some(location) = locations.next() {
13821 let buffer = location.buffer.read(cx);
13822 let mut ranges_for_buffer = Vec::new();
13823 let range = location.range.to_point(buffer);
13824 ranges_for_buffer.push(range.clone());
13825
13826 while let Some(next_location) = locations.peek() {
13827 if next_location.buffer == location.buffer {
13828 ranges_for_buffer.push(next_location.range.to_point(buffer));
13829 locations.next();
13830 } else {
13831 break;
13832 }
13833 }
13834
13835 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
13836 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
13837 PathKey::for_buffer(&location.buffer, cx),
13838 location.buffer.clone(),
13839 ranges_for_buffer,
13840 DEFAULT_MULTIBUFFER_CONTEXT,
13841 cx,
13842 );
13843 ranges.extend(new_ranges)
13844 }
13845
13846 multibuffer.with_title(title)
13847 });
13848
13849 let editor = cx.new(|cx| {
13850 Editor::for_multibuffer(
13851 excerpt_buffer,
13852 Some(workspace.project().clone()),
13853 window,
13854 cx,
13855 )
13856 });
13857 editor.update(cx, |editor, cx| {
13858 match multibuffer_selection_mode {
13859 MultibufferSelectionMode::First => {
13860 if let Some(first_range) = ranges.first() {
13861 editor.change_selections(None, window, cx, |selections| {
13862 selections.clear_disjoint();
13863 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
13864 });
13865 }
13866 editor.highlight_background::<Self>(
13867 &ranges,
13868 |theme| theme.editor_highlighted_line_background,
13869 cx,
13870 );
13871 }
13872 MultibufferSelectionMode::All => {
13873 editor.change_selections(None, window, cx, |selections| {
13874 selections.clear_disjoint();
13875 selections.select_anchor_ranges(ranges);
13876 });
13877 }
13878 }
13879 editor.register_buffers_with_language_servers(cx);
13880 });
13881
13882 let item = Box::new(editor);
13883 let item_id = item.item_id();
13884
13885 if split {
13886 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
13887 } else {
13888 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
13889 let (preview_item_id, preview_item_idx) =
13890 workspace.active_pane().update(cx, |pane, _| {
13891 (pane.preview_item_id(), pane.preview_item_idx())
13892 });
13893
13894 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
13895
13896 if let Some(preview_item_id) = preview_item_id {
13897 workspace.active_pane().update(cx, |pane, cx| {
13898 pane.remove_item(preview_item_id, false, false, window, cx);
13899 });
13900 }
13901 } else {
13902 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
13903 }
13904 }
13905 workspace.active_pane().update(cx, |pane, cx| {
13906 pane.set_preview_item_id(Some(item_id), cx);
13907 });
13908 }
13909
13910 pub fn rename(
13911 &mut self,
13912 _: &Rename,
13913 window: &mut Window,
13914 cx: &mut Context<Self>,
13915 ) -> Option<Task<Result<()>>> {
13916 use language::ToOffset as _;
13917
13918 let provider = self.semantics_provider.clone()?;
13919 let selection = self.selections.newest_anchor().clone();
13920 let (cursor_buffer, cursor_buffer_position) = self
13921 .buffer
13922 .read(cx)
13923 .text_anchor_for_position(selection.head(), cx)?;
13924 let (tail_buffer, cursor_buffer_position_end) = self
13925 .buffer
13926 .read(cx)
13927 .text_anchor_for_position(selection.tail(), cx)?;
13928 if tail_buffer != cursor_buffer {
13929 return None;
13930 }
13931
13932 let snapshot = cursor_buffer.read(cx).snapshot();
13933 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
13934 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
13935 let prepare_rename = provider
13936 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
13937 .unwrap_or_else(|| Task::ready(Ok(None)));
13938 drop(snapshot);
13939
13940 Some(cx.spawn_in(window, async move |this, cx| {
13941 let rename_range = if let Some(range) = prepare_rename.await? {
13942 Some(range)
13943 } else {
13944 this.update(cx, |this, cx| {
13945 let buffer = this.buffer.read(cx).snapshot(cx);
13946 let mut buffer_highlights = this
13947 .document_highlights_for_position(selection.head(), &buffer)
13948 .filter(|highlight| {
13949 highlight.start.excerpt_id == selection.head().excerpt_id
13950 && highlight.end.excerpt_id == selection.head().excerpt_id
13951 });
13952 buffer_highlights
13953 .next()
13954 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
13955 })?
13956 };
13957 if let Some(rename_range) = rename_range {
13958 this.update_in(cx, |this, window, cx| {
13959 let snapshot = cursor_buffer.read(cx).snapshot();
13960 let rename_buffer_range = rename_range.to_offset(&snapshot);
13961 let cursor_offset_in_rename_range =
13962 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
13963 let cursor_offset_in_rename_range_end =
13964 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
13965
13966 this.take_rename(false, window, cx);
13967 let buffer = this.buffer.read(cx).read(cx);
13968 let cursor_offset = selection.head().to_offset(&buffer);
13969 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
13970 let rename_end = rename_start + rename_buffer_range.len();
13971 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
13972 let mut old_highlight_id = None;
13973 let old_name: Arc<str> = buffer
13974 .chunks(rename_start..rename_end, true)
13975 .map(|chunk| {
13976 if old_highlight_id.is_none() {
13977 old_highlight_id = chunk.syntax_highlight_id;
13978 }
13979 chunk.text
13980 })
13981 .collect::<String>()
13982 .into();
13983
13984 drop(buffer);
13985
13986 // Position the selection in the rename editor so that it matches the current selection.
13987 this.show_local_selections = false;
13988 let rename_editor = cx.new(|cx| {
13989 let mut editor = Editor::single_line(window, cx);
13990 editor.buffer.update(cx, |buffer, cx| {
13991 buffer.edit([(0..0, old_name.clone())], None, cx)
13992 });
13993 let rename_selection_range = match cursor_offset_in_rename_range
13994 .cmp(&cursor_offset_in_rename_range_end)
13995 {
13996 Ordering::Equal => {
13997 editor.select_all(&SelectAll, window, cx);
13998 return editor;
13999 }
14000 Ordering::Less => {
14001 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14002 }
14003 Ordering::Greater => {
14004 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14005 }
14006 };
14007 if rename_selection_range.end > old_name.len() {
14008 editor.select_all(&SelectAll, window, cx);
14009 } else {
14010 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14011 s.select_ranges([rename_selection_range]);
14012 });
14013 }
14014 editor
14015 });
14016 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14017 if e == &EditorEvent::Focused {
14018 cx.emit(EditorEvent::FocusedIn)
14019 }
14020 })
14021 .detach();
14022
14023 let write_highlights =
14024 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14025 let read_highlights =
14026 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14027 let ranges = write_highlights
14028 .iter()
14029 .flat_map(|(_, ranges)| ranges.iter())
14030 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14031 .cloned()
14032 .collect();
14033
14034 this.highlight_text::<Rename>(
14035 ranges,
14036 HighlightStyle {
14037 fade_out: Some(0.6),
14038 ..Default::default()
14039 },
14040 cx,
14041 );
14042 let rename_focus_handle = rename_editor.focus_handle(cx);
14043 window.focus(&rename_focus_handle);
14044 let block_id = this.insert_blocks(
14045 [BlockProperties {
14046 style: BlockStyle::Flex,
14047 placement: BlockPlacement::Below(range.start),
14048 height: Some(1),
14049 render: Arc::new({
14050 let rename_editor = rename_editor.clone();
14051 move |cx: &mut BlockContext| {
14052 let mut text_style = cx.editor_style.text.clone();
14053 if let Some(highlight_style) = old_highlight_id
14054 .and_then(|h| h.style(&cx.editor_style.syntax))
14055 {
14056 text_style = text_style.highlight(highlight_style);
14057 }
14058 div()
14059 .block_mouse_down()
14060 .pl(cx.anchor_x)
14061 .child(EditorElement::new(
14062 &rename_editor,
14063 EditorStyle {
14064 background: cx.theme().system().transparent,
14065 local_player: cx.editor_style.local_player,
14066 text: text_style,
14067 scrollbar_width: cx.editor_style.scrollbar_width,
14068 syntax: cx.editor_style.syntax.clone(),
14069 status: cx.editor_style.status.clone(),
14070 inlay_hints_style: HighlightStyle {
14071 font_weight: Some(FontWeight::BOLD),
14072 ..make_inlay_hints_style(cx.app)
14073 },
14074 inline_completion_styles: make_suggestion_styles(
14075 cx.app,
14076 ),
14077 ..EditorStyle::default()
14078 },
14079 ))
14080 .into_any_element()
14081 }
14082 }),
14083 priority: 0,
14084 }],
14085 Some(Autoscroll::fit()),
14086 cx,
14087 )[0];
14088 this.pending_rename = Some(RenameState {
14089 range,
14090 old_name,
14091 editor: rename_editor,
14092 block_id,
14093 });
14094 })?;
14095 }
14096
14097 Ok(())
14098 }))
14099 }
14100
14101 pub fn confirm_rename(
14102 &mut self,
14103 _: &ConfirmRename,
14104 window: &mut Window,
14105 cx: &mut Context<Self>,
14106 ) -> Option<Task<Result<()>>> {
14107 let rename = self.take_rename(false, window, cx)?;
14108 let workspace = self.workspace()?.downgrade();
14109 let (buffer, start) = self
14110 .buffer
14111 .read(cx)
14112 .text_anchor_for_position(rename.range.start, cx)?;
14113 let (end_buffer, _) = self
14114 .buffer
14115 .read(cx)
14116 .text_anchor_for_position(rename.range.end, cx)?;
14117 if buffer != end_buffer {
14118 return None;
14119 }
14120
14121 let old_name = rename.old_name;
14122 let new_name = rename.editor.read(cx).text(cx);
14123
14124 let rename = self.semantics_provider.as_ref()?.perform_rename(
14125 &buffer,
14126 start,
14127 new_name.clone(),
14128 cx,
14129 )?;
14130
14131 Some(cx.spawn_in(window, async move |editor, cx| {
14132 let project_transaction = rename.await?;
14133 Self::open_project_transaction(
14134 &editor,
14135 workspace,
14136 project_transaction,
14137 format!("Rename: {} → {}", old_name, new_name),
14138 cx,
14139 )
14140 .await?;
14141
14142 editor.update(cx, |editor, cx| {
14143 editor.refresh_document_highlights(cx);
14144 })?;
14145 Ok(())
14146 }))
14147 }
14148
14149 fn take_rename(
14150 &mut self,
14151 moving_cursor: bool,
14152 window: &mut Window,
14153 cx: &mut Context<Self>,
14154 ) -> Option<RenameState> {
14155 let rename = self.pending_rename.take()?;
14156 if rename.editor.focus_handle(cx).is_focused(window) {
14157 window.focus(&self.focus_handle);
14158 }
14159
14160 self.remove_blocks(
14161 [rename.block_id].into_iter().collect(),
14162 Some(Autoscroll::fit()),
14163 cx,
14164 );
14165 self.clear_highlights::<Rename>(cx);
14166 self.show_local_selections = true;
14167
14168 if moving_cursor {
14169 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14170 editor.selections.newest::<usize>(cx).head()
14171 });
14172
14173 // Update the selection to match the position of the selection inside
14174 // the rename editor.
14175 let snapshot = self.buffer.read(cx).read(cx);
14176 let rename_range = rename.range.to_offset(&snapshot);
14177 let cursor_in_editor = snapshot
14178 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14179 .min(rename_range.end);
14180 drop(snapshot);
14181
14182 self.change_selections(None, window, cx, |s| {
14183 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14184 });
14185 } else {
14186 self.refresh_document_highlights(cx);
14187 }
14188
14189 Some(rename)
14190 }
14191
14192 pub fn pending_rename(&self) -> Option<&RenameState> {
14193 self.pending_rename.as_ref()
14194 }
14195
14196 fn format(
14197 &mut self,
14198 _: &Format,
14199 window: &mut Window,
14200 cx: &mut Context<Self>,
14201 ) -> Option<Task<Result<()>>> {
14202 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14203
14204 let project = match &self.project {
14205 Some(project) => project.clone(),
14206 None => return None,
14207 };
14208
14209 Some(self.perform_format(
14210 project,
14211 FormatTrigger::Manual,
14212 FormatTarget::Buffers,
14213 window,
14214 cx,
14215 ))
14216 }
14217
14218 fn format_selections(
14219 &mut self,
14220 _: &FormatSelections,
14221 window: &mut Window,
14222 cx: &mut Context<Self>,
14223 ) -> Option<Task<Result<()>>> {
14224 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14225
14226 let project = match &self.project {
14227 Some(project) => project.clone(),
14228 None => return None,
14229 };
14230
14231 let ranges = self
14232 .selections
14233 .all_adjusted(cx)
14234 .into_iter()
14235 .map(|selection| selection.range())
14236 .collect_vec();
14237
14238 Some(self.perform_format(
14239 project,
14240 FormatTrigger::Manual,
14241 FormatTarget::Ranges(ranges),
14242 window,
14243 cx,
14244 ))
14245 }
14246
14247 fn perform_format(
14248 &mut self,
14249 project: Entity<Project>,
14250 trigger: FormatTrigger,
14251 target: FormatTarget,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) -> Task<Result<()>> {
14255 let buffer = self.buffer.clone();
14256 let (buffers, target) = match target {
14257 FormatTarget::Buffers => {
14258 let mut buffers = buffer.read(cx).all_buffers();
14259 if trigger == FormatTrigger::Save {
14260 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14261 }
14262 (buffers, LspFormatTarget::Buffers)
14263 }
14264 FormatTarget::Ranges(selection_ranges) => {
14265 let multi_buffer = buffer.read(cx);
14266 let snapshot = multi_buffer.read(cx);
14267 let mut buffers = HashSet::default();
14268 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14269 BTreeMap::new();
14270 for selection_range in selection_ranges {
14271 for (buffer, buffer_range, _) in
14272 snapshot.range_to_buffer_ranges(selection_range)
14273 {
14274 let buffer_id = buffer.remote_id();
14275 let start = buffer.anchor_before(buffer_range.start);
14276 let end = buffer.anchor_after(buffer_range.end);
14277 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14278 buffer_id_to_ranges
14279 .entry(buffer_id)
14280 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14281 .or_insert_with(|| vec![start..end]);
14282 }
14283 }
14284 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14285 }
14286 };
14287
14288 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14289 let selections_prev = transaction_id_prev
14290 .and_then(|transaction_id_prev| {
14291 // default to selections as they were after the last edit, if we have them,
14292 // instead of how they are now.
14293 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14294 // will take you back to where you made the last edit, instead of staying where you scrolled
14295 self.selection_history
14296 .transaction(transaction_id_prev)
14297 .map(|t| t.0.clone())
14298 })
14299 .unwrap_or_else(|| {
14300 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14301 self.selections.disjoint_anchors()
14302 });
14303
14304 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14305 let format = project.update(cx, |project, cx| {
14306 project.format(buffers, target, true, trigger, cx)
14307 });
14308
14309 cx.spawn_in(window, async move |editor, cx| {
14310 let transaction = futures::select_biased! {
14311 transaction = format.log_err().fuse() => transaction,
14312 () = timeout => {
14313 log::warn!("timed out waiting for formatting");
14314 None
14315 }
14316 };
14317
14318 buffer
14319 .update(cx, |buffer, cx| {
14320 if let Some(transaction) = transaction {
14321 if !buffer.is_singleton() {
14322 buffer.push_transaction(&transaction.0, cx);
14323 }
14324 }
14325 cx.notify();
14326 })
14327 .ok();
14328
14329 if let Some(transaction_id_now) =
14330 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14331 {
14332 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14333 if has_new_transaction {
14334 _ = editor.update(cx, |editor, _| {
14335 editor
14336 .selection_history
14337 .insert_transaction(transaction_id_now, selections_prev);
14338 });
14339 }
14340 }
14341
14342 Ok(())
14343 })
14344 }
14345
14346 fn organize_imports(
14347 &mut self,
14348 _: &OrganizeImports,
14349 window: &mut Window,
14350 cx: &mut Context<Self>,
14351 ) -> Option<Task<Result<()>>> {
14352 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14353 let project = match &self.project {
14354 Some(project) => project.clone(),
14355 None => return None,
14356 };
14357 Some(self.perform_code_action_kind(
14358 project,
14359 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14360 window,
14361 cx,
14362 ))
14363 }
14364
14365 fn perform_code_action_kind(
14366 &mut self,
14367 project: Entity<Project>,
14368 kind: CodeActionKind,
14369 window: &mut Window,
14370 cx: &mut Context<Self>,
14371 ) -> Task<Result<()>> {
14372 let buffer = self.buffer.clone();
14373 let buffers = buffer.read(cx).all_buffers();
14374 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14375 let apply_action = project.update(cx, |project, cx| {
14376 project.apply_code_action_kind(buffers, kind, true, cx)
14377 });
14378 cx.spawn_in(window, async move |_, cx| {
14379 let transaction = futures::select_biased! {
14380 () = timeout => {
14381 log::warn!("timed out waiting for executing code action");
14382 None
14383 }
14384 transaction = apply_action.log_err().fuse() => transaction,
14385 };
14386 buffer
14387 .update(cx, |buffer, cx| {
14388 // check if we need this
14389 if let Some(transaction) = transaction {
14390 if !buffer.is_singleton() {
14391 buffer.push_transaction(&transaction.0, cx);
14392 }
14393 }
14394 cx.notify();
14395 })
14396 .ok();
14397 Ok(())
14398 })
14399 }
14400
14401 fn restart_language_server(
14402 &mut self,
14403 _: &RestartLanguageServer,
14404 _: &mut Window,
14405 cx: &mut Context<Self>,
14406 ) {
14407 if let Some(project) = self.project.clone() {
14408 self.buffer.update(cx, |multi_buffer, cx| {
14409 project.update(cx, |project, cx| {
14410 project.restart_language_servers_for_buffers(
14411 multi_buffer.all_buffers().into_iter().collect(),
14412 cx,
14413 );
14414 });
14415 })
14416 }
14417 }
14418
14419 fn stop_language_server(
14420 &mut self,
14421 _: &StopLanguageServer,
14422 _: &mut Window,
14423 cx: &mut Context<Self>,
14424 ) {
14425 if let Some(project) = self.project.clone() {
14426 self.buffer.update(cx, |multi_buffer, cx| {
14427 project.update(cx, |project, cx| {
14428 project.stop_language_servers_for_buffers(
14429 multi_buffer.all_buffers().into_iter().collect(),
14430 cx,
14431 );
14432 cx.emit(project::Event::RefreshInlayHints);
14433 });
14434 });
14435 }
14436 }
14437
14438 fn cancel_language_server_work(
14439 workspace: &mut Workspace,
14440 _: &actions::CancelLanguageServerWork,
14441 _: &mut Window,
14442 cx: &mut Context<Workspace>,
14443 ) {
14444 let project = workspace.project();
14445 let buffers = workspace
14446 .active_item(cx)
14447 .and_then(|item| item.act_as::<Editor>(cx))
14448 .map_or(HashSet::default(), |editor| {
14449 editor.read(cx).buffer.read(cx).all_buffers()
14450 });
14451 project.update(cx, |project, cx| {
14452 project.cancel_language_server_work_for_buffers(buffers, cx);
14453 });
14454 }
14455
14456 fn show_character_palette(
14457 &mut self,
14458 _: &ShowCharacterPalette,
14459 window: &mut Window,
14460 _: &mut Context<Self>,
14461 ) {
14462 window.show_character_palette();
14463 }
14464
14465 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14466 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14467 let buffer = self.buffer.read(cx).snapshot(cx);
14468 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14469 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14470 let is_valid = buffer
14471 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14472 .any(|entry| {
14473 entry.diagnostic.is_primary
14474 && !entry.range.is_empty()
14475 && entry.range.start == primary_range_start
14476 && entry.diagnostic.message == active_diagnostics.active_message
14477 });
14478
14479 if !is_valid {
14480 self.dismiss_diagnostics(cx);
14481 }
14482 }
14483 }
14484
14485 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14486 match &self.active_diagnostics {
14487 ActiveDiagnostic::Group(group) => Some(group),
14488 _ => None,
14489 }
14490 }
14491
14492 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14493 self.dismiss_diagnostics(cx);
14494 self.active_diagnostics = ActiveDiagnostic::All;
14495 }
14496
14497 fn activate_diagnostics(
14498 &mut self,
14499 buffer_id: BufferId,
14500 diagnostic: DiagnosticEntry<usize>,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14505 return;
14506 }
14507 self.dismiss_diagnostics(cx);
14508 let snapshot = self.snapshot(window, cx);
14509 let Some(diagnostic_renderer) = cx
14510 .try_global::<GlobalDiagnosticRenderer>()
14511 .map(|g| g.0.clone())
14512 else {
14513 return;
14514 };
14515 let buffer = self.buffer.read(cx).snapshot(cx);
14516
14517 let diagnostic_group = buffer
14518 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
14519 .collect::<Vec<_>>();
14520
14521 let blocks = diagnostic_renderer.render_group(
14522 diagnostic_group,
14523 buffer_id,
14524 snapshot,
14525 cx.weak_entity(),
14526 cx,
14527 );
14528
14529 let blocks = self.display_map.update(cx, |display_map, cx| {
14530 display_map.insert_blocks(blocks, cx).into_iter().collect()
14531 });
14532 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
14533 active_range: buffer.anchor_before(diagnostic.range.start)
14534 ..buffer.anchor_after(diagnostic.range.end),
14535 active_message: diagnostic.diagnostic.message.clone(),
14536 group_id: diagnostic.diagnostic.group_id,
14537 blocks,
14538 });
14539 cx.notify();
14540 }
14541
14542 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
14543 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
14544 return;
14545 };
14546
14547 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
14548 if let ActiveDiagnostic::Group(group) = prev {
14549 self.display_map.update(cx, |display_map, cx| {
14550 display_map.remove_blocks(group.blocks, cx);
14551 });
14552 cx.notify();
14553 }
14554 }
14555
14556 /// Disable inline diagnostics rendering for this editor.
14557 pub fn disable_inline_diagnostics(&mut self) {
14558 self.inline_diagnostics_enabled = false;
14559 self.inline_diagnostics_update = Task::ready(());
14560 self.inline_diagnostics.clear();
14561 }
14562
14563 pub fn inline_diagnostics_enabled(&self) -> bool {
14564 self.inline_diagnostics_enabled
14565 }
14566
14567 pub fn show_inline_diagnostics(&self) -> bool {
14568 self.show_inline_diagnostics
14569 }
14570
14571 pub fn toggle_inline_diagnostics(
14572 &mut self,
14573 _: &ToggleInlineDiagnostics,
14574 window: &mut Window,
14575 cx: &mut Context<Editor>,
14576 ) {
14577 self.show_inline_diagnostics = !self.show_inline_diagnostics;
14578 self.refresh_inline_diagnostics(false, window, cx);
14579 }
14580
14581 fn refresh_inline_diagnostics(
14582 &mut self,
14583 debounce: bool,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) {
14587 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
14588 self.inline_diagnostics_update = Task::ready(());
14589 self.inline_diagnostics.clear();
14590 return;
14591 }
14592
14593 let debounce_ms = ProjectSettings::get_global(cx)
14594 .diagnostics
14595 .inline
14596 .update_debounce_ms;
14597 let debounce = if debounce && debounce_ms > 0 {
14598 Some(Duration::from_millis(debounce_ms))
14599 } else {
14600 None
14601 };
14602 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
14603 let editor = editor.upgrade().unwrap();
14604
14605 if let Some(debounce) = debounce {
14606 cx.background_executor().timer(debounce).await;
14607 }
14608 let Some(snapshot) = editor
14609 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
14610 .ok()
14611 else {
14612 return;
14613 };
14614
14615 let new_inline_diagnostics = cx
14616 .background_spawn(async move {
14617 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
14618 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
14619 let message = diagnostic_entry
14620 .diagnostic
14621 .message
14622 .split_once('\n')
14623 .map(|(line, _)| line)
14624 .map(SharedString::new)
14625 .unwrap_or_else(|| {
14626 SharedString::from(diagnostic_entry.diagnostic.message)
14627 });
14628 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
14629 let (Ok(i) | Err(i)) = inline_diagnostics
14630 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
14631 inline_diagnostics.insert(
14632 i,
14633 (
14634 start_anchor,
14635 InlineDiagnostic {
14636 message,
14637 group_id: diagnostic_entry.diagnostic.group_id,
14638 start: diagnostic_entry.range.start.to_point(&snapshot),
14639 is_primary: diagnostic_entry.diagnostic.is_primary,
14640 severity: diagnostic_entry.diagnostic.severity,
14641 },
14642 ),
14643 );
14644 }
14645 inline_diagnostics
14646 })
14647 .await;
14648
14649 editor
14650 .update(cx, |editor, cx| {
14651 editor.inline_diagnostics = new_inline_diagnostics;
14652 cx.notify();
14653 })
14654 .ok();
14655 });
14656 }
14657
14658 pub fn set_selections_from_remote(
14659 &mut self,
14660 selections: Vec<Selection<Anchor>>,
14661 pending_selection: Option<Selection<Anchor>>,
14662 window: &mut Window,
14663 cx: &mut Context<Self>,
14664 ) {
14665 let old_cursor_position = self.selections.newest_anchor().head();
14666 self.selections.change_with(cx, |s| {
14667 s.select_anchors(selections);
14668 if let Some(pending_selection) = pending_selection {
14669 s.set_pending(pending_selection, SelectMode::Character);
14670 } else {
14671 s.clear_pending();
14672 }
14673 });
14674 self.selections_did_change(false, &old_cursor_position, true, window, cx);
14675 }
14676
14677 fn push_to_selection_history(&mut self) {
14678 self.selection_history.push(SelectionHistoryEntry {
14679 selections: self.selections.disjoint_anchors(),
14680 select_next_state: self.select_next_state.clone(),
14681 select_prev_state: self.select_prev_state.clone(),
14682 add_selections_state: self.add_selections_state.clone(),
14683 });
14684 }
14685
14686 pub fn transact(
14687 &mut self,
14688 window: &mut Window,
14689 cx: &mut Context<Self>,
14690 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
14691 ) -> Option<TransactionId> {
14692 self.start_transaction_at(Instant::now(), window, cx);
14693 update(self, window, cx);
14694 self.end_transaction_at(Instant::now(), cx)
14695 }
14696
14697 pub fn start_transaction_at(
14698 &mut self,
14699 now: Instant,
14700 window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) {
14703 self.end_selection(window, cx);
14704 if let Some(tx_id) = self
14705 .buffer
14706 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
14707 {
14708 self.selection_history
14709 .insert_transaction(tx_id, self.selections.disjoint_anchors());
14710 cx.emit(EditorEvent::TransactionBegun {
14711 transaction_id: tx_id,
14712 })
14713 }
14714 }
14715
14716 pub fn end_transaction_at(
14717 &mut self,
14718 now: Instant,
14719 cx: &mut Context<Self>,
14720 ) -> Option<TransactionId> {
14721 if let Some(transaction_id) = self
14722 .buffer
14723 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
14724 {
14725 if let Some((_, end_selections)) =
14726 self.selection_history.transaction_mut(transaction_id)
14727 {
14728 *end_selections = Some(self.selections.disjoint_anchors());
14729 } else {
14730 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
14731 }
14732
14733 cx.emit(EditorEvent::Edited { transaction_id });
14734 Some(transaction_id)
14735 } else {
14736 None
14737 }
14738 }
14739
14740 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
14741 if self.selection_mark_mode {
14742 self.change_selections(None, window, cx, |s| {
14743 s.move_with(|_, sel| {
14744 sel.collapse_to(sel.head(), SelectionGoal::None);
14745 });
14746 })
14747 }
14748 self.selection_mark_mode = true;
14749 cx.notify();
14750 }
14751
14752 pub fn swap_selection_ends(
14753 &mut self,
14754 _: &actions::SwapSelectionEnds,
14755 window: &mut Window,
14756 cx: &mut Context<Self>,
14757 ) {
14758 self.change_selections(None, window, cx, |s| {
14759 s.move_with(|_, sel| {
14760 if sel.start != sel.end {
14761 sel.reversed = !sel.reversed
14762 }
14763 });
14764 });
14765 self.request_autoscroll(Autoscroll::newest(), cx);
14766 cx.notify();
14767 }
14768
14769 pub fn toggle_fold(
14770 &mut self,
14771 _: &actions::ToggleFold,
14772 window: &mut Window,
14773 cx: &mut Context<Self>,
14774 ) {
14775 if self.is_singleton(cx) {
14776 let selection = self.selections.newest::<Point>(cx);
14777
14778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14779 let range = if selection.is_empty() {
14780 let point = selection.head().to_display_point(&display_map);
14781 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14782 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14783 .to_point(&display_map);
14784 start..end
14785 } else {
14786 selection.range()
14787 };
14788 if display_map.folds_in_range(range).next().is_some() {
14789 self.unfold_lines(&Default::default(), window, cx)
14790 } else {
14791 self.fold(&Default::default(), window, cx)
14792 }
14793 } else {
14794 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14795 let buffer_ids: HashSet<_> = self
14796 .selections
14797 .disjoint_anchor_ranges()
14798 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14799 .collect();
14800
14801 let should_unfold = buffer_ids
14802 .iter()
14803 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
14804
14805 for buffer_id in buffer_ids {
14806 if should_unfold {
14807 self.unfold_buffer(buffer_id, cx);
14808 } else {
14809 self.fold_buffer(buffer_id, cx);
14810 }
14811 }
14812 }
14813 }
14814
14815 pub fn toggle_fold_recursive(
14816 &mut self,
14817 _: &actions::ToggleFoldRecursive,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 let selection = self.selections.newest::<Point>(cx);
14822
14823 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14824 let range = if selection.is_empty() {
14825 let point = selection.head().to_display_point(&display_map);
14826 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
14827 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
14828 .to_point(&display_map);
14829 start..end
14830 } else {
14831 selection.range()
14832 };
14833 if display_map.folds_in_range(range).next().is_some() {
14834 self.unfold_recursive(&Default::default(), window, cx)
14835 } else {
14836 self.fold_recursive(&Default::default(), window, cx)
14837 }
14838 }
14839
14840 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
14841 if self.is_singleton(cx) {
14842 let mut to_fold = Vec::new();
14843 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14844 let selections = self.selections.all_adjusted(cx);
14845
14846 for selection in selections {
14847 let range = selection.range().sorted();
14848 let buffer_start_row = range.start.row;
14849
14850 if range.start.row != range.end.row {
14851 let mut found = false;
14852 let mut row = range.start.row;
14853 while row <= range.end.row {
14854 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
14855 {
14856 found = true;
14857 row = crease.range().end.row + 1;
14858 to_fold.push(crease);
14859 } else {
14860 row += 1
14861 }
14862 }
14863 if found {
14864 continue;
14865 }
14866 }
14867
14868 for row in (0..=range.start.row).rev() {
14869 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
14870 if crease.range().end.row >= buffer_start_row {
14871 to_fold.push(crease);
14872 if row <= range.start.row {
14873 break;
14874 }
14875 }
14876 }
14877 }
14878 }
14879
14880 self.fold_creases(to_fold, true, window, cx);
14881 } else {
14882 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
14883 let buffer_ids = self
14884 .selections
14885 .disjoint_anchor_ranges()
14886 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
14887 .collect::<HashSet<_>>();
14888 for buffer_id in buffer_ids {
14889 self.fold_buffer(buffer_id, cx);
14890 }
14891 }
14892 }
14893
14894 fn fold_at_level(
14895 &mut self,
14896 fold_at: &FoldAtLevel,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 if !self.buffer.read(cx).is_singleton() {
14901 return;
14902 }
14903
14904 let fold_at_level = fold_at.0;
14905 let snapshot = self.buffer.read(cx).snapshot(cx);
14906 let mut to_fold = Vec::new();
14907 let mut stack = vec![(0, snapshot.max_row().0, 1)];
14908
14909 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
14910 while start_row < end_row {
14911 match self
14912 .snapshot(window, cx)
14913 .crease_for_buffer_row(MultiBufferRow(start_row))
14914 {
14915 Some(crease) => {
14916 let nested_start_row = crease.range().start.row + 1;
14917 let nested_end_row = crease.range().end.row;
14918
14919 if current_level < fold_at_level {
14920 stack.push((nested_start_row, nested_end_row, current_level + 1));
14921 } else if current_level == fold_at_level {
14922 to_fold.push(crease);
14923 }
14924
14925 start_row = nested_end_row + 1;
14926 }
14927 None => start_row += 1,
14928 }
14929 }
14930 }
14931
14932 self.fold_creases(to_fold, true, window, cx);
14933 }
14934
14935 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
14936 if self.buffer.read(cx).is_singleton() {
14937 let mut fold_ranges = Vec::new();
14938 let snapshot = self.buffer.read(cx).snapshot(cx);
14939
14940 for row in 0..snapshot.max_row().0 {
14941 if let Some(foldable_range) = self
14942 .snapshot(window, cx)
14943 .crease_for_buffer_row(MultiBufferRow(row))
14944 {
14945 fold_ranges.push(foldable_range);
14946 }
14947 }
14948
14949 self.fold_creases(fold_ranges, true, window, cx);
14950 } else {
14951 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
14952 editor
14953 .update_in(cx, |editor, _, cx| {
14954 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
14955 editor.fold_buffer(buffer_id, cx);
14956 }
14957 })
14958 .ok();
14959 });
14960 }
14961 }
14962
14963 pub fn fold_function_bodies(
14964 &mut self,
14965 _: &actions::FoldFunctionBodies,
14966 window: &mut Window,
14967 cx: &mut Context<Self>,
14968 ) {
14969 let snapshot = self.buffer.read(cx).snapshot(cx);
14970
14971 let ranges = snapshot
14972 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
14973 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
14974 .collect::<Vec<_>>();
14975
14976 let creases = ranges
14977 .into_iter()
14978 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
14979 .collect();
14980
14981 self.fold_creases(creases, true, window, cx);
14982 }
14983
14984 pub fn fold_recursive(
14985 &mut self,
14986 _: &actions::FoldRecursive,
14987 window: &mut Window,
14988 cx: &mut Context<Self>,
14989 ) {
14990 let mut to_fold = Vec::new();
14991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14992 let selections = self.selections.all_adjusted(cx);
14993
14994 for selection in selections {
14995 let range = selection.range().sorted();
14996 let buffer_start_row = range.start.row;
14997
14998 if range.start.row != range.end.row {
14999 let mut found = false;
15000 for row in range.start.row..=range.end.row {
15001 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15002 found = true;
15003 to_fold.push(crease);
15004 }
15005 }
15006 if found {
15007 continue;
15008 }
15009 }
15010
15011 for row in (0..=range.start.row).rev() {
15012 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15013 if crease.range().end.row >= buffer_start_row {
15014 to_fold.push(crease);
15015 } else {
15016 break;
15017 }
15018 }
15019 }
15020 }
15021
15022 self.fold_creases(to_fold, true, window, cx);
15023 }
15024
15025 pub fn fold_at(
15026 &mut self,
15027 buffer_row: MultiBufferRow,
15028 window: &mut Window,
15029 cx: &mut Context<Self>,
15030 ) {
15031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15032
15033 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15034 let autoscroll = self
15035 .selections
15036 .all::<Point>(cx)
15037 .iter()
15038 .any(|selection| crease.range().overlaps(&selection.range()));
15039
15040 self.fold_creases(vec![crease], autoscroll, window, cx);
15041 }
15042 }
15043
15044 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15045 if self.is_singleton(cx) {
15046 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15047 let buffer = &display_map.buffer_snapshot;
15048 let selections = self.selections.all::<Point>(cx);
15049 let ranges = selections
15050 .iter()
15051 .map(|s| {
15052 let range = s.display_range(&display_map).sorted();
15053 let mut start = range.start.to_point(&display_map);
15054 let mut end = range.end.to_point(&display_map);
15055 start.column = 0;
15056 end.column = buffer.line_len(MultiBufferRow(end.row));
15057 start..end
15058 })
15059 .collect::<Vec<_>>();
15060
15061 self.unfold_ranges(&ranges, true, true, cx);
15062 } else {
15063 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15064 let buffer_ids = self
15065 .selections
15066 .disjoint_anchor_ranges()
15067 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15068 .collect::<HashSet<_>>();
15069 for buffer_id in buffer_ids {
15070 self.unfold_buffer(buffer_id, cx);
15071 }
15072 }
15073 }
15074
15075 pub fn unfold_recursive(
15076 &mut self,
15077 _: &UnfoldRecursive,
15078 _window: &mut Window,
15079 cx: &mut Context<Self>,
15080 ) {
15081 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15082 let selections = self.selections.all::<Point>(cx);
15083 let ranges = selections
15084 .iter()
15085 .map(|s| {
15086 let mut range = s.display_range(&display_map).sorted();
15087 *range.start.column_mut() = 0;
15088 *range.end.column_mut() = display_map.line_len(range.end.row());
15089 let start = range.start.to_point(&display_map);
15090 let end = range.end.to_point(&display_map);
15091 start..end
15092 })
15093 .collect::<Vec<_>>();
15094
15095 self.unfold_ranges(&ranges, true, true, cx);
15096 }
15097
15098 pub fn unfold_at(
15099 &mut self,
15100 buffer_row: MultiBufferRow,
15101 _window: &mut Window,
15102 cx: &mut Context<Self>,
15103 ) {
15104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15105
15106 let intersection_range = Point::new(buffer_row.0, 0)
15107 ..Point::new(
15108 buffer_row.0,
15109 display_map.buffer_snapshot.line_len(buffer_row),
15110 );
15111
15112 let autoscroll = self
15113 .selections
15114 .all::<Point>(cx)
15115 .iter()
15116 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15117
15118 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15119 }
15120
15121 pub fn unfold_all(
15122 &mut self,
15123 _: &actions::UnfoldAll,
15124 _window: &mut Window,
15125 cx: &mut Context<Self>,
15126 ) {
15127 if self.buffer.read(cx).is_singleton() {
15128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15129 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15130 } else {
15131 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15132 editor
15133 .update(cx, |editor, cx| {
15134 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15135 editor.unfold_buffer(buffer_id, cx);
15136 }
15137 })
15138 .ok();
15139 });
15140 }
15141 }
15142
15143 pub fn fold_selected_ranges(
15144 &mut self,
15145 _: &FoldSelectedRanges,
15146 window: &mut Window,
15147 cx: &mut Context<Self>,
15148 ) {
15149 let selections = self.selections.all_adjusted(cx);
15150 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15151 let ranges = selections
15152 .into_iter()
15153 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15154 .collect::<Vec<_>>();
15155 self.fold_creases(ranges, true, window, cx);
15156 }
15157
15158 pub fn fold_ranges<T: ToOffset + Clone>(
15159 &mut self,
15160 ranges: Vec<Range<T>>,
15161 auto_scroll: bool,
15162 window: &mut Window,
15163 cx: &mut Context<Self>,
15164 ) {
15165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15166 let ranges = ranges
15167 .into_iter()
15168 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15169 .collect::<Vec<_>>();
15170 self.fold_creases(ranges, auto_scroll, window, cx);
15171 }
15172
15173 pub fn fold_creases<T: ToOffset + Clone>(
15174 &mut self,
15175 creases: Vec<Crease<T>>,
15176 auto_scroll: bool,
15177 _window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) {
15180 if creases.is_empty() {
15181 return;
15182 }
15183
15184 let mut buffers_affected = HashSet::default();
15185 let multi_buffer = self.buffer().read(cx);
15186 for crease in &creases {
15187 if let Some((_, buffer, _)) =
15188 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15189 {
15190 buffers_affected.insert(buffer.read(cx).remote_id());
15191 };
15192 }
15193
15194 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15195
15196 if auto_scroll {
15197 self.request_autoscroll(Autoscroll::fit(), cx);
15198 }
15199
15200 cx.notify();
15201
15202 self.scrollbar_marker_state.dirty = true;
15203 self.folds_did_change(cx);
15204 }
15205
15206 /// Removes any folds whose ranges intersect any of the given ranges.
15207 pub fn unfold_ranges<T: ToOffset + Clone>(
15208 &mut self,
15209 ranges: &[Range<T>],
15210 inclusive: bool,
15211 auto_scroll: bool,
15212 cx: &mut Context<Self>,
15213 ) {
15214 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15215 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15216 });
15217 self.folds_did_change(cx);
15218 }
15219
15220 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15221 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15222 return;
15223 }
15224 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15225 self.display_map.update(cx, |display_map, cx| {
15226 display_map.fold_buffers([buffer_id], cx)
15227 });
15228 cx.emit(EditorEvent::BufferFoldToggled {
15229 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15230 folded: true,
15231 });
15232 cx.notify();
15233 }
15234
15235 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15236 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15237 return;
15238 }
15239 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15240 self.display_map.update(cx, |display_map, cx| {
15241 display_map.unfold_buffers([buffer_id], cx);
15242 });
15243 cx.emit(EditorEvent::BufferFoldToggled {
15244 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15245 folded: false,
15246 });
15247 cx.notify();
15248 }
15249
15250 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15251 self.display_map.read(cx).is_buffer_folded(buffer)
15252 }
15253
15254 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15255 self.display_map.read(cx).folded_buffers()
15256 }
15257
15258 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15259 self.display_map.update(cx, |display_map, cx| {
15260 display_map.disable_header_for_buffer(buffer_id, cx);
15261 });
15262 cx.notify();
15263 }
15264
15265 /// Removes any folds with the given ranges.
15266 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15267 &mut self,
15268 ranges: &[Range<T>],
15269 type_id: TypeId,
15270 auto_scroll: bool,
15271 cx: &mut Context<Self>,
15272 ) {
15273 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15274 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15275 });
15276 self.folds_did_change(cx);
15277 }
15278
15279 fn remove_folds_with<T: ToOffset + Clone>(
15280 &mut self,
15281 ranges: &[Range<T>],
15282 auto_scroll: bool,
15283 cx: &mut Context<Self>,
15284 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15285 ) {
15286 if ranges.is_empty() {
15287 return;
15288 }
15289
15290 let mut buffers_affected = HashSet::default();
15291 let multi_buffer = self.buffer().read(cx);
15292 for range in ranges {
15293 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15294 buffers_affected.insert(buffer.read(cx).remote_id());
15295 };
15296 }
15297
15298 self.display_map.update(cx, update);
15299
15300 if auto_scroll {
15301 self.request_autoscroll(Autoscroll::fit(), cx);
15302 }
15303
15304 cx.notify();
15305 self.scrollbar_marker_state.dirty = true;
15306 self.active_indent_guides_state.dirty = true;
15307 }
15308
15309 pub fn update_fold_widths(
15310 &mut self,
15311 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15312 cx: &mut Context<Self>,
15313 ) -> bool {
15314 self.display_map
15315 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15316 }
15317
15318 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15319 self.display_map.read(cx).fold_placeholder.clone()
15320 }
15321
15322 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15323 self.buffer.update(cx, |buffer, cx| {
15324 buffer.set_all_diff_hunks_expanded(cx);
15325 });
15326 }
15327
15328 pub fn expand_all_diff_hunks(
15329 &mut self,
15330 _: &ExpandAllDiffHunks,
15331 _window: &mut Window,
15332 cx: &mut Context<Self>,
15333 ) {
15334 self.buffer.update(cx, |buffer, cx| {
15335 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15336 });
15337 }
15338
15339 pub fn toggle_selected_diff_hunks(
15340 &mut self,
15341 _: &ToggleSelectedDiffHunks,
15342 _window: &mut Window,
15343 cx: &mut Context<Self>,
15344 ) {
15345 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15346 self.toggle_diff_hunks_in_ranges(ranges, cx);
15347 }
15348
15349 pub fn diff_hunks_in_ranges<'a>(
15350 &'a self,
15351 ranges: &'a [Range<Anchor>],
15352 buffer: &'a MultiBufferSnapshot,
15353 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15354 ranges.iter().flat_map(move |range| {
15355 let end_excerpt_id = range.end.excerpt_id;
15356 let range = range.to_point(buffer);
15357 let mut peek_end = range.end;
15358 if range.end.row < buffer.max_row().0 {
15359 peek_end = Point::new(range.end.row + 1, 0);
15360 }
15361 buffer
15362 .diff_hunks_in_range(range.start..peek_end)
15363 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15364 })
15365 }
15366
15367 pub fn has_stageable_diff_hunks_in_ranges(
15368 &self,
15369 ranges: &[Range<Anchor>],
15370 snapshot: &MultiBufferSnapshot,
15371 ) -> bool {
15372 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15373 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15374 }
15375
15376 pub fn toggle_staged_selected_diff_hunks(
15377 &mut self,
15378 _: &::git::ToggleStaged,
15379 _: &mut Window,
15380 cx: &mut Context<Self>,
15381 ) {
15382 let snapshot = self.buffer.read(cx).snapshot(cx);
15383 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15384 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15385 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15386 }
15387
15388 pub fn set_render_diff_hunk_controls(
15389 &mut self,
15390 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15391 cx: &mut Context<Self>,
15392 ) {
15393 self.render_diff_hunk_controls = render_diff_hunk_controls;
15394 cx.notify();
15395 }
15396
15397 pub fn stage_and_next(
15398 &mut self,
15399 _: &::git::StageAndNext,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) {
15403 self.do_stage_or_unstage_and_next(true, window, cx);
15404 }
15405
15406 pub fn unstage_and_next(
15407 &mut self,
15408 _: &::git::UnstageAndNext,
15409 window: &mut Window,
15410 cx: &mut Context<Self>,
15411 ) {
15412 self.do_stage_or_unstage_and_next(false, window, cx);
15413 }
15414
15415 pub fn stage_or_unstage_diff_hunks(
15416 &mut self,
15417 stage: bool,
15418 ranges: Vec<Range<Anchor>>,
15419 cx: &mut Context<Self>,
15420 ) {
15421 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15422 cx.spawn(async move |this, cx| {
15423 task.await?;
15424 this.update(cx, |this, cx| {
15425 let snapshot = this.buffer.read(cx).snapshot(cx);
15426 let chunk_by = this
15427 .diff_hunks_in_ranges(&ranges, &snapshot)
15428 .chunk_by(|hunk| hunk.buffer_id);
15429 for (buffer_id, hunks) in &chunk_by {
15430 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15431 }
15432 })
15433 })
15434 .detach_and_log_err(cx);
15435 }
15436
15437 fn save_buffers_for_ranges_if_needed(
15438 &mut self,
15439 ranges: &[Range<Anchor>],
15440 cx: &mut Context<Editor>,
15441 ) -> Task<Result<()>> {
15442 let multibuffer = self.buffer.read(cx);
15443 let snapshot = multibuffer.read(cx);
15444 let buffer_ids: HashSet<_> = ranges
15445 .iter()
15446 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15447 .collect();
15448 drop(snapshot);
15449
15450 let mut buffers = HashSet::default();
15451 for buffer_id in buffer_ids {
15452 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15453 let buffer = buffer_entity.read(cx);
15454 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15455 {
15456 buffers.insert(buffer_entity);
15457 }
15458 }
15459 }
15460
15461 if let Some(project) = &self.project {
15462 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15463 } else {
15464 Task::ready(Ok(()))
15465 }
15466 }
15467
15468 fn do_stage_or_unstage_and_next(
15469 &mut self,
15470 stage: bool,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) {
15474 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15475
15476 if ranges.iter().any(|range| range.start != range.end) {
15477 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15478 return;
15479 }
15480
15481 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15482 let snapshot = self.snapshot(window, cx);
15483 let position = self.selections.newest::<Point>(cx).head();
15484 let mut row = snapshot
15485 .buffer_snapshot
15486 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15487 .find(|hunk| hunk.row_range.start.0 > position.row)
15488 .map(|hunk| hunk.row_range.start);
15489
15490 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15491 // Outside of the project diff editor, wrap around to the beginning.
15492 if !all_diff_hunks_expanded {
15493 row = row.or_else(|| {
15494 snapshot
15495 .buffer_snapshot
15496 .diff_hunks_in_range(Point::zero()..position)
15497 .find(|hunk| hunk.row_range.end.0 < position.row)
15498 .map(|hunk| hunk.row_range.start)
15499 });
15500 }
15501
15502 if let Some(row) = row {
15503 let destination = Point::new(row.0, 0);
15504 let autoscroll = Autoscroll::center();
15505
15506 self.unfold_ranges(&[destination..destination], false, false, cx);
15507 self.change_selections(Some(autoscroll), window, cx, |s| {
15508 s.select_ranges([destination..destination]);
15509 });
15510 }
15511 }
15512
15513 fn do_stage_or_unstage(
15514 &self,
15515 stage: bool,
15516 buffer_id: BufferId,
15517 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
15518 cx: &mut App,
15519 ) -> Option<()> {
15520 let project = self.project.as_ref()?;
15521 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
15522 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
15523 let buffer_snapshot = buffer.read(cx).snapshot();
15524 let file_exists = buffer_snapshot
15525 .file()
15526 .is_some_and(|file| file.disk_state().exists());
15527 diff.update(cx, |diff, cx| {
15528 diff.stage_or_unstage_hunks(
15529 stage,
15530 &hunks
15531 .map(|hunk| buffer_diff::DiffHunk {
15532 buffer_range: hunk.buffer_range,
15533 diff_base_byte_range: hunk.diff_base_byte_range,
15534 secondary_status: hunk.secondary_status,
15535 range: Point::zero()..Point::zero(), // unused
15536 })
15537 .collect::<Vec<_>>(),
15538 &buffer_snapshot,
15539 file_exists,
15540 cx,
15541 )
15542 });
15543 None
15544 }
15545
15546 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
15547 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15548 self.buffer
15549 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
15550 }
15551
15552 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
15553 self.buffer.update(cx, |buffer, cx| {
15554 let ranges = vec![Anchor::min()..Anchor::max()];
15555 if !buffer.all_diff_hunks_expanded()
15556 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
15557 {
15558 buffer.collapse_diff_hunks(ranges, cx);
15559 true
15560 } else {
15561 false
15562 }
15563 })
15564 }
15565
15566 fn toggle_diff_hunks_in_ranges(
15567 &mut self,
15568 ranges: Vec<Range<Anchor>>,
15569 cx: &mut Context<Editor>,
15570 ) {
15571 self.buffer.update(cx, |buffer, cx| {
15572 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
15573 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
15574 })
15575 }
15576
15577 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
15578 self.buffer.update(cx, |buffer, cx| {
15579 let snapshot = buffer.snapshot(cx);
15580 let excerpt_id = range.end.excerpt_id;
15581 let point_range = range.to_point(&snapshot);
15582 let expand = !buffer.single_hunk_is_expanded(range, cx);
15583 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
15584 })
15585 }
15586
15587 pub(crate) fn apply_all_diff_hunks(
15588 &mut self,
15589 _: &ApplyAllDiffHunks,
15590 window: &mut Window,
15591 cx: &mut Context<Self>,
15592 ) {
15593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15594
15595 let buffers = self.buffer.read(cx).all_buffers();
15596 for branch_buffer in buffers {
15597 branch_buffer.update(cx, |branch_buffer, cx| {
15598 branch_buffer.merge_into_base(Vec::new(), cx);
15599 });
15600 }
15601
15602 if let Some(project) = self.project.clone() {
15603 self.save(true, project, window, cx).detach_and_log_err(cx);
15604 }
15605 }
15606
15607 pub(crate) fn apply_selected_diff_hunks(
15608 &mut self,
15609 _: &ApplyDiffHunk,
15610 window: &mut Window,
15611 cx: &mut Context<Self>,
15612 ) {
15613 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15614 let snapshot = self.snapshot(window, cx);
15615 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
15616 let mut ranges_by_buffer = HashMap::default();
15617 self.transact(window, cx, |editor, _window, cx| {
15618 for hunk in hunks {
15619 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
15620 ranges_by_buffer
15621 .entry(buffer.clone())
15622 .or_insert_with(Vec::new)
15623 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
15624 }
15625 }
15626
15627 for (buffer, ranges) in ranges_by_buffer {
15628 buffer.update(cx, |buffer, cx| {
15629 buffer.merge_into_base(ranges, cx);
15630 });
15631 }
15632 });
15633
15634 if let Some(project) = self.project.clone() {
15635 self.save(true, project, window, cx).detach_and_log_err(cx);
15636 }
15637 }
15638
15639 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
15640 if hovered != self.gutter_hovered {
15641 self.gutter_hovered = hovered;
15642 cx.notify();
15643 }
15644 }
15645
15646 pub fn insert_blocks(
15647 &mut self,
15648 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
15649 autoscroll: Option<Autoscroll>,
15650 cx: &mut Context<Self>,
15651 ) -> Vec<CustomBlockId> {
15652 let blocks = self
15653 .display_map
15654 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
15655 if let Some(autoscroll) = autoscroll {
15656 self.request_autoscroll(autoscroll, cx);
15657 }
15658 cx.notify();
15659 blocks
15660 }
15661
15662 pub fn resize_blocks(
15663 &mut self,
15664 heights: HashMap<CustomBlockId, u32>,
15665 autoscroll: Option<Autoscroll>,
15666 cx: &mut Context<Self>,
15667 ) {
15668 self.display_map
15669 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
15670 if let Some(autoscroll) = autoscroll {
15671 self.request_autoscroll(autoscroll, cx);
15672 }
15673 cx.notify();
15674 }
15675
15676 pub fn replace_blocks(
15677 &mut self,
15678 renderers: HashMap<CustomBlockId, RenderBlock>,
15679 autoscroll: Option<Autoscroll>,
15680 cx: &mut Context<Self>,
15681 ) {
15682 self.display_map
15683 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
15684 if let Some(autoscroll) = autoscroll {
15685 self.request_autoscroll(autoscroll, cx);
15686 }
15687 cx.notify();
15688 }
15689
15690 pub fn remove_blocks(
15691 &mut self,
15692 block_ids: HashSet<CustomBlockId>,
15693 autoscroll: Option<Autoscroll>,
15694 cx: &mut Context<Self>,
15695 ) {
15696 self.display_map.update(cx, |display_map, cx| {
15697 display_map.remove_blocks(block_ids, cx)
15698 });
15699 if let Some(autoscroll) = autoscroll {
15700 self.request_autoscroll(autoscroll, cx);
15701 }
15702 cx.notify();
15703 }
15704
15705 pub fn row_for_block(
15706 &self,
15707 block_id: CustomBlockId,
15708 cx: &mut Context<Self>,
15709 ) -> Option<DisplayRow> {
15710 self.display_map
15711 .update(cx, |map, cx| map.row_for_block(block_id, cx))
15712 }
15713
15714 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
15715 self.focused_block = Some(focused_block);
15716 }
15717
15718 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
15719 self.focused_block.take()
15720 }
15721
15722 pub fn insert_creases(
15723 &mut self,
15724 creases: impl IntoIterator<Item = Crease<Anchor>>,
15725 cx: &mut Context<Self>,
15726 ) -> Vec<CreaseId> {
15727 self.display_map
15728 .update(cx, |map, cx| map.insert_creases(creases, cx))
15729 }
15730
15731 pub fn remove_creases(
15732 &mut self,
15733 ids: impl IntoIterator<Item = CreaseId>,
15734 cx: &mut Context<Self>,
15735 ) {
15736 self.display_map
15737 .update(cx, |map, cx| map.remove_creases(ids, cx));
15738 }
15739
15740 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
15741 self.display_map
15742 .update(cx, |map, cx| map.snapshot(cx))
15743 .longest_row()
15744 }
15745
15746 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
15747 self.display_map
15748 .update(cx, |map, cx| map.snapshot(cx))
15749 .max_point()
15750 }
15751
15752 pub fn text(&self, cx: &App) -> String {
15753 self.buffer.read(cx).read(cx).text()
15754 }
15755
15756 pub fn is_empty(&self, cx: &App) -> bool {
15757 self.buffer.read(cx).read(cx).is_empty()
15758 }
15759
15760 pub fn text_option(&self, cx: &App) -> Option<String> {
15761 let text = self.text(cx);
15762 let text = text.trim();
15763
15764 if text.is_empty() {
15765 return None;
15766 }
15767
15768 Some(text.to_string())
15769 }
15770
15771 pub fn set_text(
15772 &mut self,
15773 text: impl Into<Arc<str>>,
15774 window: &mut Window,
15775 cx: &mut Context<Self>,
15776 ) {
15777 self.transact(window, cx, |this, _, cx| {
15778 this.buffer
15779 .read(cx)
15780 .as_singleton()
15781 .expect("you can only call set_text on editors for singleton buffers")
15782 .update(cx, |buffer, cx| buffer.set_text(text, cx));
15783 });
15784 }
15785
15786 pub fn display_text(&self, cx: &mut App) -> String {
15787 self.display_map
15788 .update(cx, |map, cx| map.snapshot(cx))
15789 .text()
15790 }
15791
15792 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
15793 let mut wrap_guides = smallvec::smallvec![];
15794
15795 if self.show_wrap_guides == Some(false) {
15796 return wrap_guides;
15797 }
15798
15799 let settings = self.buffer.read(cx).language_settings(cx);
15800 if settings.show_wrap_guides {
15801 match self.soft_wrap_mode(cx) {
15802 SoftWrap::Column(soft_wrap) => {
15803 wrap_guides.push((soft_wrap as usize, true));
15804 }
15805 SoftWrap::Bounded(soft_wrap) => {
15806 wrap_guides.push((soft_wrap as usize, true));
15807 }
15808 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
15809 }
15810 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
15811 }
15812
15813 wrap_guides
15814 }
15815
15816 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
15817 let settings = self.buffer.read(cx).language_settings(cx);
15818 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
15819 match mode {
15820 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
15821 SoftWrap::None
15822 }
15823 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
15824 language_settings::SoftWrap::PreferredLineLength => {
15825 SoftWrap::Column(settings.preferred_line_length)
15826 }
15827 language_settings::SoftWrap::Bounded => {
15828 SoftWrap::Bounded(settings.preferred_line_length)
15829 }
15830 }
15831 }
15832
15833 pub fn set_soft_wrap_mode(
15834 &mut self,
15835 mode: language_settings::SoftWrap,
15836
15837 cx: &mut Context<Self>,
15838 ) {
15839 self.soft_wrap_mode_override = Some(mode);
15840 cx.notify();
15841 }
15842
15843 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
15844 self.hard_wrap = hard_wrap;
15845 cx.notify();
15846 }
15847
15848 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
15849 self.text_style_refinement = Some(style);
15850 }
15851
15852 /// called by the Element so we know what style we were most recently rendered with.
15853 pub(crate) fn set_style(
15854 &mut self,
15855 style: EditorStyle,
15856 window: &mut Window,
15857 cx: &mut Context<Self>,
15858 ) {
15859 let rem_size = window.rem_size();
15860 self.display_map.update(cx, |map, cx| {
15861 map.set_font(
15862 style.text.font(),
15863 style.text.font_size.to_pixels(rem_size),
15864 cx,
15865 )
15866 });
15867 self.style = Some(style);
15868 }
15869
15870 pub fn style(&self) -> Option<&EditorStyle> {
15871 self.style.as_ref()
15872 }
15873
15874 // Called by the element. This method is not designed to be called outside of the editor
15875 // element's layout code because it does not notify when rewrapping is computed synchronously.
15876 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
15877 self.display_map
15878 .update(cx, |map, cx| map.set_wrap_width(width, cx))
15879 }
15880
15881 pub fn set_soft_wrap(&mut self) {
15882 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
15883 }
15884
15885 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
15886 if self.soft_wrap_mode_override.is_some() {
15887 self.soft_wrap_mode_override.take();
15888 } else {
15889 let soft_wrap = match self.soft_wrap_mode(cx) {
15890 SoftWrap::GitDiff => return,
15891 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
15892 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
15893 language_settings::SoftWrap::None
15894 }
15895 };
15896 self.soft_wrap_mode_override = Some(soft_wrap);
15897 }
15898 cx.notify();
15899 }
15900
15901 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
15902 let Some(workspace) = self.workspace() else {
15903 return;
15904 };
15905 let fs = workspace.read(cx).app_state().fs.clone();
15906 let current_show = TabBarSettings::get_global(cx).show;
15907 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
15908 setting.show = Some(!current_show);
15909 });
15910 }
15911
15912 pub fn toggle_indent_guides(
15913 &mut self,
15914 _: &ToggleIndentGuides,
15915 _: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) {
15918 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
15919 self.buffer
15920 .read(cx)
15921 .language_settings(cx)
15922 .indent_guides
15923 .enabled
15924 });
15925 self.show_indent_guides = Some(!currently_enabled);
15926 cx.notify();
15927 }
15928
15929 fn should_show_indent_guides(&self) -> Option<bool> {
15930 self.show_indent_guides
15931 }
15932
15933 pub fn toggle_line_numbers(
15934 &mut self,
15935 _: &ToggleLineNumbers,
15936 _: &mut Window,
15937 cx: &mut Context<Self>,
15938 ) {
15939 let mut editor_settings = EditorSettings::get_global(cx).clone();
15940 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
15941 EditorSettings::override_global(editor_settings, cx);
15942 }
15943
15944 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
15945 if let Some(show_line_numbers) = self.show_line_numbers {
15946 return show_line_numbers;
15947 }
15948 EditorSettings::get_global(cx).gutter.line_numbers
15949 }
15950
15951 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
15952 self.use_relative_line_numbers
15953 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
15954 }
15955
15956 pub fn toggle_relative_line_numbers(
15957 &mut self,
15958 _: &ToggleRelativeLineNumbers,
15959 _: &mut Window,
15960 cx: &mut Context<Self>,
15961 ) {
15962 let is_relative = self.should_use_relative_line_numbers(cx);
15963 self.set_relative_line_number(Some(!is_relative), cx)
15964 }
15965
15966 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
15967 self.use_relative_line_numbers = is_relative;
15968 cx.notify();
15969 }
15970
15971 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
15972 self.show_gutter = show_gutter;
15973 cx.notify();
15974 }
15975
15976 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
15977 self.show_scrollbars = show_scrollbars;
15978 cx.notify();
15979 }
15980
15981 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
15982 self.show_line_numbers = Some(show_line_numbers);
15983 cx.notify();
15984 }
15985
15986 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
15987 self.show_git_diff_gutter = Some(show_git_diff_gutter);
15988 cx.notify();
15989 }
15990
15991 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
15992 self.show_code_actions = Some(show_code_actions);
15993 cx.notify();
15994 }
15995
15996 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
15997 self.show_runnables = Some(show_runnables);
15998 cx.notify();
15999 }
16000
16001 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16002 self.show_breakpoints = Some(show_breakpoints);
16003 cx.notify();
16004 }
16005
16006 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16007 if self.display_map.read(cx).masked != masked {
16008 self.display_map.update(cx, |map, _| map.masked = masked);
16009 }
16010 cx.notify()
16011 }
16012
16013 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16014 self.show_wrap_guides = Some(show_wrap_guides);
16015 cx.notify();
16016 }
16017
16018 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16019 self.show_indent_guides = Some(show_indent_guides);
16020 cx.notify();
16021 }
16022
16023 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16024 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16025 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16026 if let Some(dir) = file.abs_path(cx).parent() {
16027 return Some(dir.to_owned());
16028 }
16029 }
16030
16031 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16032 return Some(project_path.path.to_path_buf());
16033 }
16034 }
16035
16036 None
16037 }
16038
16039 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16040 self.active_excerpt(cx)?
16041 .1
16042 .read(cx)
16043 .file()
16044 .and_then(|f| f.as_local())
16045 }
16046
16047 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16048 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16049 let buffer = buffer.read(cx);
16050 if let Some(project_path) = buffer.project_path(cx) {
16051 let project = self.project.as_ref()?.read(cx);
16052 project.absolute_path(&project_path, cx)
16053 } else {
16054 buffer
16055 .file()
16056 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16057 }
16058 })
16059 }
16060
16061 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16062 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16063 let project_path = buffer.read(cx).project_path(cx)?;
16064 let project = self.project.as_ref()?.read(cx);
16065 let entry = project.entry_for_path(&project_path, cx)?;
16066 let path = entry.path.to_path_buf();
16067 Some(path)
16068 })
16069 }
16070
16071 pub fn reveal_in_finder(
16072 &mut self,
16073 _: &RevealInFileManager,
16074 _window: &mut Window,
16075 cx: &mut Context<Self>,
16076 ) {
16077 if let Some(target) = self.target_file(cx) {
16078 cx.reveal_path(&target.abs_path(cx));
16079 }
16080 }
16081
16082 pub fn copy_path(
16083 &mut self,
16084 _: &zed_actions::workspace::CopyPath,
16085 _window: &mut Window,
16086 cx: &mut Context<Self>,
16087 ) {
16088 if let Some(path) = self.target_file_abs_path(cx) {
16089 if let Some(path) = path.to_str() {
16090 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16091 }
16092 }
16093 }
16094
16095 pub fn copy_relative_path(
16096 &mut self,
16097 _: &zed_actions::workspace::CopyRelativePath,
16098 _window: &mut Window,
16099 cx: &mut Context<Self>,
16100 ) {
16101 if let Some(path) = self.target_file_path(cx) {
16102 if let Some(path) = path.to_str() {
16103 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16104 }
16105 }
16106 }
16107
16108 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16109 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16110 buffer.read(cx).project_path(cx)
16111 } else {
16112 None
16113 }
16114 }
16115
16116 // Returns true if the editor handled a go-to-line request
16117 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16118 maybe!({
16119 let breakpoint_store = self.breakpoint_store.as_ref()?;
16120
16121 let Some((_, _, active_position)) =
16122 breakpoint_store.read(cx).active_position().cloned()
16123 else {
16124 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16125 return None;
16126 };
16127
16128 let snapshot = self
16129 .project
16130 .as_ref()?
16131 .read(cx)
16132 .buffer_for_id(active_position.buffer_id?, cx)?
16133 .read(cx)
16134 .snapshot();
16135
16136 let mut handled = false;
16137 for (id, ExcerptRange { context, .. }) in self
16138 .buffer
16139 .read(cx)
16140 .excerpts_for_buffer(active_position.buffer_id?, cx)
16141 {
16142 if context.start.cmp(&active_position, &snapshot).is_ge()
16143 || context.end.cmp(&active_position, &snapshot).is_lt()
16144 {
16145 continue;
16146 }
16147 let snapshot = self.buffer.read(cx).snapshot(cx);
16148 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, active_position)?;
16149
16150 handled = true;
16151 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16152 self.go_to_line::<DebugCurrentRowHighlight>(
16153 multibuffer_anchor,
16154 Some(cx.theme().colors().editor_debugger_active_line_background),
16155 window,
16156 cx,
16157 );
16158
16159 cx.notify();
16160 }
16161 handled.then_some(())
16162 })
16163 .is_some()
16164 }
16165
16166 pub fn copy_file_name_without_extension(
16167 &mut self,
16168 _: &CopyFileNameWithoutExtension,
16169 _: &mut Window,
16170 cx: &mut Context<Self>,
16171 ) {
16172 if let Some(file) = self.target_file(cx) {
16173 if let Some(file_stem) = file.path().file_stem() {
16174 if let Some(name) = file_stem.to_str() {
16175 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16176 }
16177 }
16178 }
16179 }
16180
16181 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16182 if let Some(file) = self.target_file(cx) {
16183 if let Some(file_name) = file.path().file_name() {
16184 if let Some(name) = file_name.to_str() {
16185 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16186 }
16187 }
16188 }
16189 }
16190
16191 pub fn toggle_git_blame(
16192 &mut self,
16193 _: &::git::Blame,
16194 window: &mut Window,
16195 cx: &mut Context<Self>,
16196 ) {
16197 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16198
16199 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16200 self.start_git_blame(true, window, cx);
16201 }
16202
16203 cx.notify();
16204 }
16205
16206 pub fn toggle_git_blame_inline(
16207 &mut self,
16208 _: &ToggleGitBlameInline,
16209 window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 self.toggle_git_blame_inline_internal(true, window, cx);
16213 cx.notify();
16214 }
16215
16216 pub fn open_git_blame_commit(
16217 &mut self,
16218 _: &OpenGitBlameCommit,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 self.open_git_blame_commit_internal(window, cx);
16223 }
16224
16225 fn open_git_blame_commit_internal(
16226 &mut self,
16227 window: &mut Window,
16228 cx: &mut Context<Self>,
16229 ) -> Option<()> {
16230 let blame = self.blame.as_ref()?;
16231 let snapshot = self.snapshot(window, cx);
16232 let cursor = self.selections.newest::<Point>(cx).head();
16233 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16234 let blame_entry = blame
16235 .update(cx, |blame, cx| {
16236 blame
16237 .blame_for_rows(
16238 &[RowInfo {
16239 buffer_id: Some(buffer.remote_id()),
16240 buffer_row: Some(point.row),
16241 ..Default::default()
16242 }],
16243 cx,
16244 )
16245 .next()
16246 })
16247 .flatten()?;
16248 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16249 let repo = blame.read(cx).repository(cx)?;
16250 let workspace = self.workspace()?.downgrade();
16251 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16252 None
16253 }
16254
16255 pub fn git_blame_inline_enabled(&self) -> bool {
16256 self.git_blame_inline_enabled
16257 }
16258
16259 pub fn toggle_selection_menu(
16260 &mut self,
16261 _: &ToggleSelectionMenu,
16262 _: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) {
16265 self.show_selection_menu = self
16266 .show_selection_menu
16267 .map(|show_selections_menu| !show_selections_menu)
16268 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16269
16270 cx.notify();
16271 }
16272
16273 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16274 self.show_selection_menu
16275 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16276 }
16277
16278 fn start_git_blame(
16279 &mut self,
16280 user_triggered: bool,
16281 window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 if let Some(project) = self.project.as_ref() {
16285 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16286 return;
16287 };
16288
16289 if buffer.read(cx).file().is_none() {
16290 return;
16291 }
16292
16293 let focused = self.focus_handle(cx).contains_focused(window, cx);
16294
16295 let project = project.clone();
16296 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16297 self.blame_subscription =
16298 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16299 self.blame = Some(blame);
16300 }
16301 }
16302
16303 fn toggle_git_blame_inline_internal(
16304 &mut self,
16305 user_triggered: bool,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) {
16309 if self.git_blame_inline_enabled {
16310 self.git_blame_inline_enabled = false;
16311 self.show_git_blame_inline = false;
16312 self.show_git_blame_inline_delay_task.take();
16313 } else {
16314 self.git_blame_inline_enabled = true;
16315 self.start_git_blame_inline(user_triggered, window, cx);
16316 }
16317
16318 cx.notify();
16319 }
16320
16321 fn start_git_blame_inline(
16322 &mut self,
16323 user_triggered: bool,
16324 window: &mut Window,
16325 cx: &mut Context<Self>,
16326 ) {
16327 self.start_git_blame(user_triggered, window, cx);
16328
16329 if ProjectSettings::get_global(cx)
16330 .git
16331 .inline_blame_delay()
16332 .is_some()
16333 {
16334 self.start_inline_blame_timer(window, cx);
16335 } else {
16336 self.show_git_blame_inline = true
16337 }
16338 }
16339
16340 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16341 self.blame.as_ref()
16342 }
16343
16344 pub fn show_git_blame_gutter(&self) -> bool {
16345 self.show_git_blame_gutter
16346 }
16347
16348 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16349 self.show_git_blame_gutter && self.has_blame_entries(cx)
16350 }
16351
16352 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16353 self.show_git_blame_inline
16354 && (self.focus_handle.is_focused(window)
16355 || self
16356 .git_blame_inline_tooltip
16357 .as_ref()
16358 .and_then(|t| t.upgrade())
16359 .is_some())
16360 && !self.newest_selection_head_on_empty_line(cx)
16361 && self.has_blame_entries(cx)
16362 }
16363
16364 fn has_blame_entries(&self, cx: &App) -> bool {
16365 self.blame()
16366 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16367 }
16368
16369 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16370 let cursor_anchor = self.selections.newest_anchor().head();
16371
16372 let snapshot = self.buffer.read(cx).snapshot(cx);
16373 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16374
16375 snapshot.line_len(buffer_row) == 0
16376 }
16377
16378 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16379 let buffer_and_selection = maybe!({
16380 let selection = self.selections.newest::<Point>(cx);
16381 let selection_range = selection.range();
16382
16383 let multi_buffer = self.buffer().read(cx);
16384 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16385 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16386
16387 let (buffer, range, _) = if selection.reversed {
16388 buffer_ranges.first()
16389 } else {
16390 buffer_ranges.last()
16391 }?;
16392
16393 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16394 ..text::ToPoint::to_point(&range.end, &buffer).row;
16395 Some((
16396 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16397 selection,
16398 ))
16399 });
16400
16401 let Some((buffer, selection)) = buffer_and_selection else {
16402 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16403 };
16404
16405 let Some(project) = self.project.as_ref() else {
16406 return Task::ready(Err(anyhow!("editor does not have project")));
16407 };
16408
16409 project.update(cx, |project, cx| {
16410 project.get_permalink_to_line(&buffer, selection, cx)
16411 })
16412 }
16413
16414 pub fn copy_permalink_to_line(
16415 &mut self,
16416 _: &CopyPermalinkToLine,
16417 window: &mut Window,
16418 cx: &mut Context<Self>,
16419 ) {
16420 let permalink_task = self.get_permalink_to_line(cx);
16421 let workspace = self.workspace();
16422
16423 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16424 Ok(permalink) => {
16425 cx.update(|_, cx| {
16426 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16427 })
16428 .ok();
16429 }
16430 Err(err) => {
16431 let message = format!("Failed to copy permalink: {err}");
16432
16433 Err::<(), anyhow::Error>(err).log_err();
16434
16435 if let Some(workspace) = workspace {
16436 workspace
16437 .update_in(cx, |workspace, _, cx| {
16438 struct CopyPermalinkToLine;
16439
16440 workspace.show_toast(
16441 Toast::new(
16442 NotificationId::unique::<CopyPermalinkToLine>(),
16443 message,
16444 ),
16445 cx,
16446 )
16447 })
16448 .ok();
16449 }
16450 }
16451 })
16452 .detach();
16453 }
16454
16455 pub fn copy_file_location(
16456 &mut self,
16457 _: &CopyFileLocation,
16458 _: &mut Window,
16459 cx: &mut Context<Self>,
16460 ) {
16461 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16462 if let Some(file) = self.target_file(cx) {
16463 if let Some(path) = file.path().to_str() {
16464 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16465 }
16466 }
16467 }
16468
16469 pub fn open_permalink_to_line(
16470 &mut self,
16471 _: &OpenPermalinkToLine,
16472 window: &mut Window,
16473 cx: &mut Context<Self>,
16474 ) {
16475 let permalink_task = self.get_permalink_to_line(cx);
16476 let workspace = self.workspace();
16477
16478 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16479 Ok(permalink) => {
16480 cx.update(|_, cx| {
16481 cx.open_url(permalink.as_ref());
16482 })
16483 .ok();
16484 }
16485 Err(err) => {
16486 let message = format!("Failed to open permalink: {err}");
16487
16488 Err::<(), anyhow::Error>(err).log_err();
16489
16490 if let Some(workspace) = workspace {
16491 workspace
16492 .update(cx, |workspace, cx| {
16493 struct OpenPermalinkToLine;
16494
16495 workspace.show_toast(
16496 Toast::new(
16497 NotificationId::unique::<OpenPermalinkToLine>(),
16498 message,
16499 ),
16500 cx,
16501 )
16502 })
16503 .ok();
16504 }
16505 }
16506 })
16507 .detach();
16508 }
16509
16510 pub fn insert_uuid_v4(
16511 &mut self,
16512 _: &InsertUuidV4,
16513 window: &mut Window,
16514 cx: &mut Context<Self>,
16515 ) {
16516 self.insert_uuid(UuidVersion::V4, window, cx);
16517 }
16518
16519 pub fn insert_uuid_v7(
16520 &mut self,
16521 _: &InsertUuidV7,
16522 window: &mut Window,
16523 cx: &mut Context<Self>,
16524 ) {
16525 self.insert_uuid(UuidVersion::V7, window, cx);
16526 }
16527
16528 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
16529 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16530 self.transact(window, cx, |this, window, cx| {
16531 let edits = this
16532 .selections
16533 .all::<Point>(cx)
16534 .into_iter()
16535 .map(|selection| {
16536 let uuid = match version {
16537 UuidVersion::V4 => uuid::Uuid::new_v4(),
16538 UuidVersion::V7 => uuid::Uuid::now_v7(),
16539 };
16540
16541 (selection.range(), uuid.to_string())
16542 });
16543 this.edit(edits, cx);
16544 this.refresh_inline_completion(true, false, window, cx);
16545 });
16546 }
16547
16548 pub fn open_selections_in_multibuffer(
16549 &mut self,
16550 _: &OpenSelectionsInMultibuffer,
16551 window: &mut Window,
16552 cx: &mut Context<Self>,
16553 ) {
16554 let multibuffer = self.buffer.read(cx);
16555
16556 let Some(buffer) = multibuffer.as_singleton() else {
16557 return;
16558 };
16559
16560 let Some(workspace) = self.workspace() else {
16561 return;
16562 };
16563
16564 let locations = self
16565 .selections
16566 .disjoint_anchors()
16567 .iter()
16568 .map(|range| Location {
16569 buffer: buffer.clone(),
16570 range: range.start.text_anchor..range.end.text_anchor,
16571 })
16572 .collect::<Vec<_>>();
16573
16574 let title = multibuffer.title(cx).to_string();
16575
16576 cx.spawn_in(window, async move |_, cx| {
16577 workspace.update_in(cx, |workspace, window, cx| {
16578 Self::open_locations_in_multibuffer(
16579 workspace,
16580 locations,
16581 format!("Selections for '{title}'"),
16582 false,
16583 MultibufferSelectionMode::All,
16584 window,
16585 cx,
16586 );
16587 })
16588 })
16589 .detach();
16590 }
16591
16592 /// Adds a row highlight for the given range. If a row has multiple highlights, the
16593 /// last highlight added will be used.
16594 ///
16595 /// If the range ends at the beginning of a line, then that line will not be highlighted.
16596 pub fn highlight_rows<T: 'static>(
16597 &mut self,
16598 range: Range<Anchor>,
16599 color: Hsla,
16600 should_autoscroll: bool,
16601 cx: &mut Context<Self>,
16602 ) {
16603 let snapshot = self.buffer().read(cx).snapshot(cx);
16604 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16605 let ix = row_highlights.binary_search_by(|highlight| {
16606 Ordering::Equal
16607 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
16608 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
16609 });
16610
16611 if let Err(mut ix) = ix {
16612 let index = post_inc(&mut self.highlight_order);
16613
16614 // If this range intersects with the preceding highlight, then merge it with
16615 // the preceding highlight. Otherwise insert a new highlight.
16616 let mut merged = false;
16617 if ix > 0 {
16618 let prev_highlight = &mut row_highlights[ix - 1];
16619 if prev_highlight
16620 .range
16621 .end
16622 .cmp(&range.start, &snapshot)
16623 .is_ge()
16624 {
16625 ix -= 1;
16626 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
16627 prev_highlight.range.end = range.end;
16628 }
16629 merged = true;
16630 prev_highlight.index = index;
16631 prev_highlight.color = color;
16632 prev_highlight.should_autoscroll = should_autoscroll;
16633 }
16634 }
16635
16636 if !merged {
16637 row_highlights.insert(
16638 ix,
16639 RowHighlight {
16640 range: range.clone(),
16641 index,
16642 color,
16643 should_autoscroll,
16644 },
16645 );
16646 }
16647
16648 // If any of the following highlights intersect with this one, merge them.
16649 while let Some(next_highlight) = row_highlights.get(ix + 1) {
16650 let highlight = &row_highlights[ix];
16651 if next_highlight
16652 .range
16653 .start
16654 .cmp(&highlight.range.end, &snapshot)
16655 .is_le()
16656 {
16657 if next_highlight
16658 .range
16659 .end
16660 .cmp(&highlight.range.end, &snapshot)
16661 .is_gt()
16662 {
16663 row_highlights[ix].range.end = next_highlight.range.end;
16664 }
16665 row_highlights.remove(ix + 1);
16666 } else {
16667 break;
16668 }
16669 }
16670 }
16671 }
16672
16673 /// Remove any highlighted row ranges of the given type that intersect the
16674 /// given ranges.
16675 pub fn remove_highlighted_rows<T: 'static>(
16676 &mut self,
16677 ranges_to_remove: Vec<Range<Anchor>>,
16678 cx: &mut Context<Self>,
16679 ) {
16680 let snapshot = self.buffer().read(cx).snapshot(cx);
16681 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
16682 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
16683 row_highlights.retain(|highlight| {
16684 while let Some(range_to_remove) = ranges_to_remove.peek() {
16685 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
16686 Ordering::Less | Ordering::Equal => {
16687 ranges_to_remove.next();
16688 }
16689 Ordering::Greater => {
16690 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
16691 Ordering::Less | Ordering::Equal => {
16692 return false;
16693 }
16694 Ordering::Greater => break,
16695 }
16696 }
16697 }
16698 }
16699
16700 true
16701 })
16702 }
16703
16704 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
16705 pub fn clear_row_highlights<T: 'static>(&mut self) {
16706 self.highlighted_rows.remove(&TypeId::of::<T>());
16707 }
16708
16709 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
16710 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
16711 self.highlighted_rows
16712 .get(&TypeId::of::<T>())
16713 .map_or(&[] as &[_], |vec| vec.as_slice())
16714 .iter()
16715 .map(|highlight| (highlight.range.clone(), highlight.color))
16716 }
16717
16718 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
16719 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
16720 /// Allows to ignore certain kinds of highlights.
16721 pub fn highlighted_display_rows(
16722 &self,
16723 window: &mut Window,
16724 cx: &mut App,
16725 ) -> BTreeMap<DisplayRow, LineHighlight> {
16726 let snapshot = self.snapshot(window, cx);
16727 let mut used_highlight_orders = HashMap::default();
16728 self.highlighted_rows
16729 .iter()
16730 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
16731 .fold(
16732 BTreeMap::<DisplayRow, LineHighlight>::new(),
16733 |mut unique_rows, highlight| {
16734 let start = highlight.range.start.to_display_point(&snapshot);
16735 let end = highlight.range.end.to_display_point(&snapshot);
16736 let start_row = start.row().0;
16737 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
16738 && end.column() == 0
16739 {
16740 end.row().0.saturating_sub(1)
16741 } else {
16742 end.row().0
16743 };
16744 for row in start_row..=end_row {
16745 let used_index =
16746 used_highlight_orders.entry(row).or_insert(highlight.index);
16747 if highlight.index >= *used_index {
16748 *used_index = highlight.index;
16749 unique_rows.insert(DisplayRow(row), highlight.color.into());
16750 }
16751 }
16752 unique_rows
16753 },
16754 )
16755 }
16756
16757 pub fn highlighted_display_row_for_autoscroll(
16758 &self,
16759 snapshot: &DisplaySnapshot,
16760 ) -> Option<DisplayRow> {
16761 self.highlighted_rows
16762 .values()
16763 .flat_map(|highlighted_rows| highlighted_rows.iter())
16764 .filter_map(|highlight| {
16765 if highlight.should_autoscroll {
16766 Some(highlight.range.start.to_display_point(snapshot).row())
16767 } else {
16768 None
16769 }
16770 })
16771 .min()
16772 }
16773
16774 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
16775 self.highlight_background::<SearchWithinRange>(
16776 ranges,
16777 |colors| colors.editor_document_highlight_read_background,
16778 cx,
16779 )
16780 }
16781
16782 pub fn set_breadcrumb_header(&mut self, new_header: String) {
16783 self.breadcrumb_header = Some(new_header);
16784 }
16785
16786 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
16787 self.clear_background_highlights::<SearchWithinRange>(cx);
16788 }
16789
16790 pub fn highlight_background<T: 'static>(
16791 &mut self,
16792 ranges: &[Range<Anchor>],
16793 color_fetcher: fn(&ThemeColors) -> Hsla,
16794 cx: &mut Context<Self>,
16795 ) {
16796 self.background_highlights
16797 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16798 self.scrollbar_marker_state.dirty = true;
16799 cx.notify();
16800 }
16801
16802 pub fn clear_background_highlights<T: 'static>(
16803 &mut self,
16804 cx: &mut Context<Self>,
16805 ) -> Option<BackgroundHighlight> {
16806 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
16807 if !text_highlights.1.is_empty() {
16808 self.scrollbar_marker_state.dirty = true;
16809 cx.notify();
16810 }
16811 Some(text_highlights)
16812 }
16813
16814 pub fn highlight_gutter<T: 'static>(
16815 &mut self,
16816 ranges: &[Range<Anchor>],
16817 color_fetcher: fn(&App) -> Hsla,
16818 cx: &mut Context<Self>,
16819 ) {
16820 self.gutter_highlights
16821 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
16822 cx.notify();
16823 }
16824
16825 pub fn clear_gutter_highlights<T: 'static>(
16826 &mut self,
16827 cx: &mut Context<Self>,
16828 ) -> Option<GutterHighlight> {
16829 cx.notify();
16830 self.gutter_highlights.remove(&TypeId::of::<T>())
16831 }
16832
16833 #[cfg(feature = "test-support")]
16834 pub fn all_text_background_highlights(
16835 &self,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16839 let snapshot = self.snapshot(window, cx);
16840 let buffer = &snapshot.buffer_snapshot;
16841 let start = buffer.anchor_before(0);
16842 let end = buffer.anchor_after(buffer.len());
16843 let theme = cx.theme().colors();
16844 self.background_highlights_in_range(start..end, &snapshot, theme)
16845 }
16846
16847 #[cfg(feature = "test-support")]
16848 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
16849 let snapshot = self.buffer().read(cx).snapshot(cx);
16850
16851 let highlights = self
16852 .background_highlights
16853 .get(&TypeId::of::<items::BufferSearchHighlights>());
16854
16855 if let Some((_color, ranges)) = highlights {
16856 ranges
16857 .iter()
16858 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
16859 .collect_vec()
16860 } else {
16861 vec![]
16862 }
16863 }
16864
16865 fn document_highlights_for_position<'a>(
16866 &'a self,
16867 position: Anchor,
16868 buffer: &'a MultiBufferSnapshot,
16869 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
16870 let read_highlights = self
16871 .background_highlights
16872 .get(&TypeId::of::<DocumentHighlightRead>())
16873 .map(|h| &h.1);
16874 let write_highlights = self
16875 .background_highlights
16876 .get(&TypeId::of::<DocumentHighlightWrite>())
16877 .map(|h| &h.1);
16878 let left_position = position.bias_left(buffer);
16879 let right_position = position.bias_right(buffer);
16880 read_highlights
16881 .into_iter()
16882 .chain(write_highlights)
16883 .flat_map(move |ranges| {
16884 let start_ix = match ranges.binary_search_by(|probe| {
16885 let cmp = probe.end.cmp(&left_position, buffer);
16886 if cmp.is_ge() {
16887 Ordering::Greater
16888 } else {
16889 Ordering::Less
16890 }
16891 }) {
16892 Ok(i) | Err(i) => i,
16893 };
16894
16895 ranges[start_ix..]
16896 .iter()
16897 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
16898 })
16899 }
16900
16901 pub fn has_background_highlights<T: 'static>(&self) -> bool {
16902 self.background_highlights
16903 .get(&TypeId::of::<T>())
16904 .map_or(false, |(_, highlights)| !highlights.is_empty())
16905 }
16906
16907 pub fn background_highlights_in_range(
16908 &self,
16909 search_range: Range<Anchor>,
16910 display_snapshot: &DisplaySnapshot,
16911 theme: &ThemeColors,
16912 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
16913 let mut results = Vec::new();
16914 for (color_fetcher, ranges) in self.background_highlights.values() {
16915 let color = color_fetcher(theme);
16916 let start_ix = match ranges.binary_search_by(|probe| {
16917 let cmp = probe
16918 .end
16919 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16920 if cmp.is_gt() {
16921 Ordering::Greater
16922 } else {
16923 Ordering::Less
16924 }
16925 }) {
16926 Ok(i) | Err(i) => i,
16927 };
16928 for range in &ranges[start_ix..] {
16929 if range
16930 .start
16931 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16932 .is_ge()
16933 {
16934 break;
16935 }
16936
16937 let start = range.start.to_display_point(display_snapshot);
16938 let end = range.end.to_display_point(display_snapshot);
16939 results.push((start..end, color))
16940 }
16941 }
16942 results
16943 }
16944
16945 pub fn background_highlight_row_ranges<T: 'static>(
16946 &self,
16947 search_range: Range<Anchor>,
16948 display_snapshot: &DisplaySnapshot,
16949 count: usize,
16950 ) -> Vec<RangeInclusive<DisplayPoint>> {
16951 let mut results = Vec::new();
16952 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
16953 return vec![];
16954 };
16955
16956 let start_ix = match ranges.binary_search_by(|probe| {
16957 let cmp = probe
16958 .end
16959 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
16960 if cmp.is_gt() {
16961 Ordering::Greater
16962 } else {
16963 Ordering::Less
16964 }
16965 }) {
16966 Ok(i) | Err(i) => i,
16967 };
16968 let mut push_region = |start: Option<Point>, end: Option<Point>| {
16969 if let (Some(start_display), Some(end_display)) = (start, end) {
16970 results.push(
16971 start_display.to_display_point(display_snapshot)
16972 ..=end_display.to_display_point(display_snapshot),
16973 );
16974 }
16975 };
16976 let mut start_row: Option<Point> = None;
16977 let mut end_row: Option<Point> = None;
16978 if ranges.len() > count {
16979 return Vec::new();
16980 }
16981 for range in &ranges[start_ix..] {
16982 if range
16983 .start
16984 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
16985 .is_ge()
16986 {
16987 break;
16988 }
16989 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
16990 if let Some(current_row) = &end_row {
16991 if end.row == current_row.row {
16992 continue;
16993 }
16994 }
16995 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
16996 if start_row.is_none() {
16997 assert_eq!(end_row, None);
16998 start_row = Some(start);
16999 end_row = Some(end);
17000 continue;
17001 }
17002 if let Some(current_end) = end_row.as_mut() {
17003 if start.row > current_end.row + 1 {
17004 push_region(start_row, end_row);
17005 start_row = Some(start);
17006 end_row = Some(end);
17007 } else {
17008 // Merge two hunks.
17009 *current_end = end;
17010 }
17011 } else {
17012 unreachable!();
17013 }
17014 }
17015 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17016 push_region(start_row, end_row);
17017 results
17018 }
17019
17020 pub fn gutter_highlights_in_range(
17021 &self,
17022 search_range: Range<Anchor>,
17023 display_snapshot: &DisplaySnapshot,
17024 cx: &App,
17025 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17026 let mut results = Vec::new();
17027 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17028 let color = color_fetcher(cx);
17029 let start_ix = match ranges.binary_search_by(|probe| {
17030 let cmp = probe
17031 .end
17032 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17033 if cmp.is_gt() {
17034 Ordering::Greater
17035 } else {
17036 Ordering::Less
17037 }
17038 }) {
17039 Ok(i) | Err(i) => i,
17040 };
17041 for range in &ranges[start_ix..] {
17042 if range
17043 .start
17044 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17045 .is_ge()
17046 {
17047 break;
17048 }
17049
17050 let start = range.start.to_display_point(display_snapshot);
17051 let end = range.end.to_display_point(display_snapshot);
17052 results.push((start..end, color))
17053 }
17054 }
17055 results
17056 }
17057
17058 /// Get the text ranges corresponding to the redaction query
17059 pub fn redacted_ranges(
17060 &self,
17061 search_range: Range<Anchor>,
17062 display_snapshot: &DisplaySnapshot,
17063 cx: &App,
17064 ) -> Vec<Range<DisplayPoint>> {
17065 display_snapshot
17066 .buffer_snapshot
17067 .redacted_ranges(search_range, |file| {
17068 if let Some(file) = file {
17069 file.is_private()
17070 && EditorSettings::get(
17071 Some(SettingsLocation {
17072 worktree_id: file.worktree_id(cx),
17073 path: file.path().as_ref(),
17074 }),
17075 cx,
17076 )
17077 .redact_private_values
17078 } else {
17079 false
17080 }
17081 })
17082 .map(|range| {
17083 range.start.to_display_point(display_snapshot)
17084 ..range.end.to_display_point(display_snapshot)
17085 })
17086 .collect()
17087 }
17088
17089 pub fn highlight_text<T: 'static>(
17090 &mut self,
17091 ranges: Vec<Range<Anchor>>,
17092 style: HighlightStyle,
17093 cx: &mut Context<Self>,
17094 ) {
17095 self.display_map.update(cx, |map, _| {
17096 map.highlight_text(TypeId::of::<T>(), ranges, style)
17097 });
17098 cx.notify();
17099 }
17100
17101 pub(crate) fn highlight_inlays<T: 'static>(
17102 &mut self,
17103 highlights: Vec<InlayHighlight>,
17104 style: HighlightStyle,
17105 cx: &mut Context<Self>,
17106 ) {
17107 self.display_map.update(cx, |map, _| {
17108 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17109 });
17110 cx.notify();
17111 }
17112
17113 pub fn text_highlights<'a, T: 'static>(
17114 &'a self,
17115 cx: &'a App,
17116 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17117 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17118 }
17119
17120 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17121 let cleared = self
17122 .display_map
17123 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17124 if cleared {
17125 cx.notify();
17126 }
17127 }
17128
17129 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17130 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17131 && self.focus_handle.is_focused(window)
17132 }
17133
17134 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17135 self.show_cursor_when_unfocused = is_enabled;
17136 cx.notify();
17137 }
17138
17139 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17140 cx.notify();
17141 }
17142
17143 fn on_buffer_event(
17144 &mut self,
17145 multibuffer: &Entity<MultiBuffer>,
17146 event: &multi_buffer::Event,
17147 window: &mut Window,
17148 cx: &mut Context<Self>,
17149 ) {
17150 match event {
17151 multi_buffer::Event::Edited {
17152 singleton_buffer_edited,
17153 edited_buffer: buffer_edited,
17154 } => {
17155 self.scrollbar_marker_state.dirty = true;
17156 self.active_indent_guides_state.dirty = true;
17157 self.refresh_active_diagnostics(cx);
17158 self.refresh_code_actions(window, cx);
17159 if self.has_active_inline_completion() {
17160 self.update_visible_inline_completion(window, cx);
17161 }
17162 if let Some(buffer) = buffer_edited {
17163 let buffer_id = buffer.read(cx).remote_id();
17164 if !self.registered_buffers.contains_key(&buffer_id) {
17165 if let Some(project) = self.project.as_ref() {
17166 project.update(cx, |project, cx| {
17167 self.registered_buffers.insert(
17168 buffer_id,
17169 project.register_buffer_with_language_servers(&buffer, cx),
17170 );
17171 })
17172 }
17173 }
17174 }
17175 cx.emit(EditorEvent::BufferEdited);
17176 cx.emit(SearchEvent::MatchesInvalidated);
17177 if *singleton_buffer_edited {
17178 if let Some(project) = &self.project {
17179 #[allow(clippy::mutable_key_type)]
17180 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17181 multibuffer
17182 .all_buffers()
17183 .into_iter()
17184 .filter_map(|buffer| {
17185 buffer.update(cx, |buffer, cx| {
17186 let language = buffer.language()?;
17187 let should_discard = project.update(cx, |project, cx| {
17188 project.is_local()
17189 && !project.has_language_servers_for(buffer, cx)
17190 });
17191 should_discard.not().then_some(language.clone())
17192 })
17193 })
17194 .collect::<HashSet<_>>()
17195 });
17196 if !languages_affected.is_empty() {
17197 self.refresh_inlay_hints(
17198 InlayHintRefreshReason::BufferEdited(languages_affected),
17199 cx,
17200 );
17201 }
17202 }
17203 }
17204
17205 let Some(project) = &self.project else { return };
17206 let (telemetry, is_via_ssh) = {
17207 let project = project.read(cx);
17208 let telemetry = project.client().telemetry().clone();
17209 let is_via_ssh = project.is_via_ssh();
17210 (telemetry, is_via_ssh)
17211 };
17212 refresh_linked_ranges(self, window, cx);
17213 telemetry.log_edit_event("editor", is_via_ssh);
17214 }
17215 multi_buffer::Event::ExcerptsAdded {
17216 buffer,
17217 predecessor,
17218 excerpts,
17219 } => {
17220 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17221 let buffer_id = buffer.read(cx).remote_id();
17222 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17223 if let Some(project) = &self.project {
17224 get_uncommitted_diff_for_buffer(
17225 project,
17226 [buffer.clone()],
17227 self.buffer.clone(),
17228 cx,
17229 )
17230 .detach();
17231 }
17232 }
17233 cx.emit(EditorEvent::ExcerptsAdded {
17234 buffer: buffer.clone(),
17235 predecessor: *predecessor,
17236 excerpts: excerpts.clone(),
17237 });
17238 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17239 }
17240 multi_buffer::Event::ExcerptsRemoved { ids } => {
17241 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17242 let buffer = self.buffer.read(cx);
17243 self.registered_buffers
17244 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17245 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17246 cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
17247 }
17248 multi_buffer::Event::ExcerptsEdited {
17249 excerpt_ids,
17250 buffer_ids,
17251 } => {
17252 self.display_map.update(cx, |map, cx| {
17253 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17254 });
17255 cx.emit(EditorEvent::ExcerptsEdited {
17256 ids: excerpt_ids.clone(),
17257 })
17258 }
17259 multi_buffer::Event::ExcerptsExpanded { ids } => {
17260 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17261 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17262 }
17263 multi_buffer::Event::Reparsed(buffer_id) => {
17264 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17265 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17266
17267 cx.emit(EditorEvent::Reparsed(*buffer_id));
17268 }
17269 multi_buffer::Event::DiffHunksToggled => {
17270 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17271 }
17272 multi_buffer::Event::LanguageChanged(buffer_id) => {
17273 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17274 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17275 cx.emit(EditorEvent::Reparsed(*buffer_id));
17276 cx.notify();
17277 }
17278 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17279 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17280 multi_buffer::Event::FileHandleChanged
17281 | multi_buffer::Event::Reloaded
17282 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17283 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17284 multi_buffer::Event::DiagnosticsUpdated => {
17285 self.refresh_active_diagnostics(cx);
17286 self.refresh_inline_diagnostics(true, window, cx);
17287 self.scrollbar_marker_state.dirty = true;
17288 cx.notify();
17289 }
17290 _ => {}
17291 };
17292 }
17293
17294 fn on_display_map_changed(
17295 &mut self,
17296 _: Entity<DisplayMap>,
17297 _: &mut Window,
17298 cx: &mut Context<Self>,
17299 ) {
17300 cx.notify();
17301 }
17302
17303 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17304 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17305 self.update_edit_prediction_settings(cx);
17306 self.refresh_inline_completion(true, false, window, cx);
17307 self.refresh_inlay_hints(
17308 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17309 self.selections.newest_anchor().head(),
17310 &self.buffer.read(cx).snapshot(cx),
17311 cx,
17312 )),
17313 cx,
17314 );
17315
17316 let old_cursor_shape = self.cursor_shape;
17317
17318 {
17319 let editor_settings = EditorSettings::get_global(cx);
17320 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17321 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17322 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17323 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17324 }
17325
17326 if old_cursor_shape != self.cursor_shape {
17327 cx.emit(EditorEvent::CursorShapeChanged);
17328 }
17329
17330 let project_settings = ProjectSettings::get_global(cx);
17331 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17332
17333 if self.mode.is_full() {
17334 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17335 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17336 if self.show_inline_diagnostics != show_inline_diagnostics {
17337 self.show_inline_diagnostics = show_inline_diagnostics;
17338 self.refresh_inline_diagnostics(false, window, cx);
17339 }
17340
17341 if self.git_blame_inline_enabled != inline_blame_enabled {
17342 self.toggle_git_blame_inline_internal(false, window, cx);
17343 }
17344 }
17345
17346 cx.notify();
17347 }
17348
17349 pub fn set_searchable(&mut self, searchable: bool) {
17350 self.searchable = searchable;
17351 }
17352
17353 pub fn searchable(&self) -> bool {
17354 self.searchable
17355 }
17356
17357 fn open_proposed_changes_editor(
17358 &mut self,
17359 _: &OpenProposedChangesEditor,
17360 window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) {
17363 let Some(workspace) = self.workspace() else {
17364 cx.propagate();
17365 return;
17366 };
17367
17368 let selections = self.selections.all::<usize>(cx);
17369 let multi_buffer = self.buffer.read(cx);
17370 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17371 let mut new_selections_by_buffer = HashMap::default();
17372 for selection in selections {
17373 for (buffer, range, _) in
17374 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17375 {
17376 let mut range = range.to_point(buffer);
17377 range.start.column = 0;
17378 range.end.column = buffer.line_len(range.end.row);
17379 new_selections_by_buffer
17380 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17381 .or_insert(Vec::new())
17382 .push(range)
17383 }
17384 }
17385
17386 let proposed_changes_buffers = new_selections_by_buffer
17387 .into_iter()
17388 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17389 .collect::<Vec<_>>();
17390 let proposed_changes_editor = cx.new(|cx| {
17391 ProposedChangesEditor::new(
17392 "Proposed changes",
17393 proposed_changes_buffers,
17394 self.project.clone(),
17395 window,
17396 cx,
17397 )
17398 });
17399
17400 window.defer(cx, move |window, cx| {
17401 workspace.update(cx, |workspace, cx| {
17402 workspace.active_pane().update(cx, |pane, cx| {
17403 pane.add_item(
17404 Box::new(proposed_changes_editor),
17405 true,
17406 true,
17407 None,
17408 window,
17409 cx,
17410 );
17411 });
17412 });
17413 });
17414 }
17415
17416 pub fn open_excerpts_in_split(
17417 &mut self,
17418 _: &OpenExcerptsSplit,
17419 window: &mut Window,
17420 cx: &mut Context<Self>,
17421 ) {
17422 self.open_excerpts_common(None, true, window, cx)
17423 }
17424
17425 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
17426 self.open_excerpts_common(None, false, window, cx)
17427 }
17428
17429 fn open_excerpts_common(
17430 &mut self,
17431 jump_data: Option<JumpData>,
17432 split: bool,
17433 window: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 let Some(workspace) = self.workspace() else {
17437 cx.propagate();
17438 return;
17439 };
17440
17441 if self.buffer.read(cx).is_singleton() {
17442 cx.propagate();
17443 return;
17444 }
17445
17446 let mut new_selections_by_buffer = HashMap::default();
17447 match &jump_data {
17448 Some(JumpData::MultiBufferPoint {
17449 excerpt_id,
17450 position,
17451 anchor,
17452 line_offset_from_top,
17453 }) => {
17454 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17455 if let Some(buffer) = multi_buffer_snapshot
17456 .buffer_id_for_excerpt(*excerpt_id)
17457 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
17458 {
17459 let buffer_snapshot = buffer.read(cx).snapshot();
17460 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
17461 language::ToPoint::to_point(anchor, &buffer_snapshot)
17462 } else {
17463 buffer_snapshot.clip_point(*position, Bias::Left)
17464 };
17465 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
17466 new_selections_by_buffer.insert(
17467 buffer,
17468 (
17469 vec![jump_to_offset..jump_to_offset],
17470 Some(*line_offset_from_top),
17471 ),
17472 );
17473 }
17474 }
17475 Some(JumpData::MultiBufferRow {
17476 row,
17477 line_offset_from_top,
17478 }) => {
17479 let point = MultiBufferPoint::new(row.0, 0);
17480 if let Some((buffer, buffer_point, _)) =
17481 self.buffer.read(cx).point_to_buffer_point(point, cx)
17482 {
17483 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
17484 new_selections_by_buffer
17485 .entry(buffer)
17486 .or_insert((Vec::new(), Some(*line_offset_from_top)))
17487 .0
17488 .push(buffer_offset..buffer_offset)
17489 }
17490 }
17491 None => {
17492 let selections = self.selections.all::<usize>(cx);
17493 let multi_buffer = self.buffer.read(cx);
17494 for selection in selections {
17495 for (snapshot, range, _, anchor) in multi_buffer
17496 .snapshot(cx)
17497 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
17498 {
17499 if let Some(anchor) = anchor {
17500 // selection is in a deleted hunk
17501 let Some(buffer_id) = anchor.buffer_id else {
17502 continue;
17503 };
17504 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
17505 continue;
17506 };
17507 let offset = text::ToOffset::to_offset(
17508 &anchor.text_anchor,
17509 &buffer_handle.read(cx).snapshot(),
17510 );
17511 let range = offset..offset;
17512 new_selections_by_buffer
17513 .entry(buffer_handle)
17514 .or_insert((Vec::new(), None))
17515 .0
17516 .push(range)
17517 } else {
17518 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
17519 else {
17520 continue;
17521 };
17522 new_selections_by_buffer
17523 .entry(buffer_handle)
17524 .or_insert((Vec::new(), None))
17525 .0
17526 .push(range)
17527 }
17528 }
17529 }
17530 }
17531 }
17532
17533 new_selections_by_buffer
17534 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
17535
17536 if new_selections_by_buffer.is_empty() {
17537 return;
17538 }
17539
17540 // We defer the pane interaction because we ourselves are a workspace item
17541 // and activating a new item causes the pane to call a method on us reentrantly,
17542 // which panics if we're on the stack.
17543 window.defer(cx, move |window, cx| {
17544 workspace.update(cx, |workspace, cx| {
17545 let pane = if split {
17546 workspace.adjacent_pane(window, cx)
17547 } else {
17548 workspace.active_pane().clone()
17549 };
17550
17551 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
17552 let editor = buffer
17553 .read(cx)
17554 .file()
17555 .is_none()
17556 .then(|| {
17557 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
17558 // so `workspace.open_project_item` will never find them, always opening a new editor.
17559 // Instead, we try to activate the existing editor in the pane first.
17560 let (editor, pane_item_index) =
17561 pane.read(cx).items().enumerate().find_map(|(i, item)| {
17562 let editor = item.downcast::<Editor>()?;
17563 let singleton_buffer =
17564 editor.read(cx).buffer().read(cx).as_singleton()?;
17565 if singleton_buffer == buffer {
17566 Some((editor, i))
17567 } else {
17568 None
17569 }
17570 })?;
17571 pane.update(cx, |pane, cx| {
17572 pane.activate_item(pane_item_index, true, true, window, cx)
17573 });
17574 Some(editor)
17575 })
17576 .flatten()
17577 .unwrap_or_else(|| {
17578 workspace.open_project_item::<Self>(
17579 pane.clone(),
17580 buffer,
17581 true,
17582 true,
17583 window,
17584 cx,
17585 )
17586 });
17587
17588 editor.update(cx, |editor, cx| {
17589 let autoscroll = match scroll_offset {
17590 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
17591 None => Autoscroll::newest(),
17592 };
17593 let nav_history = editor.nav_history.take();
17594 editor.change_selections(Some(autoscroll), window, cx, |s| {
17595 s.select_ranges(ranges);
17596 });
17597 editor.nav_history = nav_history;
17598 });
17599 }
17600 })
17601 });
17602 }
17603
17604 // For now, don't allow opening excerpts in buffers that aren't backed by
17605 // regular project files.
17606 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
17607 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
17608 }
17609
17610 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
17611 let snapshot = self.buffer.read(cx).read(cx);
17612 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
17613 Some(
17614 ranges
17615 .iter()
17616 .map(move |range| {
17617 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
17618 })
17619 .collect(),
17620 )
17621 }
17622
17623 fn selection_replacement_ranges(
17624 &self,
17625 range: Range<OffsetUtf16>,
17626 cx: &mut App,
17627 ) -> Vec<Range<OffsetUtf16>> {
17628 let selections = self.selections.all::<OffsetUtf16>(cx);
17629 let newest_selection = selections
17630 .iter()
17631 .max_by_key(|selection| selection.id)
17632 .unwrap();
17633 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
17634 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
17635 let snapshot = self.buffer.read(cx).read(cx);
17636 selections
17637 .into_iter()
17638 .map(|mut selection| {
17639 selection.start.0 =
17640 (selection.start.0 as isize).saturating_add(start_delta) as usize;
17641 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
17642 snapshot.clip_offset_utf16(selection.start, Bias::Left)
17643 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
17644 })
17645 .collect()
17646 }
17647
17648 fn report_editor_event(
17649 &self,
17650 event_type: &'static str,
17651 file_extension: Option<String>,
17652 cx: &App,
17653 ) {
17654 if cfg!(any(test, feature = "test-support")) {
17655 return;
17656 }
17657
17658 let Some(project) = &self.project else { return };
17659
17660 // If None, we are in a file without an extension
17661 let file = self
17662 .buffer
17663 .read(cx)
17664 .as_singleton()
17665 .and_then(|b| b.read(cx).file());
17666 let file_extension = file_extension.or(file
17667 .as_ref()
17668 .and_then(|file| Path::new(file.file_name(cx)).extension())
17669 .and_then(|e| e.to_str())
17670 .map(|a| a.to_string()));
17671
17672 let vim_mode = cx
17673 .global::<SettingsStore>()
17674 .raw_user_settings()
17675 .get("vim_mode")
17676 == Some(&serde_json::Value::Bool(true));
17677
17678 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
17679 let copilot_enabled = edit_predictions_provider
17680 == language::language_settings::EditPredictionProvider::Copilot;
17681 let copilot_enabled_for_language = self
17682 .buffer
17683 .read(cx)
17684 .language_settings(cx)
17685 .show_edit_predictions;
17686
17687 let project = project.read(cx);
17688 telemetry::event!(
17689 event_type,
17690 file_extension,
17691 vim_mode,
17692 copilot_enabled,
17693 copilot_enabled_for_language,
17694 edit_predictions_provider,
17695 is_via_ssh = project.is_via_ssh(),
17696 );
17697 }
17698
17699 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
17700 /// with each line being an array of {text, highlight} objects.
17701 fn copy_highlight_json(
17702 &mut self,
17703 _: &CopyHighlightJson,
17704 window: &mut Window,
17705 cx: &mut Context<Self>,
17706 ) {
17707 #[derive(Serialize)]
17708 struct Chunk<'a> {
17709 text: String,
17710 highlight: Option<&'a str>,
17711 }
17712
17713 let snapshot = self.buffer.read(cx).snapshot(cx);
17714 let range = self
17715 .selected_text_range(false, window, cx)
17716 .and_then(|selection| {
17717 if selection.range.is_empty() {
17718 None
17719 } else {
17720 Some(selection.range)
17721 }
17722 })
17723 .unwrap_or_else(|| 0..snapshot.len());
17724
17725 let chunks = snapshot.chunks(range, true);
17726 let mut lines = Vec::new();
17727 let mut line: VecDeque<Chunk> = VecDeque::new();
17728
17729 let Some(style) = self.style.as_ref() else {
17730 return;
17731 };
17732
17733 for chunk in chunks {
17734 let highlight = chunk
17735 .syntax_highlight_id
17736 .and_then(|id| id.name(&style.syntax));
17737 let mut chunk_lines = chunk.text.split('\n').peekable();
17738 while let Some(text) = chunk_lines.next() {
17739 let mut merged_with_last_token = false;
17740 if let Some(last_token) = line.back_mut() {
17741 if last_token.highlight == highlight {
17742 last_token.text.push_str(text);
17743 merged_with_last_token = true;
17744 }
17745 }
17746
17747 if !merged_with_last_token {
17748 line.push_back(Chunk {
17749 text: text.into(),
17750 highlight,
17751 });
17752 }
17753
17754 if chunk_lines.peek().is_some() {
17755 if line.len() > 1 && line.front().unwrap().text.is_empty() {
17756 line.pop_front();
17757 }
17758 if line.len() > 1 && line.back().unwrap().text.is_empty() {
17759 line.pop_back();
17760 }
17761
17762 lines.push(mem::take(&mut line));
17763 }
17764 }
17765 }
17766
17767 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
17768 return;
17769 };
17770 cx.write_to_clipboard(ClipboardItem::new_string(lines));
17771 }
17772
17773 pub fn open_context_menu(
17774 &mut self,
17775 _: &OpenContextMenu,
17776 window: &mut Window,
17777 cx: &mut Context<Self>,
17778 ) {
17779 self.request_autoscroll(Autoscroll::newest(), cx);
17780 let position = self.selections.newest_display(cx).start;
17781 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
17782 }
17783
17784 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
17785 &self.inlay_hint_cache
17786 }
17787
17788 pub fn replay_insert_event(
17789 &mut self,
17790 text: &str,
17791 relative_utf16_range: Option<Range<isize>>,
17792 window: &mut Window,
17793 cx: &mut Context<Self>,
17794 ) {
17795 if !self.input_enabled {
17796 cx.emit(EditorEvent::InputIgnored { text: text.into() });
17797 return;
17798 }
17799 if let Some(relative_utf16_range) = relative_utf16_range {
17800 let selections = self.selections.all::<OffsetUtf16>(cx);
17801 self.change_selections(None, window, cx, |s| {
17802 let new_ranges = selections.into_iter().map(|range| {
17803 let start = OffsetUtf16(
17804 range
17805 .head()
17806 .0
17807 .saturating_add_signed(relative_utf16_range.start),
17808 );
17809 let end = OffsetUtf16(
17810 range
17811 .head()
17812 .0
17813 .saturating_add_signed(relative_utf16_range.end),
17814 );
17815 start..end
17816 });
17817 s.select_ranges(new_ranges);
17818 });
17819 }
17820
17821 self.handle_input(text, window, cx);
17822 }
17823
17824 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
17825 let Some(provider) = self.semantics_provider.as_ref() else {
17826 return false;
17827 };
17828
17829 let mut supports = false;
17830 self.buffer().update(cx, |this, cx| {
17831 this.for_each_buffer(|buffer| {
17832 supports |= provider.supports_inlay_hints(buffer, cx);
17833 });
17834 });
17835
17836 supports
17837 }
17838
17839 pub fn is_focused(&self, window: &Window) -> bool {
17840 self.focus_handle.is_focused(window)
17841 }
17842
17843 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17844 cx.emit(EditorEvent::Focused);
17845
17846 if let Some(descendant) = self
17847 .last_focused_descendant
17848 .take()
17849 .and_then(|descendant| descendant.upgrade())
17850 {
17851 window.focus(&descendant);
17852 } else {
17853 if let Some(blame) = self.blame.as_ref() {
17854 blame.update(cx, GitBlame::focus)
17855 }
17856
17857 self.blink_manager.update(cx, BlinkManager::enable);
17858 self.show_cursor_names(window, cx);
17859 self.buffer.update(cx, |buffer, cx| {
17860 buffer.finalize_last_transaction(cx);
17861 if self.leader_peer_id.is_none() {
17862 buffer.set_active_selections(
17863 &self.selections.disjoint_anchors(),
17864 self.selections.line_mode,
17865 self.cursor_shape,
17866 cx,
17867 );
17868 }
17869 });
17870 }
17871 }
17872
17873 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
17874 cx.emit(EditorEvent::FocusedIn)
17875 }
17876
17877 fn handle_focus_out(
17878 &mut self,
17879 event: FocusOutEvent,
17880 _window: &mut Window,
17881 cx: &mut Context<Self>,
17882 ) {
17883 if event.blurred != self.focus_handle {
17884 self.last_focused_descendant = Some(event.blurred);
17885 }
17886 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
17887 }
17888
17889 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17890 self.blink_manager.update(cx, BlinkManager::disable);
17891 self.buffer
17892 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
17893
17894 if let Some(blame) = self.blame.as_ref() {
17895 blame.update(cx, GitBlame::blur)
17896 }
17897 if !self.hover_state.focused(window, cx) {
17898 hide_hover(self, cx);
17899 }
17900 if !self
17901 .context_menu
17902 .borrow()
17903 .as_ref()
17904 .is_some_and(|context_menu| context_menu.focused(window, cx))
17905 {
17906 self.hide_context_menu(window, cx);
17907 }
17908 self.discard_inline_completion(false, cx);
17909 cx.emit(EditorEvent::Blurred);
17910 cx.notify();
17911 }
17912
17913 pub fn register_action<A: Action>(
17914 &mut self,
17915 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
17916 ) -> Subscription {
17917 let id = self.next_editor_action_id.post_inc();
17918 let listener = Arc::new(listener);
17919 self.editor_actions.borrow_mut().insert(
17920 id,
17921 Box::new(move |window, _| {
17922 let listener = listener.clone();
17923 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
17924 let action = action.downcast_ref().unwrap();
17925 if phase == DispatchPhase::Bubble {
17926 listener(action, window, cx)
17927 }
17928 })
17929 }),
17930 );
17931
17932 let editor_actions = self.editor_actions.clone();
17933 Subscription::new(move || {
17934 editor_actions.borrow_mut().remove(&id);
17935 })
17936 }
17937
17938 pub fn file_header_size(&self) -> u32 {
17939 FILE_HEADER_HEIGHT
17940 }
17941
17942 pub fn restore(
17943 &mut self,
17944 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
17945 window: &mut Window,
17946 cx: &mut Context<Self>,
17947 ) {
17948 let workspace = self.workspace();
17949 let project = self.project.as_ref();
17950 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
17951 let mut tasks = Vec::new();
17952 for (buffer_id, changes) in revert_changes {
17953 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
17954 buffer.update(cx, |buffer, cx| {
17955 buffer.edit(
17956 changes
17957 .into_iter()
17958 .map(|(range, text)| (range, text.to_string())),
17959 None,
17960 cx,
17961 );
17962 });
17963
17964 if let Some(project) =
17965 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
17966 {
17967 project.update(cx, |project, cx| {
17968 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
17969 })
17970 }
17971 }
17972 }
17973 tasks
17974 });
17975 cx.spawn_in(window, async move |_, cx| {
17976 for (buffer, task) in save_tasks {
17977 let result = task.await;
17978 if result.is_err() {
17979 let Some(path) = buffer
17980 .read_with(cx, |buffer, cx| buffer.project_path(cx))
17981 .ok()
17982 else {
17983 continue;
17984 };
17985 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
17986 let Some(task) = cx
17987 .update_window_entity(&workspace, |workspace, window, cx| {
17988 workspace
17989 .open_path_preview(path, None, false, false, false, window, cx)
17990 })
17991 .ok()
17992 else {
17993 continue;
17994 };
17995 task.await.log_err();
17996 }
17997 }
17998 }
17999 })
18000 .detach();
18001 self.change_selections(None, window, cx, |selections| selections.refresh());
18002 }
18003
18004 pub fn to_pixel_point(
18005 &self,
18006 source: multi_buffer::Anchor,
18007 editor_snapshot: &EditorSnapshot,
18008 window: &mut Window,
18009 ) -> Option<gpui::Point<Pixels>> {
18010 let source_point = source.to_display_point(editor_snapshot);
18011 self.display_to_pixel_point(source_point, editor_snapshot, window)
18012 }
18013
18014 pub fn display_to_pixel_point(
18015 &self,
18016 source: DisplayPoint,
18017 editor_snapshot: &EditorSnapshot,
18018 window: &mut Window,
18019 ) -> Option<gpui::Point<Pixels>> {
18020 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18021 let text_layout_details = self.text_layout_details(window);
18022 let scroll_top = text_layout_details
18023 .scroll_anchor
18024 .scroll_position(editor_snapshot)
18025 .y;
18026
18027 if source.row().as_f32() < scroll_top.floor() {
18028 return None;
18029 }
18030 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18031 let source_y = line_height * (source.row().as_f32() - scroll_top);
18032 Some(gpui::Point::new(source_x, source_y))
18033 }
18034
18035 pub fn has_visible_completions_menu(&self) -> bool {
18036 !self.edit_prediction_preview_is_active()
18037 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18038 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18039 })
18040 }
18041
18042 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18043 self.addons
18044 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18045 }
18046
18047 pub fn unregister_addon<T: Addon>(&mut self) {
18048 self.addons.remove(&std::any::TypeId::of::<T>());
18049 }
18050
18051 pub fn addon<T: Addon>(&self) -> Option<&T> {
18052 let type_id = std::any::TypeId::of::<T>();
18053 self.addons
18054 .get(&type_id)
18055 .and_then(|item| item.to_any().downcast_ref::<T>())
18056 }
18057
18058 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18059 let text_layout_details = self.text_layout_details(window);
18060 let style = &text_layout_details.editor_style;
18061 let font_id = window.text_system().resolve_font(&style.text.font());
18062 let font_size = style.text.font_size.to_pixels(window.rem_size());
18063 let line_height = style.text.line_height_in_pixels(window.rem_size());
18064 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18065
18066 gpui::Size::new(em_width, line_height)
18067 }
18068
18069 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18070 self.load_diff_task.clone()
18071 }
18072
18073 fn read_metadata_from_db(
18074 &mut self,
18075 item_id: u64,
18076 workspace_id: WorkspaceId,
18077 window: &mut Window,
18078 cx: &mut Context<Editor>,
18079 ) {
18080 if self.is_singleton(cx)
18081 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18082 {
18083 let buffer_snapshot = OnceCell::new();
18084
18085 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18086 if !folds.is_empty() {
18087 let snapshot =
18088 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18089 self.fold_ranges(
18090 folds
18091 .into_iter()
18092 .map(|(start, end)| {
18093 snapshot.clip_offset(start, Bias::Left)
18094 ..snapshot.clip_offset(end, Bias::Right)
18095 })
18096 .collect(),
18097 false,
18098 window,
18099 cx,
18100 );
18101 }
18102 }
18103
18104 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18105 if !selections.is_empty() {
18106 let snapshot =
18107 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18108 self.change_selections(None, window, cx, |s| {
18109 s.select_ranges(selections.into_iter().map(|(start, end)| {
18110 snapshot.clip_offset(start, Bias::Left)
18111 ..snapshot.clip_offset(end, Bias::Right)
18112 }));
18113 });
18114 }
18115 };
18116 }
18117
18118 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18119 }
18120}
18121
18122// Consider user intent and default settings
18123fn choose_completion_range(
18124 completion: &Completion,
18125 intent: CompletionIntent,
18126 buffer: &Entity<Buffer>,
18127 cx: &mut Context<Editor>,
18128) -> Range<usize> {
18129 fn should_replace(
18130 completion: &Completion,
18131 insert_range: &Range<text::Anchor>,
18132 intent: CompletionIntent,
18133 completion_mode_setting: LspInsertMode,
18134 buffer: &Buffer,
18135 ) -> bool {
18136 // specific actions take precedence over settings
18137 match intent {
18138 CompletionIntent::CompleteWithInsert => return false,
18139 CompletionIntent::CompleteWithReplace => return true,
18140 CompletionIntent::Complete | CompletionIntent::Compose => {}
18141 }
18142
18143 match completion_mode_setting {
18144 LspInsertMode::Insert => false,
18145 LspInsertMode::Replace => true,
18146 LspInsertMode::ReplaceSubsequence => {
18147 let mut text_to_replace = buffer.chars_for_range(
18148 buffer.anchor_before(completion.replace_range.start)
18149 ..buffer.anchor_after(completion.replace_range.end),
18150 );
18151 let mut completion_text = completion.new_text.chars();
18152
18153 // is `text_to_replace` a subsequence of `completion_text`
18154 text_to_replace
18155 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18156 }
18157 LspInsertMode::ReplaceSuffix => {
18158 let range_after_cursor = insert_range.end..completion.replace_range.end;
18159
18160 let text_after_cursor = buffer
18161 .text_for_range(
18162 buffer.anchor_before(range_after_cursor.start)
18163 ..buffer.anchor_after(range_after_cursor.end),
18164 )
18165 .collect::<String>();
18166 completion.new_text.ends_with(&text_after_cursor)
18167 }
18168 }
18169 }
18170
18171 let buffer = buffer.read(cx);
18172
18173 if let CompletionSource::Lsp {
18174 insert_range: Some(insert_range),
18175 ..
18176 } = &completion.source
18177 {
18178 let completion_mode_setting =
18179 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18180 .completions
18181 .lsp_insert_mode;
18182
18183 if !should_replace(
18184 completion,
18185 &insert_range,
18186 intent,
18187 completion_mode_setting,
18188 buffer,
18189 ) {
18190 return insert_range.to_offset(buffer);
18191 }
18192 }
18193
18194 completion.replace_range.to_offset(buffer)
18195}
18196
18197fn insert_extra_newline_brackets(
18198 buffer: &MultiBufferSnapshot,
18199 range: Range<usize>,
18200 language: &language::LanguageScope,
18201) -> bool {
18202 let leading_whitespace_len = buffer
18203 .reversed_chars_at(range.start)
18204 .take_while(|c| c.is_whitespace() && *c != '\n')
18205 .map(|c| c.len_utf8())
18206 .sum::<usize>();
18207 let trailing_whitespace_len = buffer
18208 .chars_at(range.end)
18209 .take_while(|c| c.is_whitespace() && *c != '\n')
18210 .map(|c| c.len_utf8())
18211 .sum::<usize>();
18212 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18213
18214 language.brackets().any(|(pair, enabled)| {
18215 let pair_start = pair.start.trim_end();
18216 let pair_end = pair.end.trim_start();
18217
18218 enabled
18219 && pair.newline
18220 && buffer.contains_str_at(range.end, pair_end)
18221 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18222 })
18223}
18224
18225fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18226 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18227 [(buffer, range, _)] => (*buffer, range.clone()),
18228 _ => return false,
18229 };
18230 let pair = {
18231 let mut result: Option<BracketMatch> = None;
18232
18233 for pair in buffer
18234 .all_bracket_ranges(range.clone())
18235 .filter(move |pair| {
18236 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18237 })
18238 {
18239 let len = pair.close_range.end - pair.open_range.start;
18240
18241 if let Some(existing) = &result {
18242 let existing_len = existing.close_range.end - existing.open_range.start;
18243 if len > existing_len {
18244 continue;
18245 }
18246 }
18247
18248 result = Some(pair);
18249 }
18250
18251 result
18252 };
18253 let Some(pair) = pair else {
18254 return false;
18255 };
18256 pair.newline_only
18257 && buffer
18258 .chars_for_range(pair.open_range.end..range.start)
18259 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18260 .all(|c| c.is_whitespace() && c != '\n')
18261}
18262
18263fn get_uncommitted_diff_for_buffer(
18264 project: &Entity<Project>,
18265 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18266 buffer: Entity<MultiBuffer>,
18267 cx: &mut App,
18268) -> Task<()> {
18269 let mut tasks = Vec::new();
18270 project.update(cx, |project, cx| {
18271 for buffer in buffers {
18272 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18273 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18274 }
18275 }
18276 });
18277 cx.spawn(async move |cx| {
18278 let diffs = future::join_all(tasks).await;
18279 buffer
18280 .update(cx, |buffer, cx| {
18281 for diff in diffs.into_iter().flatten() {
18282 buffer.add_diff(diff, cx);
18283 }
18284 })
18285 .ok();
18286 })
18287}
18288
18289fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18290 let tab_size = tab_size.get() as usize;
18291 let mut width = offset;
18292
18293 for ch in text.chars() {
18294 width += if ch == '\t' {
18295 tab_size - (width % tab_size)
18296 } else {
18297 1
18298 };
18299 }
18300
18301 width - offset
18302}
18303
18304#[cfg(test)]
18305mod tests {
18306 use super::*;
18307
18308 #[test]
18309 fn test_string_size_with_expanded_tabs() {
18310 let nz = |val| NonZeroU32::new(val).unwrap();
18311 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18312 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18313 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18314 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18315 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18316 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18317 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18318 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18319 }
18320}
18321
18322/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18323struct WordBreakingTokenizer<'a> {
18324 input: &'a str,
18325}
18326
18327impl<'a> WordBreakingTokenizer<'a> {
18328 fn new(input: &'a str) -> Self {
18329 Self { input }
18330 }
18331}
18332
18333fn is_char_ideographic(ch: char) -> bool {
18334 use unicode_script::Script::*;
18335 use unicode_script::UnicodeScript;
18336 matches!(ch.script(), Han | Tangut | Yi)
18337}
18338
18339fn is_grapheme_ideographic(text: &str) -> bool {
18340 text.chars().any(is_char_ideographic)
18341}
18342
18343fn is_grapheme_whitespace(text: &str) -> bool {
18344 text.chars().any(|x| x.is_whitespace())
18345}
18346
18347fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18348 text.chars().next().map_or(false, |ch| {
18349 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18350 })
18351}
18352
18353#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18354enum WordBreakToken<'a> {
18355 Word { token: &'a str, grapheme_len: usize },
18356 InlineWhitespace { token: &'a str, grapheme_len: usize },
18357 Newline,
18358}
18359
18360impl<'a> Iterator for WordBreakingTokenizer<'a> {
18361 /// Yields a span, the count of graphemes in the token, and whether it was
18362 /// whitespace. Note that it also breaks at word boundaries.
18363 type Item = WordBreakToken<'a>;
18364
18365 fn next(&mut self) -> Option<Self::Item> {
18366 use unicode_segmentation::UnicodeSegmentation;
18367 if self.input.is_empty() {
18368 return None;
18369 }
18370
18371 let mut iter = self.input.graphemes(true).peekable();
18372 let mut offset = 0;
18373 let mut grapheme_len = 0;
18374 if let Some(first_grapheme) = iter.next() {
18375 let is_newline = first_grapheme == "\n";
18376 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18377 offset += first_grapheme.len();
18378 grapheme_len += 1;
18379 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18380 if let Some(grapheme) = iter.peek().copied() {
18381 if should_stay_with_preceding_ideograph(grapheme) {
18382 offset += grapheme.len();
18383 grapheme_len += 1;
18384 }
18385 }
18386 } else {
18387 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18388 let mut next_word_bound = words.peek().copied();
18389 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18390 next_word_bound = words.next();
18391 }
18392 while let Some(grapheme) = iter.peek().copied() {
18393 if next_word_bound.map_or(false, |(i, _)| i == offset) {
18394 break;
18395 };
18396 if is_grapheme_whitespace(grapheme) != is_whitespace
18397 || (grapheme == "\n") != is_newline
18398 {
18399 break;
18400 };
18401 offset += grapheme.len();
18402 grapheme_len += 1;
18403 iter.next();
18404 }
18405 }
18406 let token = &self.input[..offset];
18407 self.input = &self.input[offset..];
18408 if token == "\n" {
18409 Some(WordBreakToken::Newline)
18410 } else if is_whitespace {
18411 Some(WordBreakToken::InlineWhitespace {
18412 token,
18413 grapheme_len,
18414 })
18415 } else {
18416 Some(WordBreakToken::Word {
18417 token,
18418 grapheme_len,
18419 })
18420 }
18421 } else {
18422 None
18423 }
18424 }
18425}
18426
18427#[test]
18428fn test_word_breaking_tokenizer() {
18429 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
18430 ("", &[]),
18431 (" ", &[whitespace(" ", 2)]),
18432 ("Ʒ", &[word("Ʒ", 1)]),
18433 ("Ǽ", &[word("Ǽ", 1)]),
18434 ("⋑", &[word("⋑", 1)]),
18435 ("⋑⋑", &[word("⋑⋑", 2)]),
18436 (
18437 "原理,进而",
18438 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
18439 ),
18440 (
18441 "hello world",
18442 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
18443 ),
18444 (
18445 "hello, world",
18446 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
18447 ),
18448 (
18449 " hello world",
18450 &[
18451 whitespace(" ", 2),
18452 word("hello", 5),
18453 whitespace(" ", 1),
18454 word("world", 5),
18455 ],
18456 ),
18457 (
18458 "这是什么 \n 钢笔",
18459 &[
18460 word("这", 1),
18461 word("是", 1),
18462 word("什", 1),
18463 word("么", 1),
18464 whitespace(" ", 1),
18465 newline(),
18466 whitespace(" ", 1),
18467 word("钢", 1),
18468 word("笔", 1),
18469 ],
18470 ),
18471 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
18472 ];
18473
18474 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18475 WordBreakToken::Word {
18476 token,
18477 grapheme_len,
18478 }
18479 }
18480
18481 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
18482 WordBreakToken::InlineWhitespace {
18483 token,
18484 grapheme_len,
18485 }
18486 }
18487
18488 fn newline() -> WordBreakToken<'static> {
18489 WordBreakToken::Newline
18490 }
18491
18492 for (input, result) in tests {
18493 assert_eq!(
18494 WordBreakingTokenizer::new(input)
18495 .collect::<Vec<_>>()
18496 .as_slice(),
18497 *result,
18498 );
18499 }
18500}
18501
18502fn wrap_with_prefix(
18503 line_prefix: String,
18504 unwrapped_text: String,
18505 wrap_column: usize,
18506 tab_size: NonZeroU32,
18507 preserve_existing_whitespace: bool,
18508) -> String {
18509 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
18510 let mut wrapped_text = String::new();
18511 let mut current_line = line_prefix.clone();
18512
18513 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
18514 let mut current_line_len = line_prefix_len;
18515 let mut in_whitespace = false;
18516 for token in tokenizer {
18517 let have_preceding_whitespace = in_whitespace;
18518 match token {
18519 WordBreakToken::Word {
18520 token,
18521 grapheme_len,
18522 } => {
18523 in_whitespace = false;
18524 if current_line_len + grapheme_len > wrap_column
18525 && current_line_len != line_prefix_len
18526 {
18527 wrapped_text.push_str(current_line.trim_end());
18528 wrapped_text.push('\n');
18529 current_line.truncate(line_prefix.len());
18530 current_line_len = line_prefix_len;
18531 }
18532 current_line.push_str(token);
18533 current_line_len += grapheme_len;
18534 }
18535 WordBreakToken::InlineWhitespace {
18536 mut token,
18537 mut grapheme_len,
18538 } => {
18539 in_whitespace = true;
18540 if have_preceding_whitespace && !preserve_existing_whitespace {
18541 continue;
18542 }
18543 if !preserve_existing_whitespace {
18544 token = " ";
18545 grapheme_len = 1;
18546 }
18547 if current_line_len + grapheme_len > wrap_column {
18548 wrapped_text.push_str(current_line.trim_end());
18549 wrapped_text.push('\n');
18550 current_line.truncate(line_prefix.len());
18551 current_line_len = line_prefix_len;
18552 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
18553 current_line.push_str(token);
18554 current_line_len += grapheme_len;
18555 }
18556 }
18557 WordBreakToken::Newline => {
18558 in_whitespace = true;
18559 if preserve_existing_whitespace {
18560 wrapped_text.push_str(current_line.trim_end());
18561 wrapped_text.push('\n');
18562 current_line.truncate(line_prefix.len());
18563 current_line_len = line_prefix_len;
18564 } else if have_preceding_whitespace {
18565 continue;
18566 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
18567 {
18568 wrapped_text.push_str(current_line.trim_end());
18569 wrapped_text.push('\n');
18570 current_line.truncate(line_prefix.len());
18571 current_line_len = line_prefix_len;
18572 } else if current_line_len != line_prefix_len {
18573 current_line.push(' ');
18574 current_line_len += 1;
18575 }
18576 }
18577 }
18578 }
18579
18580 if !current_line.is_empty() {
18581 wrapped_text.push_str(¤t_line);
18582 }
18583 wrapped_text
18584}
18585
18586#[test]
18587fn test_wrap_with_prefix() {
18588 assert_eq!(
18589 wrap_with_prefix(
18590 "# ".to_string(),
18591 "abcdefg".to_string(),
18592 4,
18593 NonZeroU32::new(4).unwrap(),
18594 false,
18595 ),
18596 "# abcdefg"
18597 );
18598 assert_eq!(
18599 wrap_with_prefix(
18600 "".to_string(),
18601 "\thello world".to_string(),
18602 8,
18603 NonZeroU32::new(4).unwrap(),
18604 false,
18605 ),
18606 "hello\nworld"
18607 );
18608 assert_eq!(
18609 wrap_with_prefix(
18610 "// ".to_string(),
18611 "xx \nyy zz aa bb cc".to_string(),
18612 12,
18613 NonZeroU32::new(4).unwrap(),
18614 false,
18615 ),
18616 "// xx yy zz\n// aa bb cc"
18617 );
18618 assert_eq!(
18619 wrap_with_prefix(
18620 String::new(),
18621 "这是什么 \n 钢笔".to_string(),
18622 3,
18623 NonZeroU32::new(4).unwrap(),
18624 false,
18625 ),
18626 "这是什\n么 钢\n笔"
18627 );
18628}
18629
18630pub trait CollaborationHub {
18631 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
18632 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
18633 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
18634}
18635
18636impl CollaborationHub for Entity<Project> {
18637 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
18638 self.read(cx).collaborators()
18639 }
18640
18641 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
18642 self.read(cx).user_store().read(cx).participant_indices()
18643 }
18644
18645 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
18646 let this = self.read(cx);
18647 let user_ids = this.collaborators().values().map(|c| c.user_id);
18648 this.user_store().read_with(cx, |user_store, cx| {
18649 user_store.participant_names(user_ids, cx)
18650 })
18651 }
18652}
18653
18654pub trait SemanticsProvider {
18655 fn hover(
18656 &self,
18657 buffer: &Entity<Buffer>,
18658 position: text::Anchor,
18659 cx: &mut App,
18660 ) -> Option<Task<Vec<project::Hover>>>;
18661
18662 fn inlay_hints(
18663 &self,
18664 buffer_handle: Entity<Buffer>,
18665 range: Range<text::Anchor>,
18666 cx: &mut App,
18667 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
18668
18669 fn resolve_inlay_hint(
18670 &self,
18671 hint: InlayHint,
18672 buffer_handle: Entity<Buffer>,
18673 server_id: LanguageServerId,
18674 cx: &mut App,
18675 ) -> Option<Task<anyhow::Result<InlayHint>>>;
18676
18677 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
18678
18679 fn document_highlights(
18680 &self,
18681 buffer: &Entity<Buffer>,
18682 position: text::Anchor,
18683 cx: &mut App,
18684 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
18685
18686 fn definitions(
18687 &self,
18688 buffer: &Entity<Buffer>,
18689 position: text::Anchor,
18690 kind: GotoDefinitionKind,
18691 cx: &mut App,
18692 ) -> Option<Task<Result<Vec<LocationLink>>>>;
18693
18694 fn range_for_rename(
18695 &self,
18696 buffer: &Entity<Buffer>,
18697 position: text::Anchor,
18698 cx: &mut App,
18699 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
18700
18701 fn perform_rename(
18702 &self,
18703 buffer: &Entity<Buffer>,
18704 position: text::Anchor,
18705 new_name: String,
18706 cx: &mut App,
18707 ) -> Option<Task<Result<ProjectTransaction>>>;
18708}
18709
18710pub trait CompletionProvider {
18711 fn completions(
18712 &self,
18713 excerpt_id: ExcerptId,
18714 buffer: &Entity<Buffer>,
18715 buffer_position: text::Anchor,
18716 trigger: CompletionContext,
18717 window: &mut Window,
18718 cx: &mut Context<Editor>,
18719 ) -> Task<Result<Option<Vec<Completion>>>>;
18720
18721 fn resolve_completions(
18722 &self,
18723 buffer: Entity<Buffer>,
18724 completion_indices: Vec<usize>,
18725 completions: Rc<RefCell<Box<[Completion]>>>,
18726 cx: &mut Context<Editor>,
18727 ) -> Task<Result<bool>>;
18728
18729 fn apply_additional_edits_for_completion(
18730 &self,
18731 _buffer: Entity<Buffer>,
18732 _completions: Rc<RefCell<Box<[Completion]>>>,
18733 _completion_index: usize,
18734 _push_to_history: bool,
18735 _cx: &mut Context<Editor>,
18736 ) -> Task<Result<Option<language::Transaction>>> {
18737 Task::ready(Ok(None))
18738 }
18739
18740 fn is_completion_trigger(
18741 &self,
18742 buffer: &Entity<Buffer>,
18743 position: language::Anchor,
18744 text: &str,
18745 trigger_in_words: bool,
18746 cx: &mut Context<Editor>,
18747 ) -> bool;
18748
18749 fn sort_completions(&self) -> bool {
18750 true
18751 }
18752
18753 fn filter_completions(&self) -> bool {
18754 true
18755 }
18756}
18757
18758pub trait CodeActionProvider {
18759 fn id(&self) -> Arc<str>;
18760
18761 fn code_actions(
18762 &self,
18763 buffer: &Entity<Buffer>,
18764 range: Range<text::Anchor>,
18765 window: &mut Window,
18766 cx: &mut App,
18767 ) -> Task<Result<Vec<CodeAction>>>;
18768
18769 fn apply_code_action(
18770 &self,
18771 buffer_handle: Entity<Buffer>,
18772 action: CodeAction,
18773 excerpt_id: ExcerptId,
18774 push_to_history: bool,
18775 window: &mut Window,
18776 cx: &mut App,
18777 ) -> Task<Result<ProjectTransaction>>;
18778}
18779
18780impl CodeActionProvider for Entity<Project> {
18781 fn id(&self) -> Arc<str> {
18782 "project".into()
18783 }
18784
18785 fn code_actions(
18786 &self,
18787 buffer: &Entity<Buffer>,
18788 range: Range<text::Anchor>,
18789 _window: &mut Window,
18790 cx: &mut App,
18791 ) -> Task<Result<Vec<CodeAction>>> {
18792 self.update(cx, |project, cx| {
18793 let code_lens = project.code_lens(buffer, range.clone(), cx);
18794 let code_actions = project.code_actions(buffer, range, None, cx);
18795 cx.background_spawn(async move {
18796 let (code_lens, code_actions) = join(code_lens, code_actions).await;
18797 Ok(code_lens
18798 .context("code lens fetch")?
18799 .into_iter()
18800 .chain(code_actions.context("code action fetch")?)
18801 .collect())
18802 })
18803 })
18804 }
18805
18806 fn apply_code_action(
18807 &self,
18808 buffer_handle: Entity<Buffer>,
18809 action: CodeAction,
18810 _excerpt_id: ExcerptId,
18811 push_to_history: bool,
18812 _window: &mut Window,
18813 cx: &mut App,
18814 ) -> Task<Result<ProjectTransaction>> {
18815 self.update(cx, |project, cx| {
18816 project.apply_code_action(buffer_handle, action, push_to_history, cx)
18817 })
18818 }
18819}
18820
18821fn snippet_completions(
18822 project: &Project,
18823 buffer: &Entity<Buffer>,
18824 buffer_position: text::Anchor,
18825 cx: &mut App,
18826) -> Task<Result<Vec<Completion>>> {
18827 let languages = buffer.read(cx).languages_at(buffer_position);
18828 let snippet_store = project.snippets().read(cx);
18829
18830 let scopes: Vec<_> = languages
18831 .iter()
18832 .filter_map(|language| {
18833 let language_name = language.lsp_id();
18834 let snippets = snippet_store.snippets_for(Some(language_name), cx);
18835
18836 if snippets.is_empty() {
18837 None
18838 } else {
18839 Some((language.default_scope(), snippets))
18840 }
18841 })
18842 .collect();
18843
18844 if scopes.is_empty() {
18845 return Task::ready(Ok(vec![]));
18846 }
18847
18848 let snapshot = buffer.read(cx).text_snapshot();
18849 let chars: String = snapshot
18850 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
18851 .collect();
18852 let executor = cx.background_executor().clone();
18853
18854 cx.background_spawn(async move {
18855 let mut all_results: Vec<Completion> = Vec::new();
18856 for (scope, snippets) in scopes.into_iter() {
18857 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
18858 let mut last_word = chars
18859 .chars()
18860 .take_while(|c| classifier.is_word(*c))
18861 .collect::<String>();
18862 last_word = last_word.chars().rev().collect();
18863
18864 if last_word.is_empty() {
18865 return Ok(vec![]);
18866 }
18867
18868 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
18869 let to_lsp = |point: &text::Anchor| {
18870 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
18871 point_to_lsp(end)
18872 };
18873 let lsp_end = to_lsp(&buffer_position);
18874
18875 let candidates = snippets
18876 .iter()
18877 .enumerate()
18878 .flat_map(|(ix, snippet)| {
18879 snippet
18880 .prefix
18881 .iter()
18882 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
18883 })
18884 .collect::<Vec<StringMatchCandidate>>();
18885
18886 let mut matches = fuzzy::match_strings(
18887 &candidates,
18888 &last_word,
18889 last_word.chars().any(|c| c.is_uppercase()),
18890 100,
18891 &Default::default(),
18892 executor.clone(),
18893 )
18894 .await;
18895
18896 // Remove all candidates where the query's start does not match the start of any word in the candidate
18897 if let Some(query_start) = last_word.chars().next() {
18898 matches.retain(|string_match| {
18899 split_words(&string_match.string).any(|word| {
18900 // Check that the first codepoint of the word as lowercase matches the first
18901 // codepoint of the query as lowercase
18902 word.chars()
18903 .flat_map(|codepoint| codepoint.to_lowercase())
18904 .zip(query_start.to_lowercase())
18905 .all(|(word_cp, query_cp)| word_cp == query_cp)
18906 })
18907 });
18908 }
18909
18910 let matched_strings = matches
18911 .into_iter()
18912 .map(|m| m.string)
18913 .collect::<HashSet<_>>();
18914
18915 let mut result: Vec<Completion> = snippets
18916 .iter()
18917 .filter_map(|snippet| {
18918 let matching_prefix = snippet
18919 .prefix
18920 .iter()
18921 .find(|prefix| matched_strings.contains(*prefix))?;
18922 let start = as_offset - last_word.len();
18923 let start = snapshot.anchor_before(start);
18924 let range = start..buffer_position;
18925 let lsp_start = to_lsp(&start);
18926 let lsp_range = lsp::Range {
18927 start: lsp_start,
18928 end: lsp_end,
18929 };
18930 Some(Completion {
18931 replace_range: range,
18932 new_text: snippet.body.clone(),
18933 source: CompletionSource::Lsp {
18934 insert_range: None,
18935 server_id: LanguageServerId(usize::MAX),
18936 resolved: true,
18937 lsp_completion: Box::new(lsp::CompletionItem {
18938 label: snippet.prefix.first().unwrap().clone(),
18939 kind: Some(CompletionItemKind::SNIPPET),
18940 label_details: snippet.description.as_ref().map(|description| {
18941 lsp::CompletionItemLabelDetails {
18942 detail: Some(description.clone()),
18943 description: None,
18944 }
18945 }),
18946 insert_text_format: Some(InsertTextFormat::SNIPPET),
18947 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18948 lsp::InsertReplaceEdit {
18949 new_text: snippet.body.clone(),
18950 insert: lsp_range,
18951 replace: lsp_range,
18952 },
18953 )),
18954 filter_text: Some(snippet.body.clone()),
18955 sort_text: Some(char::MAX.to_string()),
18956 ..lsp::CompletionItem::default()
18957 }),
18958 lsp_defaults: None,
18959 },
18960 label: CodeLabel {
18961 text: matching_prefix.clone(),
18962 runs: Vec::new(),
18963 filter_range: 0..matching_prefix.len(),
18964 },
18965 icon_path: None,
18966 documentation: snippet.description.clone().map(|description| {
18967 CompletionDocumentation::SingleLine(description.into())
18968 }),
18969 insert_text_mode: None,
18970 confirm: None,
18971 })
18972 })
18973 .collect();
18974
18975 all_results.append(&mut result);
18976 }
18977
18978 Ok(all_results)
18979 })
18980}
18981
18982impl CompletionProvider for Entity<Project> {
18983 fn completions(
18984 &self,
18985 _excerpt_id: ExcerptId,
18986 buffer: &Entity<Buffer>,
18987 buffer_position: text::Anchor,
18988 options: CompletionContext,
18989 _window: &mut Window,
18990 cx: &mut Context<Editor>,
18991 ) -> Task<Result<Option<Vec<Completion>>>> {
18992 self.update(cx, |project, cx| {
18993 let snippets = snippet_completions(project, buffer, buffer_position, cx);
18994 let project_completions = project.completions(buffer, buffer_position, options, cx);
18995 cx.background_spawn(async move {
18996 let snippets_completions = snippets.await?;
18997 match project_completions.await? {
18998 Some(mut completions) => {
18999 completions.extend(snippets_completions);
19000 Ok(Some(completions))
19001 }
19002 None => {
19003 if snippets_completions.is_empty() {
19004 Ok(None)
19005 } else {
19006 Ok(Some(snippets_completions))
19007 }
19008 }
19009 }
19010 })
19011 })
19012 }
19013
19014 fn resolve_completions(
19015 &self,
19016 buffer: Entity<Buffer>,
19017 completion_indices: Vec<usize>,
19018 completions: Rc<RefCell<Box<[Completion]>>>,
19019 cx: &mut Context<Editor>,
19020 ) -> Task<Result<bool>> {
19021 self.update(cx, |project, cx| {
19022 project.lsp_store().update(cx, |lsp_store, cx| {
19023 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19024 })
19025 })
19026 }
19027
19028 fn apply_additional_edits_for_completion(
19029 &self,
19030 buffer: Entity<Buffer>,
19031 completions: Rc<RefCell<Box<[Completion]>>>,
19032 completion_index: usize,
19033 push_to_history: bool,
19034 cx: &mut Context<Editor>,
19035 ) -> Task<Result<Option<language::Transaction>>> {
19036 self.update(cx, |project, cx| {
19037 project.lsp_store().update(cx, |lsp_store, cx| {
19038 lsp_store.apply_additional_edits_for_completion(
19039 buffer,
19040 completions,
19041 completion_index,
19042 push_to_history,
19043 cx,
19044 )
19045 })
19046 })
19047 }
19048
19049 fn is_completion_trigger(
19050 &self,
19051 buffer: &Entity<Buffer>,
19052 position: language::Anchor,
19053 text: &str,
19054 trigger_in_words: bool,
19055 cx: &mut Context<Editor>,
19056 ) -> bool {
19057 let mut chars = text.chars();
19058 let char = if let Some(char) = chars.next() {
19059 char
19060 } else {
19061 return false;
19062 };
19063 if chars.next().is_some() {
19064 return false;
19065 }
19066
19067 let buffer = buffer.read(cx);
19068 let snapshot = buffer.snapshot();
19069 if !snapshot.settings_at(position, cx).show_completions_on_input {
19070 return false;
19071 }
19072 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19073 if trigger_in_words && classifier.is_word(char) {
19074 return true;
19075 }
19076
19077 buffer.completion_triggers().contains(text)
19078 }
19079}
19080
19081impl SemanticsProvider for Entity<Project> {
19082 fn hover(
19083 &self,
19084 buffer: &Entity<Buffer>,
19085 position: text::Anchor,
19086 cx: &mut App,
19087 ) -> Option<Task<Vec<project::Hover>>> {
19088 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19089 }
19090
19091 fn document_highlights(
19092 &self,
19093 buffer: &Entity<Buffer>,
19094 position: text::Anchor,
19095 cx: &mut App,
19096 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19097 Some(self.update(cx, |project, cx| {
19098 project.document_highlights(buffer, position, cx)
19099 }))
19100 }
19101
19102 fn definitions(
19103 &self,
19104 buffer: &Entity<Buffer>,
19105 position: text::Anchor,
19106 kind: GotoDefinitionKind,
19107 cx: &mut App,
19108 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19109 Some(self.update(cx, |project, cx| match kind {
19110 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19111 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19112 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19113 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19114 }))
19115 }
19116
19117 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19118 // TODO: make this work for remote projects
19119 self.update(cx, |this, cx| {
19120 buffer.update(cx, |buffer, cx| {
19121 this.any_language_server_supports_inlay_hints(buffer, cx)
19122 })
19123 })
19124 }
19125
19126 fn inlay_hints(
19127 &self,
19128 buffer_handle: Entity<Buffer>,
19129 range: Range<text::Anchor>,
19130 cx: &mut App,
19131 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19132 Some(self.update(cx, |project, cx| {
19133 project.inlay_hints(buffer_handle, range, cx)
19134 }))
19135 }
19136
19137 fn resolve_inlay_hint(
19138 &self,
19139 hint: InlayHint,
19140 buffer_handle: Entity<Buffer>,
19141 server_id: LanguageServerId,
19142 cx: &mut App,
19143 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19144 Some(self.update(cx, |project, cx| {
19145 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19146 }))
19147 }
19148
19149 fn range_for_rename(
19150 &self,
19151 buffer: &Entity<Buffer>,
19152 position: text::Anchor,
19153 cx: &mut App,
19154 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19155 Some(self.update(cx, |project, cx| {
19156 let buffer = buffer.clone();
19157 let task = project.prepare_rename(buffer.clone(), position, cx);
19158 cx.spawn(async move |_, cx| {
19159 Ok(match task.await? {
19160 PrepareRenameResponse::Success(range) => Some(range),
19161 PrepareRenameResponse::InvalidPosition => None,
19162 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19163 // Fallback on using TreeSitter info to determine identifier range
19164 buffer.update(cx, |buffer, _| {
19165 let snapshot = buffer.snapshot();
19166 let (range, kind) = snapshot.surrounding_word(position);
19167 if kind != Some(CharKind::Word) {
19168 return None;
19169 }
19170 Some(
19171 snapshot.anchor_before(range.start)
19172 ..snapshot.anchor_after(range.end),
19173 )
19174 })?
19175 }
19176 })
19177 })
19178 }))
19179 }
19180
19181 fn perform_rename(
19182 &self,
19183 buffer: &Entity<Buffer>,
19184 position: text::Anchor,
19185 new_name: String,
19186 cx: &mut App,
19187 ) -> Option<Task<Result<ProjectTransaction>>> {
19188 Some(self.update(cx, |project, cx| {
19189 project.perform_rename(buffer.clone(), position, new_name, cx)
19190 }))
19191 }
19192}
19193
19194fn inlay_hint_settings(
19195 location: Anchor,
19196 snapshot: &MultiBufferSnapshot,
19197 cx: &mut Context<Editor>,
19198) -> InlayHintSettings {
19199 let file = snapshot.file_at(location);
19200 let language = snapshot.language_at(location).map(|l| l.name());
19201 language_settings(language, file, cx).inlay_hints
19202}
19203
19204fn consume_contiguous_rows(
19205 contiguous_row_selections: &mut Vec<Selection<Point>>,
19206 selection: &Selection<Point>,
19207 display_map: &DisplaySnapshot,
19208 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19209) -> (MultiBufferRow, MultiBufferRow) {
19210 contiguous_row_selections.push(selection.clone());
19211 let start_row = MultiBufferRow(selection.start.row);
19212 let mut end_row = ending_row(selection, display_map);
19213
19214 while let Some(next_selection) = selections.peek() {
19215 if next_selection.start.row <= end_row.0 {
19216 end_row = ending_row(next_selection, display_map);
19217 contiguous_row_selections.push(selections.next().unwrap().clone());
19218 } else {
19219 break;
19220 }
19221 }
19222 (start_row, end_row)
19223}
19224
19225fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19226 if next_selection.end.column > 0 || next_selection.is_empty() {
19227 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19228 } else {
19229 MultiBufferRow(next_selection.end.row)
19230 }
19231}
19232
19233impl EditorSnapshot {
19234 pub fn remote_selections_in_range<'a>(
19235 &'a self,
19236 range: &'a Range<Anchor>,
19237 collaboration_hub: &dyn CollaborationHub,
19238 cx: &'a App,
19239 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19240 let participant_names = collaboration_hub.user_names(cx);
19241 let participant_indices = collaboration_hub.user_participant_indices(cx);
19242 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19243 let collaborators_by_replica_id = collaborators_by_peer_id
19244 .iter()
19245 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19246 .collect::<HashMap<_, _>>();
19247 self.buffer_snapshot
19248 .selections_in_range(range, false)
19249 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19250 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19251 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19252 let user_name = participant_names.get(&collaborator.user_id).cloned();
19253 Some(RemoteSelection {
19254 replica_id,
19255 selection,
19256 cursor_shape,
19257 line_mode,
19258 participant_index,
19259 peer_id: collaborator.peer_id,
19260 user_name,
19261 })
19262 })
19263 }
19264
19265 pub fn hunks_for_ranges(
19266 &self,
19267 ranges: impl IntoIterator<Item = Range<Point>>,
19268 ) -> Vec<MultiBufferDiffHunk> {
19269 let mut hunks = Vec::new();
19270 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19271 HashMap::default();
19272 for query_range in ranges {
19273 let query_rows =
19274 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19275 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19276 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19277 ) {
19278 // Include deleted hunks that are adjacent to the query range, because
19279 // otherwise they would be missed.
19280 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19281 if hunk.status().is_deleted() {
19282 intersects_range |= hunk.row_range.start == query_rows.end;
19283 intersects_range |= hunk.row_range.end == query_rows.start;
19284 }
19285 if intersects_range {
19286 if !processed_buffer_rows
19287 .entry(hunk.buffer_id)
19288 .or_default()
19289 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19290 {
19291 continue;
19292 }
19293 hunks.push(hunk);
19294 }
19295 }
19296 }
19297
19298 hunks
19299 }
19300
19301 fn display_diff_hunks_for_rows<'a>(
19302 &'a self,
19303 display_rows: Range<DisplayRow>,
19304 folded_buffers: &'a HashSet<BufferId>,
19305 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19306 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19307 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19308
19309 self.buffer_snapshot
19310 .diff_hunks_in_range(buffer_start..buffer_end)
19311 .filter_map(|hunk| {
19312 if folded_buffers.contains(&hunk.buffer_id) {
19313 return None;
19314 }
19315
19316 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19317 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19318
19319 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19320 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19321
19322 let display_hunk = if hunk_display_start.column() != 0 {
19323 DisplayDiffHunk::Folded {
19324 display_row: hunk_display_start.row(),
19325 }
19326 } else {
19327 let mut end_row = hunk_display_end.row();
19328 if hunk_display_end.column() > 0 {
19329 end_row.0 += 1;
19330 }
19331 let is_created_file = hunk.is_created_file();
19332 DisplayDiffHunk::Unfolded {
19333 status: hunk.status(),
19334 diff_base_byte_range: hunk.diff_base_byte_range,
19335 display_row_range: hunk_display_start.row()..end_row,
19336 multi_buffer_range: Anchor::range_in_buffer(
19337 hunk.excerpt_id,
19338 hunk.buffer_id,
19339 hunk.buffer_range,
19340 ),
19341 is_created_file,
19342 }
19343 };
19344
19345 Some(display_hunk)
19346 })
19347 }
19348
19349 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19350 self.display_snapshot.buffer_snapshot.language_at(position)
19351 }
19352
19353 pub fn is_focused(&self) -> bool {
19354 self.is_focused
19355 }
19356
19357 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19358 self.placeholder_text.as_ref()
19359 }
19360
19361 pub fn scroll_position(&self) -> gpui::Point<f32> {
19362 self.scroll_anchor.scroll_position(&self.display_snapshot)
19363 }
19364
19365 fn gutter_dimensions(
19366 &self,
19367 font_id: FontId,
19368 font_size: Pixels,
19369 max_line_number_width: Pixels,
19370 cx: &App,
19371 ) -> Option<GutterDimensions> {
19372 if !self.show_gutter {
19373 return None;
19374 }
19375
19376 let descent = cx.text_system().descent(font_id, font_size);
19377 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
19378 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
19379
19380 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
19381 matches!(
19382 ProjectSettings::get_global(cx).git.git_gutter,
19383 Some(GitGutterSetting::TrackedFiles)
19384 )
19385 });
19386 let gutter_settings = EditorSettings::get_global(cx).gutter;
19387 let show_line_numbers = self
19388 .show_line_numbers
19389 .unwrap_or(gutter_settings.line_numbers);
19390 let line_gutter_width = if show_line_numbers {
19391 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
19392 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
19393 max_line_number_width.max(min_width_for_number_on_gutter)
19394 } else {
19395 0.0.into()
19396 };
19397
19398 let show_code_actions = self
19399 .show_code_actions
19400 .unwrap_or(gutter_settings.code_actions);
19401
19402 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
19403 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
19404
19405 let git_blame_entries_width =
19406 self.git_blame_gutter_max_author_length
19407 .map(|max_author_length| {
19408 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19409 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
19410
19411 /// The number of characters to dedicate to gaps and margins.
19412 const SPACING_WIDTH: usize = 4;
19413
19414 let max_char_count = max_author_length.min(renderer.max_author_length())
19415 + ::git::SHORT_SHA_LENGTH
19416 + MAX_RELATIVE_TIMESTAMP.len()
19417 + SPACING_WIDTH;
19418
19419 em_advance * max_char_count
19420 });
19421
19422 let is_singleton = self.buffer_snapshot.is_singleton();
19423
19424 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
19425 left_padding += if !is_singleton {
19426 em_width * 4.0
19427 } else if show_code_actions || show_runnables || show_breakpoints {
19428 em_width * 3.0
19429 } else if show_git_gutter && show_line_numbers {
19430 em_width * 2.0
19431 } else if show_git_gutter || show_line_numbers {
19432 em_width
19433 } else {
19434 px(0.)
19435 };
19436
19437 let shows_folds = is_singleton && gutter_settings.folds;
19438
19439 let right_padding = if shows_folds && show_line_numbers {
19440 em_width * 4.0
19441 } else if shows_folds || (!is_singleton && show_line_numbers) {
19442 em_width * 3.0
19443 } else if show_line_numbers {
19444 em_width
19445 } else {
19446 px(0.)
19447 };
19448
19449 Some(GutterDimensions {
19450 left_padding,
19451 right_padding,
19452 width: line_gutter_width + left_padding + right_padding,
19453 margin: -descent,
19454 git_blame_entries_width,
19455 })
19456 }
19457
19458 pub fn render_crease_toggle(
19459 &self,
19460 buffer_row: MultiBufferRow,
19461 row_contains_cursor: bool,
19462 editor: Entity<Editor>,
19463 window: &mut Window,
19464 cx: &mut App,
19465 ) -> Option<AnyElement> {
19466 let folded = self.is_line_folded(buffer_row);
19467 let mut is_foldable = false;
19468
19469 if let Some(crease) = self
19470 .crease_snapshot
19471 .query_row(buffer_row, &self.buffer_snapshot)
19472 {
19473 is_foldable = true;
19474 match crease {
19475 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
19476 if let Some(render_toggle) = render_toggle {
19477 let toggle_callback =
19478 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
19479 if folded {
19480 editor.update(cx, |editor, cx| {
19481 editor.fold_at(buffer_row, window, cx)
19482 });
19483 } else {
19484 editor.update(cx, |editor, cx| {
19485 editor.unfold_at(buffer_row, window, cx)
19486 });
19487 }
19488 });
19489 return Some((render_toggle)(
19490 buffer_row,
19491 folded,
19492 toggle_callback,
19493 window,
19494 cx,
19495 ));
19496 }
19497 }
19498 }
19499 }
19500
19501 is_foldable |= self.starts_indent(buffer_row);
19502
19503 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
19504 Some(
19505 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
19506 .toggle_state(folded)
19507 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
19508 if folded {
19509 this.unfold_at(buffer_row, window, cx);
19510 } else {
19511 this.fold_at(buffer_row, window, cx);
19512 }
19513 }))
19514 .into_any_element(),
19515 )
19516 } else {
19517 None
19518 }
19519 }
19520
19521 pub fn render_crease_trailer(
19522 &self,
19523 buffer_row: MultiBufferRow,
19524 window: &mut Window,
19525 cx: &mut App,
19526 ) -> Option<AnyElement> {
19527 let folded = self.is_line_folded(buffer_row);
19528 if let Crease::Inline { render_trailer, .. } = self
19529 .crease_snapshot
19530 .query_row(buffer_row, &self.buffer_snapshot)?
19531 {
19532 let render_trailer = render_trailer.as_ref()?;
19533 Some(render_trailer(buffer_row, folded, window, cx))
19534 } else {
19535 None
19536 }
19537 }
19538}
19539
19540impl Deref for EditorSnapshot {
19541 type Target = DisplaySnapshot;
19542
19543 fn deref(&self) -> &Self::Target {
19544 &self.display_snapshot
19545 }
19546}
19547
19548#[derive(Clone, Debug, PartialEq, Eq)]
19549pub enum EditorEvent {
19550 InputIgnored {
19551 text: Arc<str>,
19552 },
19553 InputHandled {
19554 utf16_range_to_replace: Option<Range<isize>>,
19555 text: Arc<str>,
19556 },
19557 ExcerptsAdded {
19558 buffer: Entity<Buffer>,
19559 predecessor: ExcerptId,
19560 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
19561 },
19562 ExcerptsRemoved {
19563 ids: Vec<ExcerptId>,
19564 },
19565 BufferFoldToggled {
19566 ids: Vec<ExcerptId>,
19567 folded: bool,
19568 },
19569 ExcerptsEdited {
19570 ids: Vec<ExcerptId>,
19571 },
19572 ExcerptsExpanded {
19573 ids: Vec<ExcerptId>,
19574 },
19575 BufferEdited,
19576 Edited {
19577 transaction_id: clock::Lamport,
19578 },
19579 Reparsed(BufferId),
19580 Focused,
19581 FocusedIn,
19582 Blurred,
19583 DirtyChanged,
19584 Saved,
19585 TitleChanged,
19586 DiffBaseChanged,
19587 SelectionsChanged {
19588 local: bool,
19589 },
19590 ScrollPositionChanged {
19591 local: bool,
19592 autoscroll: bool,
19593 },
19594 Closed,
19595 TransactionUndone {
19596 transaction_id: clock::Lamport,
19597 },
19598 TransactionBegun {
19599 transaction_id: clock::Lamport,
19600 },
19601 Reloaded,
19602 CursorShapeChanged,
19603 PushedToNavHistory {
19604 anchor: Anchor,
19605 is_deactivate: bool,
19606 },
19607}
19608
19609impl EventEmitter<EditorEvent> for Editor {}
19610
19611impl Focusable for Editor {
19612 fn focus_handle(&self, _cx: &App) -> FocusHandle {
19613 self.focus_handle.clone()
19614 }
19615}
19616
19617impl Render for Editor {
19618 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
19619 let settings = ThemeSettings::get_global(cx);
19620
19621 let mut text_style = match self.mode {
19622 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
19623 color: cx.theme().colors().editor_foreground,
19624 font_family: settings.ui_font.family.clone(),
19625 font_features: settings.ui_font.features.clone(),
19626 font_fallbacks: settings.ui_font.fallbacks.clone(),
19627 font_size: rems(0.875).into(),
19628 font_weight: settings.ui_font.weight,
19629 line_height: relative(settings.buffer_line_height.value()),
19630 ..Default::default()
19631 },
19632 EditorMode::Full { .. } => TextStyle {
19633 color: cx.theme().colors().editor_foreground,
19634 font_family: settings.buffer_font.family.clone(),
19635 font_features: settings.buffer_font.features.clone(),
19636 font_fallbacks: settings.buffer_font.fallbacks.clone(),
19637 font_size: settings.buffer_font_size(cx).into(),
19638 font_weight: settings.buffer_font.weight,
19639 line_height: relative(settings.buffer_line_height.value()),
19640 ..Default::default()
19641 },
19642 };
19643 if let Some(text_style_refinement) = &self.text_style_refinement {
19644 text_style.refine(text_style_refinement)
19645 }
19646
19647 let background = match self.mode {
19648 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
19649 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
19650 EditorMode::Full { .. } => cx.theme().colors().editor_background,
19651 };
19652
19653 EditorElement::new(
19654 &cx.entity(),
19655 EditorStyle {
19656 background,
19657 local_player: cx.theme().players().local(),
19658 text: text_style,
19659 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
19660 syntax: cx.theme().syntax().clone(),
19661 status: cx.theme().status().clone(),
19662 inlay_hints_style: make_inlay_hints_style(cx),
19663 inline_completion_styles: make_suggestion_styles(cx),
19664 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
19665 },
19666 )
19667 }
19668}
19669
19670impl EntityInputHandler for Editor {
19671 fn text_for_range(
19672 &mut self,
19673 range_utf16: Range<usize>,
19674 adjusted_range: &mut Option<Range<usize>>,
19675 _: &mut Window,
19676 cx: &mut Context<Self>,
19677 ) -> Option<String> {
19678 let snapshot = self.buffer.read(cx).read(cx);
19679 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
19680 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
19681 if (start.0..end.0) != range_utf16 {
19682 adjusted_range.replace(start.0..end.0);
19683 }
19684 Some(snapshot.text_for_range(start..end).collect())
19685 }
19686
19687 fn selected_text_range(
19688 &mut self,
19689 ignore_disabled_input: bool,
19690 _: &mut Window,
19691 cx: &mut Context<Self>,
19692 ) -> Option<UTF16Selection> {
19693 // Prevent the IME menu from appearing when holding down an alphabetic key
19694 // while input is disabled.
19695 if !ignore_disabled_input && !self.input_enabled {
19696 return None;
19697 }
19698
19699 let selection = self.selections.newest::<OffsetUtf16>(cx);
19700 let range = selection.range();
19701
19702 Some(UTF16Selection {
19703 range: range.start.0..range.end.0,
19704 reversed: selection.reversed,
19705 })
19706 }
19707
19708 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
19709 let snapshot = self.buffer.read(cx).read(cx);
19710 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
19711 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
19712 }
19713
19714 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19715 self.clear_highlights::<InputComposition>(cx);
19716 self.ime_transaction.take();
19717 }
19718
19719 fn replace_text_in_range(
19720 &mut self,
19721 range_utf16: Option<Range<usize>>,
19722 text: &str,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 ) {
19726 if !self.input_enabled {
19727 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19728 return;
19729 }
19730
19731 self.transact(window, cx, |this, window, cx| {
19732 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
19733 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19734 Some(this.selection_replacement_ranges(range_utf16, cx))
19735 } else {
19736 this.marked_text_ranges(cx)
19737 };
19738
19739 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
19740 let newest_selection_id = this.selections.newest_anchor().id;
19741 this.selections
19742 .all::<OffsetUtf16>(cx)
19743 .iter()
19744 .zip(ranges_to_replace.iter())
19745 .find_map(|(selection, range)| {
19746 if selection.id == newest_selection_id {
19747 Some(
19748 (range.start.0 as isize - selection.head().0 as isize)
19749 ..(range.end.0 as isize - selection.head().0 as isize),
19750 )
19751 } else {
19752 None
19753 }
19754 })
19755 });
19756
19757 cx.emit(EditorEvent::InputHandled {
19758 utf16_range_to_replace: range_to_replace,
19759 text: text.into(),
19760 });
19761
19762 if let Some(new_selected_ranges) = new_selected_ranges {
19763 this.change_selections(None, window, cx, |selections| {
19764 selections.select_ranges(new_selected_ranges)
19765 });
19766 this.backspace(&Default::default(), window, cx);
19767 }
19768
19769 this.handle_input(text, window, cx);
19770 });
19771
19772 if let Some(transaction) = self.ime_transaction {
19773 self.buffer.update(cx, |buffer, cx| {
19774 buffer.group_until_transaction(transaction, cx);
19775 });
19776 }
19777
19778 self.unmark_text(window, cx);
19779 }
19780
19781 fn replace_and_mark_text_in_range(
19782 &mut self,
19783 range_utf16: Option<Range<usize>>,
19784 text: &str,
19785 new_selected_range_utf16: Option<Range<usize>>,
19786 window: &mut Window,
19787 cx: &mut Context<Self>,
19788 ) {
19789 if !self.input_enabled {
19790 return;
19791 }
19792
19793 let transaction = self.transact(window, cx, |this, window, cx| {
19794 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
19795 let snapshot = this.buffer.read(cx).read(cx);
19796 if let Some(relative_range_utf16) = range_utf16.as_ref() {
19797 for marked_range in &mut marked_ranges {
19798 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
19799 marked_range.start.0 += relative_range_utf16.start;
19800 marked_range.start =
19801 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
19802 marked_range.end =
19803 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
19804 }
19805 }
19806 Some(marked_ranges)
19807 } else if let Some(range_utf16) = range_utf16 {
19808 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
19809 Some(this.selection_replacement_ranges(range_utf16, cx))
19810 } else {
19811 None
19812 };
19813
19814 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
19815 let newest_selection_id = this.selections.newest_anchor().id;
19816 this.selections
19817 .all::<OffsetUtf16>(cx)
19818 .iter()
19819 .zip(ranges_to_replace.iter())
19820 .find_map(|(selection, range)| {
19821 if selection.id == newest_selection_id {
19822 Some(
19823 (range.start.0 as isize - selection.head().0 as isize)
19824 ..(range.end.0 as isize - selection.head().0 as isize),
19825 )
19826 } else {
19827 None
19828 }
19829 })
19830 });
19831
19832 cx.emit(EditorEvent::InputHandled {
19833 utf16_range_to_replace: range_to_replace,
19834 text: text.into(),
19835 });
19836
19837 if let Some(ranges) = ranges_to_replace {
19838 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
19839 }
19840
19841 let marked_ranges = {
19842 let snapshot = this.buffer.read(cx).read(cx);
19843 this.selections
19844 .disjoint_anchors()
19845 .iter()
19846 .map(|selection| {
19847 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
19848 })
19849 .collect::<Vec<_>>()
19850 };
19851
19852 if text.is_empty() {
19853 this.unmark_text(window, cx);
19854 } else {
19855 this.highlight_text::<InputComposition>(
19856 marked_ranges.clone(),
19857 HighlightStyle {
19858 underline: Some(UnderlineStyle {
19859 thickness: px(1.),
19860 color: None,
19861 wavy: false,
19862 }),
19863 ..Default::default()
19864 },
19865 cx,
19866 );
19867 }
19868
19869 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
19870 let use_autoclose = this.use_autoclose;
19871 let use_auto_surround = this.use_auto_surround;
19872 this.set_use_autoclose(false);
19873 this.set_use_auto_surround(false);
19874 this.handle_input(text, window, cx);
19875 this.set_use_autoclose(use_autoclose);
19876 this.set_use_auto_surround(use_auto_surround);
19877
19878 if let Some(new_selected_range) = new_selected_range_utf16 {
19879 let snapshot = this.buffer.read(cx).read(cx);
19880 let new_selected_ranges = marked_ranges
19881 .into_iter()
19882 .map(|marked_range| {
19883 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
19884 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
19885 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
19886 snapshot.clip_offset_utf16(new_start, Bias::Left)
19887 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
19888 })
19889 .collect::<Vec<_>>();
19890
19891 drop(snapshot);
19892 this.change_selections(None, window, cx, |selections| {
19893 selections.select_ranges(new_selected_ranges)
19894 });
19895 }
19896 });
19897
19898 self.ime_transaction = self.ime_transaction.or(transaction);
19899 if let Some(transaction) = self.ime_transaction {
19900 self.buffer.update(cx, |buffer, cx| {
19901 buffer.group_until_transaction(transaction, cx);
19902 });
19903 }
19904
19905 if self.text_highlights::<InputComposition>(cx).is_none() {
19906 self.ime_transaction.take();
19907 }
19908 }
19909
19910 fn bounds_for_range(
19911 &mut self,
19912 range_utf16: Range<usize>,
19913 element_bounds: gpui::Bounds<Pixels>,
19914 window: &mut Window,
19915 cx: &mut Context<Self>,
19916 ) -> Option<gpui::Bounds<Pixels>> {
19917 let text_layout_details = self.text_layout_details(window);
19918 let gpui::Size {
19919 width: em_width,
19920 height: line_height,
19921 } = self.character_size(window);
19922
19923 let snapshot = self.snapshot(window, cx);
19924 let scroll_position = snapshot.scroll_position();
19925 let scroll_left = scroll_position.x * em_width;
19926
19927 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
19928 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
19929 + self.gutter_dimensions.width
19930 + self.gutter_dimensions.margin;
19931 let y = line_height * (start.row().as_f32() - scroll_position.y);
19932
19933 Some(Bounds {
19934 origin: element_bounds.origin + point(x, y),
19935 size: size(em_width, line_height),
19936 })
19937 }
19938
19939 fn character_index_for_point(
19940 &mut self,
19941 point: gpui::Point<Pixels>,
19942 _window: &mut Window,
19943 _cx: &mut Context<Self>,
19944 ) -> Option<usize> {
19945 let position_map = self.last_position_map.as_ref()?;
19946 if !position_map.text_hitbox.contains(&point) {
19947 return None;
19948 }
19949 let display_point = position_map.point_for_position(point).previous_valid;
19950 let anchor = position_map
19951 .snapshot
19952 .display_point_to_anchor(display_point, Bias::Left);
19953 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
19954 Some(utf16_offset.0)
19955 }
19956}
19957
19958trait SelectionExt {
19959 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
19960 fn spanned_rows(
19961 &self,
19962 include_end_if_at_line_start: bool,
19963 map: &DisplaySnapshot,
19964 ) -> Range<MultiBufferRow>;
19965}
19966
19967impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
19968 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
19969 let start = self
19970 .start
19971 .to_point(&map.buffer_snapshot)
19972 .to_display_point(map);
19973 let end = self
19974 .end
19975 .to_point(&map.buffer_snapshot)
19976 .to_display_point(map);
19977 if self.reversed {
19978 end..start
19979 } else {
19980 start..end
19981 }
19982 }
19983
19984 fn spanned_rows(
19985 &self,
19986 include_end_if_at_line_start: bool,
19987 map: &DisplaySnapshot,
19988 ) -> Range<MultiBufferRow> {
19989 let start = self.start.to_point(&map.buffer_snapshot);
19990 let mut end = self.end.to_point(&map.buffer_snapshot);
19991 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
19992 end.row -= 1;
19993 }
19994
19995 let buffer_start = map.prev_line_boundary(start).0;
19996 let buffer_end = map.next_line_boundary(end).0;
19997 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
19998 }
19999}
20000
20001impl<T: InvalidationRegion> InvalidationStack<T> {
20002 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20003 where
20004 S: Clone + ToOffset,
20005 {
20006 while let Some(region) = self.last() {
20007 let all_selections_inside_invalidation_ranges =
20008 if selections.len() == region.ranges().len() {
20009 selections
20010 .iter()
20011 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20012 .all(|(selection, invalidation_range)| {
20013 let head = selection.head().to_offset(buffer);
20014 invalidation_range.start <= head && invalidation_range.end >= head
20015 })
20016 } else {
20017 false
20018 };
20019
20020 if all_selections_inside_invalidation_ranges {
20021 break;
20022 } else {
20023 self.pop();
20024 }
20025 }
20026 }
20027}
20028
20029impl<T> Default for InvalidationStack<T> {
20030 fn default() -> Self {
20031 Self(Default::default())
20032 }
20033}
20034
20035impl<T> Deref for InvalidationStack<T> {
20036 type Target = Vec<T>;
20037
20038 fn deref(&self) -> &Self::Target {
20039 &self.0
20040 }
20041}
20042
20043impl<T> DerefMut for InvalidationStack<T> {
20044 fn deref_mut(&mut self) -> &mut Self::Target {
20045 &mut self.0
20046 }
20047}
20048
20049impl InvalidationRegion for SnippetState {
20050 fn ranges(&self) -> &[Range<Anchor>] {
20051 &self.ranges[self.active_index]
20052 }
20053}
20054
20055fn inline_completion_edit_text(
20056 current_snapshot: &BufferSnapshot,
20057 edits: &[(Range<Anchor>, String)],
20058 edit_preview: &EditPreview,
20059 include_deletions: bool,
20060 cx: &App,
20061) -> HighlightedText {
20062 let edits = edits
20063 .iter()
20064 .map(|(anchor, text)| {
20065 (
20066 anchor.start.text_anchor..anchor.end.text_anchor,
20067 text.clone(),
20068 )
20069 })
20070 .collect::<Vec<_>>();
20071
20072 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20073}
20074
20075pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20076 match severity {
20077 DiagnosticSeverity::ERROR => colors.error,
20078 DiagnosticSeverity::WARNING => colors.warning,
20079 DiagnosticSeverity::INFORMATION => colors.info,
20080 DiagnosticSeverity::HINT => colors.info,
20081 _ => colors.ignored,
20082 }
20083}
20084
20085pub fn styled_runs_for_code_label<'a>(
20086 label: &'a CodeLabel,
20087 syntax_theme: &'a theme::SyntaxTheme,
20088) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20089 let fade_out = HighlightStyle {
20090 fade_out: Some(0.35),
20091 ..Default::default()
20092 };
20093
20094 let mut prev_end = label.filter_range.end;
20095 label
20096 .runs
20097 .iter()
20098 .enumerate()
20099 .flat_map(move |(ix, (range, highlight_id))| {
20100 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20101 style
20102 } else {
20103 return Default::default();
20104 };
20105 let mut muted_style = style;
20106 muted_style.highlight(fade_out);
20107
20108 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20109 if range.start >= label.filter_range.end {
20110 if range.start > prev_end {
20111 runs.push((prev_end..range.start, fade_out));
20112 }
20113 runs.push((range.clone(), muted_style));
20114 } else if range.end <= label.filter_range.end {
20115 runs.push((range.clone(), style));
20116 } else {
20117 runs.push((range.start..label.filter_range.end, style));
20118 runs.push((label.filter_range.end..range.end, muted_style));
20119 }
20120 prev_end = cmp::max(prev_end, range.end);
20121
20122 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20123 runs.push((prev_end..label.text.len(), fade_out));
20124 }
20125
20126 runs
20127 })
20128}
20129
20130pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20131 let mut prev_index = 0;
20132 let mut prev_codepoint: Option<char> = None;
20133 text.char_indices()
20134 .chain([(text.len(), '\0')])
20135 .filter_map(move |(index, codepoint)| {
20136 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20137 let is_boundary = index == text.len()
20138 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20139 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20140 if is_boundary {
20141 let chunk = &text[prev_index..index];
20142 prev_index = index;
20143 Some(chunk)
20144 } else {
20145 None
20146 }
20147 })
20148}
20149
20150pub trait RangeToAnchorExt: Sized {
20151 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20152
20153 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20154 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20155 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20156 }
20157}
20158
20159impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20160 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20161 let start_offset = self.start.to_offset(snapshot);
20162 let end_offset = self.end.to_offset(snapshot);
20163 if start_offset == end_offset {
20164 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20165 } else {
20166 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20167 }
20168 }
20169}
20170
20171pub trait RowExt {
20172 fn as_f32(&self) -> f32;
20173
20174 fn next_row(&self) -> Self;
20175
20176 fn previous_row(&self) -> Self;
20177
20178 fn minus(&self, other: Self) -> u32;
20179}
20180
20181impl RowExt for DisplayRow {
20182 fn as_f32(&self) -> f32 {
20183 self.0 as f32
20184 }
20185
20186 fn next_row(&self) -> Self {
20187 Self(self.0 + 1)
20188 }
20189
20190 fn previous_row(&self) -> Self {
20191 Self(self.0.saturating_sub(1))
20192 }
20193
20194 fn minus(&self, other: Self) -> u32 {
20195 self.0 - other.0
20196 }
20197}
20198
20199impl RowExt for MultiBufferRow {
20200 fn as_f32(&self) -> f32 {
20201 self.0 as f32
20202 }
20203
20204 fn next_row(&self) -> Self {
20205 Self(self.0 + 1)
20206 }
20207
20208 fn previous_row(&self) -> Self {
20209 Self(self.0.saturating_sub(1))
20210 }
20211
20212 fn minus(&self, other: Self) -> u32 {
20213 self.0 - other.0
20214 }
20215}
20216
20217trait RowRangeExt {
20218 type Row;
20219
20220 fn len(&self) -> usize;
20221
20222 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20223}
20224
20225impl RowRangeExt for Range<MultiBufferRow> {
20226 type Row = MultiBufferRow;
20227
20228 fn len(&self) -> usize {
20229 (self.end.0 - self.start.0) as usize
20230 }
20231
20232 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20233 (self.start.0..self.end.0).map(MultiBufferRow)
20234 }
20235}
20236
20237impl RowRangeExt for Range<DisplayRow> {
20238 type Row = DisplayRow;
20239
20240 fn len(&self) -> usize {
20241 (self.end.0 - self.start.0) as usize
20242 }
20243
20244 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20245 (self.start.0..self.end.0).map(DisplayRow)
20246 }
20247}
20248
20249/// If select range has more than one line, we
20250/// just point the cursor to range.start.
20251fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20252 if range.start.row == range.end.row {
20253 range
20254 } else {
20255 range.start..range.start
20256 }
20257}
20258pub struct KillRing(ClipboardItem);
20259impl Global for KillRing {}
20260
20261const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20262
20263enum BreakpointPromptEditAction {
20264 Log,
20265 Condition,
20266 HitCondition,
20267}
20268
20269struct BreakpointPromptEditor {
20270 pub(crate) prompt: Entity<Editor>,
20271 editor: WeakEntity<Editor>,
20272 breakpoint_anchor: Anchor,
20273 breakpoint: Breakpoint,
20274 edit_action: BreakpointPromptEditAction,
20275 block_ids: HashSet<CustomBlockId>,
20276 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20277 _subscriptions: Vec<Subscription>,
20278}
20279
20280impl BreakpointPromptEditor {
20281 const MAX_LINES: u8 = 4;
20282
20283 fn new(
20284 editor: WeakEntity<Editor>,
20285 breakpoint_anchor: Anchor,
20286 breakpoint: Breakpoint,
20287 edit_action: BreakpointPromptEditAction,
20288 window: &mut Window,
20289 cx: &mut Context<Self>,
20290 ) -> Self {
20291 let base_text = match edit_action {
20292 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20293 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20294 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20295 }
20296 .map(|msg| msg.to_string())
20297 .unwrap_or_default();
20298
20299 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20300 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20301
20302 let prompt = cx.new(|cx| {
20303 let mut prompt = Editor::new(
20304 EditorMode::AutoHeight {
20305 max_lines: Self::MAX_LINES as usize,
20306 },
20307 buffer,
20308 None,
20309 window,
20310 cx,
20311 );
20312 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20313 prompt.set_show_cursor_when_unfocused(false, cx);
20314 prompt.set_placeholder_text(
20315 match edit_action {
20316 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20317 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20318 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20319 },
20320 cx,
20321 );
20322
20323 prompt
20324 });
20325
20326 Self {
20327 prompt,
20328 editor,
20329 breakpoint_anchor,
20330 breakpoint,
20331 edit_action,
20332 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20333 block_ids: Default::default(),
20334 _subscriptions: vec![],
20335 }
20336 }
20337
20338 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20339 self.block_ids.extend(block_ids)
20340 }
20341
20342 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20343 if let Some(editor) = self.editor.upgrade() {
20344 let message = self
20345 .prompt
20346 .read(cx)
20347 .buffer
20348 .read(cx)
20349 .as_singleton()
20350 .expect("A multi buffer in breakpoint prompt isn't possible")
20351 .read(cx)
20352 .as_rope()
20353 .to_string();
20354
20355 editor.update(cx, |editor, cx| {
20356 editor.edit_breakpoint_at_anchor(
20357 self.breakpoint_anchor,
20358 self.breakpoint.clone(),
20359 match self.edit_action {
20360 BreakpointPromptEditAction::Log => {
20361 BreakpointEditAction::EditLogMessage(message.into())
20362 }
20363 BreakpointPromptEditAction::Condition => {
20364 BreakpointEditAction::EditCondition(message.into())
20365 }
20366 BreakpointPromptEditAction::HitCondition => {
20367 BreakpointEditAction::EditHitCondition(message.into())
20368 }
20369 },
20370 cx,
20371 );
20372
20373 editor.remove_blocks(self.block_ids.clone(), None, cx);
20374 cx.focus_self(window);
20375 });
20376 }
20377 }
20378
20379 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
20380 self.editor
20381 .update(cx, |editor, cx| {
20382 editor.remove_blocks(self.block_ids.clone(), None, cx);
20383 window.focus(&editor.focus_handle);
20384 })
20385 .log_err();
20386 }
20387
20388 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
20389 let settings = ThemeSettings::get_global(cx);
20390 let text_style = TextStyle {
20391 color: if self.prompt.read(cx).read_only(cx) {
20392 cx.theme().colors().text_disabled
20393 } else {
20394 cx.theme().colors().text
20395 },
20396 font_family: settings.buffer_font.family.clone(),
20397 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20398 font_size: settings.buffer_font_size(cx).into(),
20399 font_weight: settings.buffer_font.weight,
20400 line_height: relative(settings.buffer_line_height.value()),
20401 ..Default::default()
20402 };
20403 EditorElement::new(
20404 &self.prompt,
20405 EditorStyle {
20406 background: cx.theme().colors().editor_background,
20407 local_player: cx.theme().players().local(),
20408 text: text_style,
20409 ..Default::default()
20410 },
20411 )
20412 }
20413}
20414
20415impl Render for BreakpointPromptEditor {
20416 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20417 let gutter_dimensions = *self.gutter_dimensions.lock();
20418 h_flex()
20419 .key_context("Editor")
20420 .bg(cx.theme().colors().editor_background)
20421 .border_y_1()
20422 .border_color(cx.theme().status().info_border)
20423 .size_full()
20424 .py(window.line_height() / 2.5)
20425 .on_action(cx.listener(Self::confirm))
20426 .on_action(cx.listener(Self::cancel))
20427 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
20428 .child(div().flex_1().child(self.render_prompt_editor(cx)))
20429 }
20430}
20431
20432impl Focusable for BreakpointPromptEditor {
20433 fn focus_handle(&self, cx: &App) -> FocusHandle {
20434 self.prompt.focus_handle(cx)
20435 }
20436}
20437
20438fn all_edits_insertions_or_deletions(
20439 edits: &Vec<(Range<Anchor>, String)>,
20440 snapshot: &MultiBufferSnapshot,
20441) -> bool {
20442 let mut all_insertions = true;
20443 let mut all_deletions = true;
20444
20445 for (range, new_text) in edits.iter() {
20446 let range_is_empty = range.to_offset(&snapshot).is_empty();
20447 let text_is_empty = new_text.is_empty();
20448
20449 if range_is_empty != text_is_empty {
20450 if range_is_empty {
20451 all_deletions = false;
20452 } else {
20453 all_insertions = false;
20454 }
20455 } else {
20456 return false;
20457 }
20458
20459 if !all_insertions && !all_deletions {
20460 return false;
20461 }
20462 }
20463 all_insertions || all_deletions
20464}
20465
20466struct MissingEditPredictionKeybindingTooltip;
20467
20468impl Render for MissingEditPredictionKeybindingTooltip {
20469 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20470 ui::tooltip_container(window, cx, |container, _, cx| {
20471 container
20472 .flex_shrink_0()
20473 .max_w_80()
20474 .min_h(rems_from_px(124.))
20475 .justify_between()
20476 .child(
20477 v_flex()
20478 .flex_1()
20479 .text_ui_sm(cx)
20480 .child(Label::new("Conflict with Accept Keybinding"))
20481 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
20482 )
20483 .child(
20484 h_flex()
20485 .pb_1()
20486 .gap_1()
20487 .items_end()
20488 .w_full()
20489 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
20490 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
20491 }))
20492 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
20493 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
20494 })),
20495 )
20496 })
20497 }
20498}
20499
20500#[derive(Debug, Clone, Copy, PartialEq)]
20501pub struct LineHighlight {
20502 pub background: Background,
20503 pub border: Option<gpui::Hsla>,
20504}
20505
20506impl From<Hsla> for LineHighlight {
20507 fn from(hsla: Hsla) -> Self {
20508 Self {
20509 background: hsla.into(),
20510 border: None,
20511 }
20512 }
20513}
20514
20515impl From<Background> for LineHighlight {
20516 fn from(background: Background) -> Self {
20517 Self {
20518 background,
20519 border: None,
20520 }
20521 }
20522}
20523
20524fn render_diff_hunk_controls(
20525 row: u32,
20526 status: &DiffHunkStatus,
20527 hunk_range: Range<Anchor>,
20528 is_created_file: bool,
20529 line_height: Pixels,
20530 editor: &Entity<Editor>,
20531 _window: &mut Window,
20532 cx: &mut App,
20533) -> AnyElement {
20534 h_flex()
20535 .h(line_height)
20536 .mr_1()
20537 .gap_1()
20538 .px_0p5()
20539 .pb_1()
20540 .border_x_1()
20541 .border_b_1()
20542 .border_color(cx.theme().colors().border_variant)
20543 .rounded_b_lg()
20544 .bg(cx.theme().colors().editor_background)
20545 .gap_1()
20546 .occlude()
20547 .shadow_md()
20548 .child(if status.has_secondary_hunk() {
20549 Button::new(("stage", row as u64), "Stage")
20550 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20551 .tooltip({
20552 let focus_handle = editor.focus_handle(cx);
20553 move |window, cx| {
20554 Tooltip::for_action_in(
20555 "Stage Hunk",
20556 &::git::ToggleStaged,
20557 &focus_handle,
20558 window,
20559 cx,
20560 )
20561 }
20562 })
20563 .on_click({
20564 let editor = editor.clone();
20565 move |_event, _window, cx| {
20566 editor.update(cx, |editor, cx| {
20567 editor.stage_or_unstage_diff_hunks(
20568 true,
20569 vec![hunk_range.start..hunk_range.start],
20570 cx,
20571 );
20572 });
20573 }
20574 })
20575 } else {
20576 Button::new(("unstage", row as u64), "Unstage")
20577 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
20578 .tooltip({
20579 let focus_handle = editor.focus_handle(cx);
20580 move |window, cx| {
20581 Tooltip::for_action_in(
20582 "Unstage Hunk",
20583 &::git::ToggleStaged,
20584 &focus_handle,
20585 window,
20586 cx,
20587 )
20588 }
20589 })
20590 .on_click({
20591 let editor = editor.clone();
20592 move |_event, _window, cx| {
20593 editor.update(cx, |editor, cx| {
20594 editor.stage_or_unstage_diff_hunks(
20595 false,
20596 vec![hunk_range.start..hunk_range.start],
20597 cx,
20598 );
20599 });
20600 }
20601 })
20602 })
20603 .child(
20604 Button::new(("restore", row as u64), "Restore")
20605 .tooltip({
20606 let focus_handle = editor.focus_handle(cx);
20607 move |window, cx| {
20608 Tooltip::for_action_in(
20609 "Restore Hunk",
20610 &::git::Restore,
20611 &focus_handle,
20612 window,
20613 cx,
20614 )
20615 }
20616 })
20617 .on_click({
20618 let editor = editor.clone();
20619 move |_event, window, cx| {
20620 editor.update(cx, |editor, cx| {
20621 let snapshot = editor.snapshot(window, cx);
20622 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
20623 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
20624 });
20625 }
20626 })
20627 .disabled(is_created_file),
20628 )
20629 .when(
20630 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
20631 |el| {
20632 el.child(
20633 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
20634 .shape(IconButtonShape::Square)
20635 .icon_size(IconSize::Small)
20636 // .disabled(!has_multiple_hunks)
20637 .tooltip({
20638 let focus_handle = editor.focus_handle(cx);
20639 move |window, cx| {
20640 Tooltip::for_action_in(
20641 "Next Hunk",
20642 &GoToHunk,
20643 &focus_handle,
20644 window,
20645 cx,
20646 )
20647 }
20648 })
20649 .on_click({
20650 let editor = editor.clone();
20651 move |_event, window, cx| {
20652 editor.update(cx, |editor, cx| {
20653 let snapshot = editor.snapshot(window, cx);
20654 let position =
20655 hunk_range.end.to_point(&snapshot.buffer_snapshot);
20656 editor.go_to_hunk_before_or_after_position(
20657 &snapshot,
20658 position,
20659 Direction::Next,
20660 window,
20661 cx,
20662 );
20663 editor.expand_selected_diff_hunks(cx);
20664 });
20665 }
20666 }),
20667 )
20668 .child(
20669 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
20670 .shape(IconButtonShape::Square)
20671 .icon_size(IconSize::Small)
20672 // .disabled(!has_multiple_hunks)
20673 .tooltip({
20674 let focus_handle = editor.focus_handle(cx);
20675 move |window, cx| {
20676 Tooltip::for_action_in(
20677 "Previous Hunk",
20678 &GoToPreviousHunk,
20679 &focus_handle,
20680 window,
20681 cx,
20682 )
20683 }
20684 })
20685 .on_click({
20686 let editor = editor.clone();
20687 move |_event, window, cx| {
20688 editor.update(cx, |editor, cx| {
20689 let snapshot = editor.snapshot(window, cx);
20690 let point =
20691 hunk_range.start.to_point(&snapshot.buffer_snapshot);
20692 editor.go_to_hunk_before_or_after_position(
20693 &snapshot,
20694 point,
20695 Direction::Prev,
20696 window,
20697 cx,
20698 );
20699 editor.expand_selected_diff_hunks(cx);
20700 });
20701 }
20702 }),
20703 )
20704 },
20705 )
20706 .into_any_element()
20707}