1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::ReplicaId;
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use display_map::*;
63pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
64use editor_settings::GoToDefinitionFallback;
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69pub use editor_settings_controls::*;
70use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
71pub use element::{
72 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
73};
74use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
75use futures::{
76 FutureExt,
77 future::{self, Shared, join},
78};
79use fuzzy::StringMatchCandidate;
80
81use ::git::blame::BlameEntry;
82use ::git::{Restore, blame::ParsedCommitMessage};
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use git::blame::{GitBlame, GlobalBlameRenderer};
88use gpui::{
89 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
90 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
91 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
92 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
93 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
94 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
95 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
96 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
97};
98use highlight_matching_bracket::refresh_matching_bracket_highlights;
99use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
100pub use hover_popover::hover_markdown_style;
101use hover_popover::{HoverState, hide_hover};
102use indent_guides::ActiveIndentGuidesState;
103use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
104pub use inline_completion::Direction;
105use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
106pub use items::MAX_TAB_TITLE_LEN;
107use itertools::Itertools;
108use language::{
109 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
110 CursorShape, DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText,
111 IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject,
112 TransactionId, TreeSitterOptions, WordsQuery,
113 language_settings::{
114 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
115 all_language_settings, language_settings,
116 },
117 point_from_lsp, text_diff_with_options,
118};
119use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
120use linked_editing_ranges::refresh_linked_ranges;
121use markdown::Markdown;
122use mouse_context_menu::MouseContextMenu;
123use persistence::DB;
124use project::{
125 ProjectPath,
126 debugger::{
127 breakpoint_store::{
128 BreakpointEditAction, BreakpointState, BreakpointStore, BreakpointStoreEvent,
129 },
130 session::{Session, SessionEvent},
131 },
132};
133
134pub use git::blame::BlameRenderer;
135pub use proposed_changes_editor::{
136 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
137};
138use smallvec::smallvec;
139use std::{cell::OnceCell, iter::Peekable};
140use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
141
142pub use lsp::CompletionContext;
143use lsp::{
144 CodeActionKind, CompletionItemKind, CompletionTriggerKind, DiagnosticSeverity,
145 InsertTextFormat, InsertTextMode, LanguageServerId, LanguageServerName,
146};
147
148use language::BufferSnapshot;
149pub use lsp_ext::lsp_tasks;
150use movement::TextLayoutDetails;
151pub use multi_buffer::{
152 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
153 RowInfo, ToOffset, ToPoint,
154};
155use multi_buffer::{
156 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
157 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
158};
159use parking_lot::Mutex;
160use project::{
161 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
162 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
163 TaskSourceKind,
164 debugger::breakpoint_store::Breakpoint,
165 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
166 project_settings::{GitGutterSetting, ProjectSettings},
167};
168use rand::prelude::*;
169use rpc::{ErrorExt, proto::*};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
171use selections_collection::{
172 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
173};
174use serde::{Deserialize, Serialize};
175use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
176use smallvec::SmallVec;
177use snippet::Snippet;
178use std::sync::Arc;
179use std::{
180 any::TypeId,
181 borrow::Cow,
182 cell::RefCell,
183 cmp::{self, Ordering, Reverse},
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not as _, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 time::{Duration, Instant},
190};
191pub use sum_tree::Bias;
192use sum_tree::TreeMap;
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
194use theme::{
195 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Key, Tooltip, h_flex, prelude::*,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemHandle, PreviewTabsSettings},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::hover_links::{find_url, find_url_from_range};
213use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
214
215pub const FILE_HEADER_HEIGHT: u32 = 2;
216pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
217pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
249 alt: true,
250 shift: true,
251 control: false,
252 platform: false,
253 function: false,
254};
255
256struct InlineValueCache {
257 enabled: bool,
258 inlays: Vec<InlayId>,
259 refresh_task: Task<Option<()>>,
260}
261
262impl InlineValueCache {
263 fn new(enabled: bool) -> Self {
264 Self {
265 enabled,
266 inlays: Vec::new(),
267 refresh_task: Task::ready(None),
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
273pub enum InlayId {
274 InlineCompletion(usize),
275 Hint(usize),
276 DebuggerValue(usize),
277}
278
279impl InlayId {
280 fn id(&self) -> usize {
281 match self {
282 Self::InlineCompletion(id) => *id,
283 Self::Hint(id) => *id,
284 Self::DebuggerValue(id) => *id,
285 }
286 }
287}
288
289pub enum DebugCurrentRowHighlight {}
290enum DocumentHighlightRead {}
291enum DocumentHighlightWrite {}
292enum InputComposition {}
293enum SelectedTextHighlight {}
294
295pub enum ConflictsOuter {}
296pub enum ConflictsOurs {}
297pub enum ConflictsTheirs {}
298pub enum ConflictsOursMarker {}
299pub enum ConflictsTheirsMarker {}
300
301#[derive(Debug, Copy, Clone, PartialEq, Eq)]
302pub enum Navigated {
303 Yes,
304 No,
305}
306
307impl Navigated {
308 pub fn from_bool(yes: bool) -> Navigated {
309 if yes { Navigated::Yes } else { Navigated::No }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq)]
314enum DisplayDiffHunk {
315 Folded {
316 display_row: DisplayRow,
317 },
318 Unfolded {
319 is_created_file: bool,
320 diff_base_byte_range: Range<usize>,
321 display_row_range: Range<DisplayRow>,
322 multi_buffer_range: Range<Anchor>,
323 status: DiffHunkStatus,
324 },
325}
326
327pub enum HideMouseCursorOrigin {
328 TypingAction,
329 MovementAction,
330}
331
332pub fn init_settings(cx: &mut App) {
333 EditorSettings::register(cx);
334}
335
336pub fn init(cx: &mut App) {
337 init_settings(cx);
338
339 cx.set_global(GlobalBlameRenderer(Arc::new(())));
340
341 workspace::register_project_item::<Editor>(cx);
342 workspace::FollowableViewRegistry::register::<Editor>(cx);
343 workspace::register_serializable_item::<Editor>(cx);
344
345 cx.observe_new(
346 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
347 workspace.register_action(Editor::new_file);
348 workspace.register_action(Editor::new_file_vertical);
349 workspace.register_action(Editor::new_file_horizontal);
350 workspace.register_action(Editor::cancel_language_server_work);
351 },
352 )
353 .detach();
354
355 cx.on_action(move |_: &workspace::NewFile, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 Editor::new_file(workspace, &Default::default(), window, cx)
364 },
365 )
366 .detach();
367 }
368 });
369 cx.on_action(move |_: &workspace::NewWindow, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 cx.activate(true);
378 Editor::new_file(workspace, &Default::default(), window, cx)
379 },
380 )
381 .detach();
382 }
383 });
384}
385
386pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
387 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
388}
389
390pub trait DiagnosticRenderer {
391 fn render_group(
392 &self,
393 diagnostic_group: Vec<DiagnosticEntry<Point>>,
394 buffer_id: BufferId,
395 snapshot: EditorSnapshot,
396 editor: WeakEntity<Editor>,
397 cx: &mut App,
398 ) -> Vec<BlockProperties<Anchor>>;
399
400 fn render_hover(
401 &self,
402 diagnostic_group: Vec<DiagnosticEntry<Point>>,
403 range: Range<Point>,
404 buffer_id: BufferId,
405 cx: &mut App,
406 ) -> Option<Entity<markdown::Markdown>>;
407
408 fn open_link(
409 &self,
410 editor: &mut Editor,
411 link: SharedString,
412 window: &mut Window,
413 cx: &mut Context<Editor>,
414 );
415}
416
417pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
418
419impl GlobalDiagnosticRenderer {
420 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
421 cx.try_global::<Self>().map(|g| g.0.clone())
422 }
423}
424
425impl gpui::Global for GlobalDiagnosticRenderer {}
426pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
427 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
428}
429
430pub struct SearchWithinRange;
431
432trait InvalidationRegion {
433 fn ranges(&self) -> &[Range<Anchor>];
434}
435
436#[derive(Clone, Debug, PartialEq)]
437pub enum SelectPhase {
438 Begin {
439 position: DisplayPoint,
440 add: bool,
441 click_count: usize,
442 },
443 BeginColumnar {
444 position: DisplayPoint,
445 reset: bool,
446 goal_column: u32,
447 },
448 Extend {
449 position: DisplayPoint,
450 click_count: usize,
451 },
452 Update {
453 position: DisplayPoint,
454 goal_column: u32,
455 scroll_delta: gpui::Point<f32>,
456 },
457 End,
458}
459
460#[derive(Clone, Debug)]
461pub enum SelectMode {
462 Character,
463 Word(Range<Anchor>),
464 Line(Range<Anchor>),
465 All,
466}
467
468#[derive(Copy, Clone, PartialEq, Eq, Debug)]
469pub enum EditorMode {
470 SingleLine {
471 auto_width: bool,
472 },
473 AutoHeight {
474 max_lines: usize,
475 },
476 Full {
477 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
478 scale_ui_elements_with_buffer_font_size: bool,
479 /// When set to `true`, the editor will render a background for the active line.
480 show_active_line_background: bool,
481 /// When set to `true`, the editor's height will be determined by its content.
482 sized_by_content: bool,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sized_by_content: false,
492 }
493 }
494
495 pub fn is_full(&self) -> bool {
496 matches!(self, Self::Full { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub local_player: PlayerColor,
521 pub text: TextStyle,
522 pub scrollbar_width: Pixels,
523 pub syntax: Arc<SyntaxTheme>,
524 pub status: StatusColors,
525 pub inlay_hints_style: HighlightStyle,
526 pub inline_completion_styles: InlineCompletionStyles,
527 pub unnecessary_code_fade: f32,
528}
529
530impl Default for EditorStyle {
531 fn default() -> Self {
532 Self {
533 background: Hsla::default(),
534 local_player: PlayerColor::default(),
535 text: TextStyle::default(),
536 scrollbar_width: Pixels::default(),
537 syntax: Default::default(),
538 // HACK: Status colors don't have a real default.
539 // We should look into removing the status colors from the editor
540 // style and retrieve them directly from the theme.
541 status: StatusColors::dark(),
542 inlay_hints_style: HighlightStyle::default(),
543 inline_completion_styles: InlineCompletionStyles {
544 insertion: HighlightStyle::default(),
545 whitespace: HighlightStyle::default(),
546 },
547 unnecessary_code_fade: Default::default(),
548 }
549 }
550}
551
552pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
553 let show_background = language_settings::language_settings(None, None, cx)
554 .inlay_hints
555 .show_background;
556
557 HighlightStyle {
558 color: Some(cx.theme().status().hint),
559 background_color: show_background.then(|| cx.theme().status().hint_background),
560 ..HighlightStyle::default()
561 }
562}
563
564pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
565 InlineCompletionStyles {
566 insertion: HighlightStyle {
567 color: Some(cx.theme().status().predictive),
568 ..HighlightStyle::default()
569 },
570 whitespace: HighlightStyle {
571 background_color: Some(cx.theme().status().created_background),
572 ..HighlightStyle::default()
573 },
574 }
575}
576
577type CompletionId = usize;
578
579pub(crate) enum EditDisplayMode {
580 TabAccept,
581 DiffPopover,
582 Inline,
583}
584
585enum InlineCompletion {
586 Edit {
587 edits: Vec<(Range<Anchor>, String)>,
588 edit_preview: Option<EditPreview>,
589 display_mode: EditDisplayMode,
590 snapshot: BufferSnapshot,
591 },
592 Move {
593 target: Anchor,
594 snapshot: BufferSnapshot,
595 },
596}
597
598struct InlineCompletionState {
599 inlay_ids: Vec<InlayId>,
600 completion: InlineCompletion,
601 completion_id: Option<SharedString>,
602 invalidation_range: Range<Anchor>,
603}
604
605enum EditPredictionSettings {
606 Disabled,
607 Enabled {
608 show_in_menu: bool,
609 preview_requires_modifier: bool,
610 },
611}
612
613enum InlineCompletionHighlight {}
614
615#[derive(Debug, Clone)]
616struct InlineDiagnostic {
617 message: SharedString,
618 group_id: usize,
619 is_primary: bool,
620 start: Point,
621 severity: DiagnosticSeverity,
622}
623
624pub enum MenuInlineCompletionsPolicy {
625 Never,
626 ByProvider,
627}
628
629pub enum EditPredictionPreview {
630 /// Modifier is not pressed
631 Inactive { released_too_fast: bool },
632 /// Modifier pressed
633 Active {
634 since: Instant,
635 previous_scroll_position: Option<ScrollAnchor>,
636 },
637}
638
639impl EditPredictionPreview {
640 pub fn released_too_fast(&self) -> bool {
641 match self {
642 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
643 EditPredictionPreview::Active { .. } => false,
644 }
645 }
646
647 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
648 if let EditPredictionPreview::Active {
649 previous_scroll_position,
650 ..
651 } = self
652 {
653 *previous_scroll_position = scroll_position;
654 }
655 }
656}
657
658pub struct ContextMenuOptions {
659 pub min_entries_visible: usize,
660 pub max_entries_visible: usize,
661 pub placement: Option<ContextMenuPlacement>,
662}
663
664#[derive(Debug, Clone, PartialEq, Eq)]
665pub enum ContextMenuPlacement {
666 Above,
667 Below,
668}
669
670#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
671struct EditorActionId(usize);
672
673impl EditorActionId {
674 pub fn post_inc(&mut self) -> Self {
675 let answer = self.0;
676
677 *self = Self(answer + 1);
678
679 Self(answer)
680 }
681}
682
683// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
684// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
685
686type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
687type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
688
689#[derive(Default)]
690struct ScrollbarMarkerState {
691 scrollbar_size: Size<Pixels>,
692 dirty: bool,
693 markers: Arc<[PaintQuad]>,
694 pending_refresh: Option<Task<Result<()>>>,
695}
696
697impl ScrollbarMarkerState {
698 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
699 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
700 }
701}
702
703#[derive(Clone, Debug)]
704struct RunnableTasks {
705 templates: Vec<(TaskSourceKind, TaskTemplate)>,
706 offset: multi_buffer::Anchor,
707 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
708 column: u32,
709 // Values of all named captures, including those starting with '_'
710 extra_variables: HashMap<String, String>,
711 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
712 context_range: Range<BufferOffset>,
713}
714
715impl RunnableTasks {
716 fn resolve<'a>(
717 &'a self,
718 cx: &'a task::TaskContext,
719 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
720 self.templates.iter().filter_map(|(kind, template)| {
721 template
722 .resolve_task(&kind.to_id_base(), cx)
723 .map(|task| (kind.clone(), task))
724 })
725 }
726}
727
728#[derive(Clone)]
729struct ResolvedTasks {
730 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
731 position: Anchor,
732}
733
734#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
735struct BufferOffset(usize);
736
737// Addons allow storing per-editor state in other crates (e.g. Vim)
738pub trait Addon: 'static {
739 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
740
741 fn render_buffer_header_controls(
742 &self,
743 _: &ExcerptInfo,
744 _: &Window,
745 _: &App,
746 ) -> Option<AnyElement> {
747 None
748 }
749
750 fn to_any(&self) -> &dyn std::any::Any;
751
752 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
753 None
754 }
755}
756
757/// A set of caret positions, registered when the editor was edited.
758pub struct ChangeList {
759 changes: Vec<Vec<Anchor>>,
760 /// Currently "selected" change.
761 position: Option<usize>,
762}
763
764impl ChangeList {
765 pub fn new() -> Self {
766 Self {
767 changes: Vec::new(),
768 position: None,
769 }
770 }
771
772 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
773 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
774 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
775 if self.changes.is_empty() {
776 return None;
777 }
778
779 let prev = self.position.unwrap_or(self.changes.len());
780 let next = if direction == Direction::Prev {
781 prev.saturating_sub(count)
782 } else {
783 (prev + count).min(self.changes.len() - 1)
784 };
785 self.position = Some(next);
786 self.changes.get(next).map(|anchors| anchors.as_slice())
787 }
788
789 /// Adds a new change to the list, resetting the change list position.
790 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
791 self.position.take();
792 if pop_state {
793 self.changes.pop();
794 }
795 self.changes.push(new_positions.clone());
796 }
797
798 pub fn last(&self) -> Option<&[Anchor]> {
799 self.changes.last().map(|anchors| anchors.as_slice())
800 }
801}
802
803#[derive(Clone)]
804struct InlineBlamePopoverState {
805 scroll_handle: ScrollHandle,
806 commit_message: Option<ParsedCommitMessage>,
807 markdown: Entity<Markdown>,
808}
809
810struct InlineBlamePopover {
811 position: gpui::Point<Pixels>,
812 show_task: Option<Task<()>>,
813 hide_task: Option<Task<()>>,
814 popover_bounds: Option<Bounds<Pixels>>,
815 popover_state: InlineBlamePopoverState,
816}
817
818/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
819/// a breakpoint on them.
820#[derive(Clone, Copy, Debug)]
821struct PhantomBreakpointIndicator {
822 display_row: DisplayRow,
823 /// There's a small debounce between hovering over the line and showing the indicator.
824 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
825 is_active: bool,
826 collides_with_existing_breakpoint: bool,
827}
828/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
829///
830/// See the [module level documentation](self) for more information.
831pub struct Editor {
832 focus_handle: FocusHandle,
833 last_focused_descendant: Option<WeakFocusHandle>,
834 /// The text buffer being edited
835 buffer: Entity<MultiBuffer>,
836 /// Map of how text in the buffer should be displayed.
837 /// Handles soft wraps, folds, fake inlay text insertions, etc.
838 pub display_map: Entity<DisplayMap>,
839 pub selections: SelectionsCollection,
840 pub scroll_manager: ScrollManager,
841 /// When inline assist editors are linked, they all render cursors because
842 /// typing enters text into each of them, even the ones that aren't focused.
843 pub(crate) show_cursor_when_unfocused: bool,
844 columnar_selection_tail: Option<Anchor>,
845 add_selections_state: Option<AddSelectionsState>,
846 select_next_state: Option<SelectNextState>,
847 select_prev_state: Option<SelectNextState>,
848 selection_history: SelectionHistory,
849 autoclose_regions: Vec<AutocloseRegion>,
850 snippet_stack: InvalidationStack<SnippetState>,
851 select_syntax_node_history: SelectSyntaxNodeHistory,
852 ime_transaction: Option<TransactionId>,
853 active_diagnostics: ActiveDiagnostic,
854 show_inline_diagnostics: bool,
855 inline_diagnostics_update: Task<()>,
856 inline_diagnostics_enabled: bool,
857 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
858 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
859 hard_wrap: Option<usize>,
860
861 // TODO: make this a access method
862 pub project: Option<Entity<Project>>,
863 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
864 completion_provider: Option<Box<dyn CompletionProvider>>,
865 collaboration_hub: Option<Box<dyn CollaborationHub>>,
866 blink_manager: Entity<BlinkManager>,
867 show_cursor_names: bool,
868 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
869 pub show_local_selections: bool,
870 mode: EditorMode,
871 show_breadcrumbs: bool,
872 show_gutter: bool,
873 show_scrollbars: bool,
874 disable_scrolling: bool,
875 disable_expand_excerpt_buttons: bool,
876 show_line_numbers: Option<bool>,
877 use_relative_line_numbers: Option<bool>,
878 show_git_diff_gutter: Option<bool>,
879 show_code_actions: Option<bool>,
880 show_runnables: Option<bool>,
881 show_breakpoints: Option<bool>,
882 show_wrap_guides: Option<bool>,
883 show_indent_guides: Option<bool>,
884 placeholder_text: Option<Arc<str>>,
885 highlight_order: usize,
886 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
887 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
888 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
889 scrollbar_marker_state: ScrollbarMarkerState,
890 active_indent_guides_state: ActiveIndentGuidesState,
891 nav_history: Option<ItemNavHistory>,
892 context_menu: RefCell<Option<CodeContextMenu>>,
893 context_menu_options: Option<ContextMenuOptions>,
894 mouse_context_menu: Option<MouseContextMenu>,
895 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
896 inline_blame_popover: Option<InlineBlamePopover>,
897 signature_help_state: SignatureHelpState,
898 auto_signature_help: Option<bool>,
899 find_all_references_task_sources: Vec<Anchor>,
900 next_completion_id: CompletionId,
901 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
902 code_actions_task: Option<Task<Result<()>>>,
903 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
904 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
905 document_highlights_task: Option<Task<()>>,
906 linked_editing_range_task: Option<Task<Option<()>>>,
907 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
908 pending_rename: Option<RenameState>,
909 searchable: bool,
910 cursor_shape: CursorShape,
911 current_line_highlight: Option<CurrentLineHighlight>,
912 collapse_matches: bool,
913 autoindent_mode: Option<AutoindentMode>,
914 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
915 input_enabled: bool,
916 use_modal_editing: bool,
917 read_only: bool,
918 leader_peer_id: Option<PeerId>,
919 remote_id: Option<ViewId>,
920 pub hover_state: HoverState,
921 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
922 gutter_hovered: bool,
923 hovered_link_state: Option<HoveredLinkState>,
924 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
925 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
926 active_inline_completion: Option<InlineCompletionState>,
927 /// Used to prevent flickering as the user types while the menu is open
928 stale_inline_completion_in_menu: Option<InlineCompletionState>,
929 edit_prediction_settings: EditPredictionSettings,
930 inline_completions_hidden_for_vim_mode: bool,
931 show_inline_completions_override: Option<bool>,
932 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
933 edit_prediction_preview: EditPredictionPreview,
934 edit_prediction_indent_conflict: bool,
935 edit_prediction_requires_modifier_in_indent_conflict: bool,
936 inlay_hint_cache: InlayHintCache,
937 next_inlay_id: usize,
938 _subscriptions: Vec<Subscription>,
939 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
940 gutter_dimensions: GutterDimensions,
941 style: Option<EditorStyle>,
942 text_style_refinement: Option<TextStyleRefinement>,
943 next_editor_action_id: EditorActionId,
944 editor_actions:
945 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
946 use_autoclose: bool,
947 use_auto_surround: bool,
948 auto_replace_emoji_shortcode: bool,
949 jsx_tag_auto_close_enabled_in_any_buffer: bool,
950 show_git_blame_gutter: bool,
951 show_git_blame_inline: bool,
952 show_git_blame_inline_delay_task: Option<Task<()>>,
953 git_blame_inline_enabled: bool,
954 render_diff_hunk_controls: RenderDiffHunkControlsFn,
955 serialize_dirty_buffers: bool,
956 show_selection_menu: Option<bool>,
957 blame: Option<Entity<GitBlame>>,
958 blame_subscription: Option<Subscription>,
959 custom_context_menu: Option<
960 Box<
961 dyn 'static
962 + Fn(
963 &mut Self,
964 DisplayPoint,
965 &mut Window,
966 &mut Context<Self>,
967 ) -> Option<Entity<ui::ContextMenu>>,
968 >,
969 >,
970 last_bounds: Option<Bounds<Pixels>>,
971 last_position_map: Option<Rc<PositionMap>>,
972 expect_bounds_change: Option<Bounds<Pixels>>,
973 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
974 tasks_update_task: Option<Task<()>>,
975 breakpoint_store: Option<Entity<BreakpointStore>>,
976 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
977 in_project_search: bool,
978 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
979 breadcrumb_header: Option<String>,
980 focused_block: Option<FocusedBlock>,
981 next_scroll_position: NextScrollCursorCenterTopBottom,
982 addons: HashMap<TypeId, Box<dyn Addon>>,
983 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
984 load_diff_task: Option<Shared<Task<()>>>,
985 selection_mark_mode: bool,
986 toggle_fold_multiple_buffers: Task<()>,
987 _scroll_cursor_center_top_bottom_task: Task<()>,
988 serialize_selections: Task<()>,
989 serialize_folds: Task<()>,
990 mouse_cursor_hidden: bool,
991 hide_mouse_mode: HideMouseMode,
992 pub change_list: ChangeList,
993 inline_value_cache: InlineValueCache,
994}
995
996#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
997enum NextScrollCursorCenterTopBottom {
998 #[default]
999 Center,
1000 Top,
1001 Bottom,
1002}
1003
1004impl NextScrollCursorCenterTopBottom {
1005 fn next(&self) -> Self {
1006 match self {
1007 Self::Center => Self::Top,
1008 Self::Top => Self::Bottom,
1009 Self::Bottom => Self::Center,
1010 }
1011 }
1012}
1013
1014#[derive(Clone)]
1015pub struct EditorSnapshot {
1016 pub mode: EditorMode,
1017 show_gutter: bool,
1018 show_line_numbers: Option<bool>,
1019 show_git_diff_gutter: Option<bool>,
1020 show_code_actions: Option<bool>,
1021 show_runnables: Option<bool>,
1022 show_breakpoints: Option<bool>,
1023 git_blame_gutter_max_author_length: Option<usize>,
1024 pub display_snapshot: DisplaySnapshot,
1025 pub placeholder_text: Option<Arc<str>>,
1026 is_focused: bool,
1027 scroll_anchor: ScrollAnchor,
1028 ongoing_scroll: OngoingScroll,
1029 current_line_highlight: CurrentLineHighlight,
1030 gutter_hovered: bool,
1031}
1032
1033#[derive(Default, Debug, Clone, Copy)]
1034pub struct GutterDimensions {
1035 pub left_padding: Pixels,
1036 pub right_padding: Pixels,
1037 pub width: Pixels,
1038 pub margin: Pixels,
1039 pub git_blame_entries_width: Option<Pixels>,
1040}
1041
1042impl GutterDimensions {
1043 /// The full width of the space taken up by the gutter.
1044 pub fn full_width(&self) -> Pixels {
1045 self.margin + self.width
1046 }
1047
1048 /// The width of the space reserved for the fold indicators,
1049 /// use alongside 'justify_end' and `gutter_width` to
1050 /// right align content with the line numbers
1051 pub fn fold_area_width(&self) -> Pixels {
1052 self.margin + self.right_padding
1053 }
1054}
1055
1056#[derive(Debug)]
1057pub struct RemoteSelection {
1058 pub replica_id: ReplicaId,
1059 pub selection: Selection<Anchor>,
1060 pub cursor_shape: CursorShape,
1061 pub peer_id: PeerId,
1062 pub line_mode: bool,
1063 pub participant_index: Option<ParticipantIndex>,
1064 pub user_name: Option<SharedString>,
1065}
1066
1067#[derive(Clone, Debug)]
1068struct SelectionHistoryEntry {
1069 selections: Arc<[Selection<Anchor>]>,
1070 select_next_state: Option<SelectNextState>,
1071 select_prev_state: Option<SelectNextState>,
1072 add_selections_state: Option<AddSelectionsState>,
1073}
1074
1075enum SelectionHistoryMode {
1076 Normal,
1077 Undoing,
1078 Redoing,
1079}
1080
1081#[derive(Clone, PartialEq, Eq, Hash)]
1082struct HoveredCursor {
1083 replica_id: u16,
1084 selection_id: usize,
1085}
1086
1087impl Default for SelectionHistoryMode {
1088 fn default() -> Self {
1089 Self::Normal
1090 }
1091}
1092
1093#[derive(Default)]
1094struct SelectionHistory {
1095 #[allow(clippy::type_complexity)]
1096 selections_by_transaction:
1097 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1098 mode: SelectionHistoryMode,
1099 undo_stack: VecDeque<SelectionHistoryEntry>,
1100 redo_stack: VecDeque<SelectionHistoryEntry>,
1101}
1102
1103impl SelectionHistory {
1104 fn insert_transaction(
1105 &mut self,
1106 transaction_id: TransactionId,
1107 selections: Arc<[Selection<Anchor>]>,
1108 ) {
1109 self.selections_by_transaction
1110 .insert(transaction_id, (selections, None));
1111 }
1112
1113 #[allow(clippy::type_complexity)]
1114 fn transaction(
1115 &self,
1116 transaction_id: TransactionId,
1117 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1118 self.selections_by_transaction.get(&transaction_id)
1119 }
1120
1121 #[allow(clippy::type_complexity)]
1122 fn transaction_mut(
1123 &mut self,
1124 transaction_id: TransactionId,
1125 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1126 self.selections_by_transaction.get_mut(&transaction_id)
1127 }
1128
1129 fn push(&mut self, entry: SelectionHistoryEntry) {
1130 if !entry.selections.is_empty() {
1131 match self.mode {
1132 SelectionHistoryMode::Normal => {
1133 self.push_undo(entry);
1134 self.redo_stack.clear();
1135 }
1136 SelectionHistoryMode::Undoing => self.push_redo(entry),
1137 SelectionHistoryMode::Redoing => self.push_undo(entry),
1138 }
1139 }
1140 }
1141
1142 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1143 if self
1144 .undo_stack
1145 .back()
1146 .map_or(true, |e| e.selections != entry.selections)
1147 {
1148 self.undo_stack.push_back(entry);
1149 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1150 self.undo_stack.pop_front();
1151 }
1152 }
1153 }
1154
1155 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1156 if self
1157 .redo_stack
1158 .back()
1159 .map_or(true, |e| e.selections != entry.selections)
1160 {
1161 self.redo_stack.push_back(entry);
1162 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1163 self.redo_stack.pop_front();
1164 }
1165 }
1166 }
1167}
1168
1169#[derive(Clone, Copy)]
1170pub struct RowHighlightOptions {
1171 pub autoscroll: bool,
1172 pub include_gutter: bool,
1173}
1174
1175impl Default for RowHighlightOptions {
1176 fn default() -> Self {
1177 Self {
1178 autoscroll: Default::default(),
1179 include_gutter: true,
1180 }
1181 }
1182}
1183
1184struct RowHighlight {
1185 index: usize,
1186 range: Range<Anchor>,
1187 color: Hsla,
1188 options: RowHighlightOptions,
1189 type_id: TypeId,
1190}
1191
1192#[derive(Clone, Debug)]
1193struct AddSelectionsState {
1194 above: bool,
1195 stack: Vec<usize>,
1196}
1197
1198#[derive(Clone)]
1199struct SelectNextState {
1200 query: AhoCorasick,
1201 wordwise: bool,
1202 done: bool,
1203}
1204
1205impl std::fmt::Debug for SelectNextState {
1206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1207 f.debug_struct(std::any::type_name::<Self>())
1208 .field("wordwise", &self.wordwise)
1209 .field("done", &self.done)
1210 .finish()
1211 }
1212}
1213
1214#[derive(Debug)]
1215struct AutocloseRegion {
1216 selection_id: usize,
1217 range: Range<Anchor>,
1218 pair: BracketPair,
1219}
1220
1221#[derive(Debug)]
1222struct SnippetState {
1223 ranges: Vec<Vec<Range<Anchor>>>,
1224 active_index: usize,
1225 choices: Vec<Option<Vec<String>>>,
1226}
1227
1228#[doc(hidden)]
1229pub struct RenameState {
1230 pub range: Range<Anchor>,
1231 pub old_name: Arc<str>,
1232 pub editor: Entity<Editor>,
1233 block_id: CustomBlockId,
1234}
1235
1236struct InvalidationStack<T>(Vec<T>);
1237
1238struct RegisteredInlineCompletionProvider {
1239 provider: Arc<dyn InlineCompletionProviderHandle>,
1240 _subscription: Subscription,
1241}
1242
1243#[derive(Debug, PartialEq, Eq)]
1244pub struct ActiveDiagnosticGroup {
1245 pub active_range: Range<Anchor>,
1246 pub active_message: String,
1247 pub group_id: usize,
1248 pub blocks: HashSet<CustomBlockId>,
1249}
1250
1251#[derive(Debug, PartialEq, Eq)]
1252#[allow(clippy::large_enum_variant)]
1253pub(crate) enum ActiveDiagnostic {
1254 None,
1255 All,
1256 Group(ActiveDiagnosticGroup),
1257}
1258
1259#[derive(Serialize, Deserialize, Clone, Debug)]
1260pub struct ClipboardSelection {
1261 /// The number of bytes in this selection.
1262 pub len: usize,
1263 /// Whether this was a full-line selection.
1264 pub is_entire_line: bool,
1265 /// The indentation of the first line when this content was originally copied.
1266 pub first_line_indent: u32,
1267}
1268
1269// selections, scroll behavior, was newest selection reversed
1270type SelectSyntaxNodeHistoryState = (
1271 Box<[Selection<usize>]>,
1272 SelectSyntaxNodeScrollBehavior,
1273 bool,
1274);
1275
1276#[derive(Default)]
1277struct SelectSyntaxNodeHistory {
1278 stack: Vec<SelectSyntaxNodeHistoryState>,
1279 // disable temporarily to allow changing selections without losing the stack
1280 pub disable_clearing: bool,
1281}
1282
1283impl SelectSyntaxNodeHistory {
1284 pub fn try_clear(&mut self) {
1285 if !self.disable_clearing {
1286 self.stack.clear();
1287 }
1288 }
1289
1290 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1291 self.stack.push(selection);
1292 }
1293
1294 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1295 self.stack.pop()
1296 }
1297}
1298
1299enum SelectSyntaxNodeScrollBehavior {
1300 CursorTop,
1301 FitSelection,
1302 CursorBottom,
1303}
1304
1305#[derive(Debug)]
1306pub(crate) struct NavigationData {
1307 cursor_anchor: Anchor,
1308 cursor_position: Point,
1309 scroll_anchor: ScrollAnchor,
1310 scroll_top_row: u32,
1311}
1312
1313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1314pub enum GotoDefinitionKind {
1315 Symbol,
1316 Declaration,
1317 Type,
1318 Implementation,
1319}
1320
1321#[derive(Debug, Clone)]
1322enum InlayHintRefreshReason {
1323 ModifiersChanged(bool),
1324 Toggle(bool),
1325 SettingsChange(InlayHintSettings),
1326 NewLinesShown,
1327 BufferEdited(HashSet<Arc<Language>>),
1328 RefreshRequested,
1329 ExcerptsRemoved(Vec<ExcerptId>),
1330}
1331
1332impl InlayHintRefreshReason {
1333 fn description(&self) -> &'static str {
1334 match self {
1335 Self::ModifiersChanged(_) => "modifiers changed",
1336 Self::Toggle(_) => "toggle",
1337 Self::SettingsChange(_) => "settings change",
1338 Self::NewLinesShown => "new lines shown",
1339 Self::BufferEdited(_) => "buffer edited",
1340 Self::RefreshRequested => "refresh requested",
1341 Self::ExcerptsRemoved(_) => "excerpts removed",
1342 }
1343 }
1344}
1345
1346pub enum FormatTarget {
1347 Buffers,
1348 Ranges(Vec<Range<MultiBufferPoint>>),
1349}
1350
1351pub(crate) struct FocusedBlock {
1352 id: BlockId,
1353 focus_handle: WeakFocusHandle,
1354}
1355
1356#[derive(Clone)]
1357enum JumpData {
1358 MultiBufferRow {
1359 row: MultiBufferRow,
1360 line_offset_from_top: u32,
1361 },
1362 MultiBufferPoint {
1363 excerpt_id: ExcerptId,
1364 position: Point,
1365 anchor: text::Anchor,
1366 line_offset_from_top: u32,
1367 },
1368}
1369
1370pub enum MultibufferSelectionMode {
1371 First,
1372 All,
1373}
1374
1375#[derive(Clone, Copy, Debug, Default)]
1376pub struct RewrapOptions {
1377 pub override_language_settings: bool,
1378 pub preserve_existing_whitespace: bool,
1379}
1380
1381impl Editor {
1382 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1383 let buffer = cx.new(|cx| Buffer::local("", cx));
1384 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1385 Self::new(
1386 EditorMode::SingleLine { auto_width: false },
1387 buffer,
1388 None,
1389 window,
1390 cx,
1391 )
1392 }
1393
1394 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1395 let buffer = cx.new(|cx| Buffer::local("", cx));
1396 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1397 Self::new(EditorMode::full(), buffer, None, window, cx)
1398 }
1399
1400 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1401 let buffer = cx.new(|cx| Buffer::local("", cx));
1402 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1403 Self::new(
1404 EditorMode::SingleLine { auto_width: true },
1405 buffer,
1406 None,
1407 window,
1408 cx,
1409 )
1410 }
1411
1412 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1413 let buffer = cx.new(|cx| Buffer::local("", cx));
1414 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1415 Self::new(
1416 EditorMode::AutoHeight { max_lines },
1417 buffer,
1418 None,
1419 window,
1420 cx,
1421 )
1422 }
1423
1424 pub fn for_buffer(
1425 buffer: Entity<Buffer>,
1426 project: Option<Entity<Project>>,
1427 window: &mut Window,
1428 cx: &mut Context<Self>,
1429 ) -> Self {
1430 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1431 Self::new(EditorMode::full(), buffer, project, window, cx)
1432 }
1433
1434 pub fn for_multibuffer(
1435 buffer: Entity<MultiBuffer>,
1436 project: Option<Entity<Project>>,
1437 window: &mut Window,
1438 cx: &mut Context<Self>,
1439 ) -> Self {
1440 Self::new(EditorMode::full(), buffer, project, window, cx)
1441 }
1442
1443 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1444 let mut clone = Self::new(
1445 self.mode,
1446 self.buffer.clone(),
1447 self.project.clone(),
1448 window,
1449 cx,
1450 );
1451 self.display_map.update(cx, |display_map, cx| {
1452 let snapshot = display_map.snapshot(cx);
1453 clone.display_map.update(cx, |display_map, cx| {
1454 display_map.set_state(&snapshot, cx);
1455 });
1456 });
1457 clone.folds_did_change(cx);
1458 clone.selections.clone_state(&self.selections);
1459 clone.scroll_manager.clone_state(&self.scroll_manager);
1460 clone.searchable = self.searchable;
1461 clone.read_only = self.read_only;
1462 clone
1463 }
1464
1465 pub fn new(
1466 mode: EditorMode,
1467 buffer: Entity<MultiBuffer>,
1468 project: Option<Entity<Project>>,
1469 window: &mut Window,
1470 cx: &mut Context<Self>,
1471 ) -> Self {
1472 let style = window.text_style();
1473 let font_size = style.font_size.to_pixels(window.rem_size());
1474 let editor = cx.entity().downgrade();
1475 let fold_placeholder = FoldPlaceholder {
1476 constrain_width: true,
1477 render: Arc::new(move |fold_id, fold_range, cx| {
1478 let editor = editor.clone();
1479 div()
1480 .id(fold_id)
1481 .bg(cx.theme().colors().ghost_element_background)
1482 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1483 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1484 .rounded_xs()
1485 .size_full()
1486 .cursor_pointer()
1487 .child("⋯")
1488 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1489 .on_click(move |_, _window, cx| {
1490 editor
1491 .update(cx, |editor, cx| {
1492 editor.unfold_ranges(
1493 &[fold_range.start..fold_range.end],
1494 true,
1495 false,
1496 cx,
1497 );
1498 cx.stop_propagation();
1499 })
1500 .ok();
1501 })
1502 .into_any()
1503 }),
1504 merge_adjacent: true,
1505 ..Default::default()
1506 };
1507 let display_map = cx.new(|cx| {
1508 DisplayMap::new(
1509 buffer.clone(),
1510 style.font(),
1511 font_size,
1512 None,
1513 FILE_HEADER_HEIGHT,
1514 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1515 fold_placeholder,
1516 cx,
1517 )
1518 });
1519
1520 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1521
1522 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1523
1524 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1525 .then(|| language_settings::SoftWrap::None);
1526
1527 let mut project_subscriptions = Vec::new();
1528 if mode.is_full() {
1529 if let Some(project) = project.as_ref() {
1530 project_subscriptions.push(cx.subscribe_in(
1531 project,
1532 window,
1533 |editor, _, event, window, cx| match event {
1534 project::Event::RefreshCodeLens => {
1535 // we always query lens with actions, without storing them, always refreshing them
1536 }
1537 project::Event::RefreshInlayHints => {
1538 editor
1539 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1540 }
1541 project::Event::SnippetEdit(id, snippet_edits) => {
1542 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1543 let focus_handle = editor.focus_handle(cx);
1544 if focus_handle.is_focused(window) {
1545 let snapshot = buffer.read(cx).snapshot();
1546 for (range, snippet) in snippet_edits {
1547 let editor_range =
1548 language::range_from_lsp(*range).to_offset(&snapshot);
1549 editor
1550 .insert_snippet(
1551 &[editor_range],
1552 snippet.clone(),
1553 window,
1554 cx,
1555 )
1556 .ok();
1557 }
1558 }
1559 }
1560 }
1561 _ => {}
1562 },
1563 ));
1564 if let Some(task_inventory) = project
1565 .read(cx)
1566 .task_store()
1567 .read(cx)
1568 .task_inventory()
1569 .cloned()
1570 {
1571 project_subscriptions.push(cx.observe_in(
1572 &task_inventory,
1573 window,
1574 |editor, _, window, cx| {
1575 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1576 },
1577 ));
1578 };
1579
1580 project_subscriptions.push(cx.subscribe_in(
1581 &project.read(cx).breakpoint_store(),
1582 window,
1583 |editor, _, event, window, cx| match event {
1584 BreakpointStoreEvent::ActiveDebugLineChanged => {
1585 if editor.go_to_active_debug_line(window, cx) {
1586 cx.stop_propagation();
1587 }
1588
1589 editor.refresh_inline_values(cx);
1590 }
1591 _ => {}
1592 },
1593 ));
1594 }
1595 }
1596
1597 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1598
1599 let inlay_hint_settings =
1600 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1601 let focus_handle = cx.focus_handle();
1602 cx.on_focus(&focus_handle, window, Self::handle_focus)
1603 .detach();
1604 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1605 .detach();
1606 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1607 .detach();
1608 cx.on_blur(&focus_handle, window, Self::handle_blur)
1609 .detach();
1610
1611 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1612 Some(false)
1613 } else {
1614 None
1615 };
1616
1617 let breakpoint_store = match (mode, project.as_ref()) {
1618 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1619 _ => None,
1620 };
1621
1622 let mut code_action_providers = Vec::new();
1623 let mut load_uncommitted_diff = None;
1624 if let Some(project) = project.clone() {
1625 load_uncommitted_diff = Some(
1626 get_uncommitted_diff_for_buffer(
1627 &project,
1628 buffer.read(cx).all_buffers(),
1629 buffer.clone(),
1630 cx,
1631 )
1632 .shared(),
1633 );
1634 code_action_providers.push(Rc::new(project) as Rc<_>);
1635 }
1636
1637 let mut this = Self {
1638 focus_handle,
1639 show_cursor_when_unfocused: false,
1640 last_focused_descendant: None,
1641 buffer: buffer.clone(),
1642 display_map: display_map.clone(),
1643 selections,
1644 scroll_manager: ScrollManager::new(cx),
1645 columnar_selection_tail: None,
1646 add_selections_state: None,
1647 select_next_state: None,
1648 select_prev_state: None,
1649 selection_history: Default::default(),
1650 autoclose_regions: Default::default(),
1651 snippet_stack: Default::default(),
1652 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1653 ime_transaction: Default::default(),
1654 active_diagnostics: ActiveDiagnostic::None,
1655 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1656 inline_diagnostics_update: Task::ready(()),
1657 inline_diagnostics: Vec::new(),
1658 soft_wrap_mode_override,
1659 hard_wrap: None,
1660 completion_provider: project.clone().map(|project| Box::new(project) as _),
1661 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1662 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1663 project,
1664 blink_manager: blink_manager.clone(),
1665 show_local_selections: true,
1666 show_scrollbars: true,
1667 disable_scrolling: false,
1668 mode,
1669 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1670 show_gutter: mode.is_full(),
1671 show_line_numbers: None,
1672 use_relative_line_numbers: None,
1673 disable_expand_excerpt_buttons: false,
1674 show_git_diff_gutter: None,
1675 show_code_actions: None,
1676 show_runnables: None,
1677 show_breakpoints: None,
1678 show_wrap_guides: None,
1679 show_indent_guides,
1680 placeholder_text: None,
1681 highlight_order: 0,
1682 highlighted_rows: HashMap::default(),
1683 background_highlights: Default::default(),
1684 gutter_highlights: TreeMap::default(),
1685 scrollbar_marker_state: ScrollbarMarkerState::default(),
1686 active_indent_guides_state: ActiveIndentGuidesState::default(),
1687 nav_history: None,
1688 context_menu: RefCell::new(None),
1689 context_menu_options: None,
1690 mouse_context_menu: None,
1691 completion_tasks: Default::default(),
1692 inline_blame_popover: Default::default(),
1693 signature_help_state: SignatureHelpState::default(),
1694 auto_signature_help: None,
1695 find_all_references_task_sources: Vec::new(),
1696 next_completion_id: 0,
1697 next_inlay_id: 0,
1698 code_action_providers,
1699 available_code_actions: Default::default(),
1700 code_actions_task: Default::default(),
1701 quick_selection_highlight_task: Default::default(),
1702 debounced_selection_highlight_task: Default::default(),
1703 document_highlights_task: Default::default(),
1704 linked_editing_range_task: Default::default(),
1705 pending_rename: Default::default(),
1706 searchable: true,
1707 cursor_shape: EditorSettings::get_global(cx)
1708 .cursor_shape
1709 .unwrap_or_default(),
1710 current_line_highlight: None,
1711 autoindent_mode: Some(AutoindentMode::EachLine),
1712 collapse_matches: false,
1713 workspace: None,
1714 input_enabled: true,
1715 use_modal_editing: mode.is_full(),
1716 read_only: false,
1717 use_autoclose: true,
1718 use_auto_surround: true,
1719 auto_replace_emoji_shortcode: false,
1720 jsx_tag_auto_close_enabled_in_any_buffer: false,
1721 leader_peer_id: None,
1722 remote_id: None,
1723 hover_state: Default::default(),
1724 pending_mouse_down: None,
1725 hovered_link_state: Default::default(),
1726 edit_prediction_provider: None,
1727 active_inline_completion: None,
1728 stale_inline_completion_in_menu: None,
1729 edit_prediction_preview: EditPredictionPreview::Inactive {
1730 released_too_fast: false,
1731 },
1732 inline_diagnostics_enabled: mode.is_full(),
1733 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1734 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1735
1736 gutter_hovered: false,
1737 pixel_position_of_newest_cursor: None,
1738 last_bounds: None,
1739 last_position_map: None,
1740 expect_bounds_change: None,
1741 gutter_dimensions: GutterDimensions::default(),
1742 style: None,
1743 show_cursor_names: false,
1744 hovered_cursors: Default::default(),
1745 next_editor_action_id: EditorActionId::default(),
1746 editor_actions: Rc::default(),
1747 inline_completions_hidden_for_vim_mode: false,
1748 show_inline_completions_override: None,
1749 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1750 edit_prediction_settings: EditPredictionSettings::Disabled,
1751 edit_prediction_indent_conflict: false,
1752 edit_prediction_requires_modifier_in_indent_conflict: true,
1753 custom_context_menu: None,
1754 show_git_blame_gutter: false,
1755 show_git_blame_inline: false,
1756 show_selection_menu: None,
1757 show_git_blame_inline_delay_task: None,
1758 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1759 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1760 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1761 .session
1762 .restore_unsaved_buffers,
1763 blame: None,
1764 blame_subscription: None,
1765 tasks: Default::default(),
1766
1767 breakpoint_store,
1768 gutter_breakpoint_indicator: (None, None),
1769 _subscriptions: vec![
1770 cx.observe(&buffer, Self::on_buffer_changed),
1771 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1772 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1773 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1774 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1775 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1776 cx.observe_window_activation(window, |editor, window, cx| {
1777 let active = window.is_window_active();
1778 editor.blink_manager.update(cx, |blink_manager, cx| {
1779 if active {
1780 blink_manager.enable(cx);
1781 } else {
1782 blink_manager.disable(cx);
1783 }
1784 });
1785 }),
1786 ],
1787 tasks_update_task: None,
1788 linked_edit_ranges: Default::default(),
1789 in_project_search: false,
1790 previous_search_ranges: None,
1791 breadcrumb_header: None,
1792 focused_block: None,
1793 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1794 addons: HashMap::default(),
1795 registered_buffers: HashMap::default(),
1796 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1797 selection_mark_mode: false,
1798 toggle_fold_multiple_buffers: Task::ready(()),
1799 serialize_selections: Task::ready(()),
1800 serialize_folds: Task::ready(()),
1801 text_style_refinement: None,
1802 load_diff_task: load_uncommitted_diff,
1803 mouse_cursor_hidden: false,
1804 hide_mouse_mode: EditorSettings::get_global(cx)
1805 .hide_mouse
1806 .unwrap_or_default(),
1807 change_list: ChangeList::new(),
1808 };
1809 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1810 this._subscriptions
1811 .push(cx.observe(breakpoints, |_, _, cx| {
1812 cx.notify();
1813 }));
1814 }
1815 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1816 this._subscriptions.extend(project_subscriptions);
1817
1818 this._subscriptions.push(cx.subscribe_in(
1819 &cx.entity(),
1820 window,
1821 |editor, _, e: &EditorEvent, window, cx| match e {
1822 EditorEvent::ScrollPositionChanged { local, .. } => {
1823 if *local {
1824 let new_anchor = editor.scroll_manager.anchor();
1825 let snapshot = editor.snapshot(window, cx);
1826 editor.update_restoration_data(cx, move |data| {
1827 data.scroll_position = (
1828 new_anchor.top_row(&snapshot.buffer_snapshot),
1829 new_anchor.offset,
1830 );
1831 });
1832 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1833 editor.inline_blame_popover.take();
1834 }
1835 }
1836 EditorEvent::Edited { .. } => {
1837 if !vim_enabled(cx) {
1838 let (map, selections) = editor.selections.all_adjusted_display(cx);
1839 let pop_state = editor
1840 .change_list
1841 .last()
1842 .map(|previous| {
1843 previous.len() == selections.len()
1844 && previous.iter().enumerate().all(|(ix, p)| {
1845 p.to_display_point(&map).row()
1846 == selections[ix].head().row()
1847 })
1848 })
1849 .unwrap_or(false);
1850 let new_positions = selections
1851 .into_iter()
1852 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1853 .collect();
1854 editor
1855 .change_list
1856 .push_to_change_list(pop_state, new_positions);
1857 }
1858 }
1859 _ => (),
1860 },
1861 ));
1862
1863 if let Some(dap_store) = this
1864 .project
1865 .as_ref()
1866 .map(|project| project.read(cx).dap_store())
1867 {
1868 let weak_editor = cx.weak_entity();
1869
1870 this._subscriptions
1871 .push(
1872 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1873 let session_entity = cx.entity();
1874 weak_editor
1875 .update(cx, |editor, cx| {
1876 editor._subscriptions.push(
1877 cx.subscribe(&session_entity, Self::on_debug_session_event),
1878 );
1879 })
1880 .ok();
1881 }),
1882 );
1883
1884 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1885 this._subscriptions
1886 .push(cx.subscribe(&session, Self::on_debug_session_event));
1887 }
1888 }
1889
1890 this.end_selection(window, cx);
1891 this.scroll_manager.show_scrollbars(window, cx);
1892 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1893
1894 if mode.is_full() {
1895 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1896 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1897
1898 if this.git_blame_inline_enabled {
1899 this.git_blame_inline_enabled = true;
1900 this.start_git_blame_inline(false, window, cx);
1901 }
1902
1903 this.go_to_active_debug_line(window, cx);
1904
1905 if let Some(buffer) = buffer.read(cx).as_singleton() {
1906 if let Some(project) = this.project.as_ref() {
1907 let handle = project.update(cx, |project, cx| {
1908 project.register_buffer_with_language_servers(&buffer, cx)
1909 });
1910 this.registered_buffers
1911 .insert(buffer.read(cx).remote_id(), handle);
1912 }
1913 }
1914 }
1915
1916 this.report_editor_event("Editor Opened", None, cx);
1917 this
1918 }
1919
1920 pub fn deploy_mouse_context_menu(
1921 &mut self,
1922 position: gpui::Point<Pixels>,
1923 context_menu: Entity<ContextMenu>,
1924 window: &mut Window,
1925 cx: &mut Context<Self>,
1926 ) {
1927 self.mouse_context_menu = Some(MouseContextMenu::new(
1928 self,
1929 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1930 context_menu,
1931 window,
1932 cx,
1933 ));
1934 }
1935
1936 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1937 self.mouse_context_menu
1938 .as_ref()
1939 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1940 }
1941
1942 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1943 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1944 }
1945
1946 fn key_context_internal(
1947 &self,
1948 has_active_edit_prediction: bool,
1949 window: &Window,
1950 cx: &App,
1951 ) -> KeyContext {
1952 let mut key_context = KeyContext::new_with_defaults();
1953 key_context.add("Editor");
1954 let mode = match self.mode {
1955 EditorMode::SingleLine { .. } => "single_line",
1956 EditorMode::AutoHeight { .. } => "auto_height",
1957 EditorMode::Full { .. } => "full",
1958 };
1959
1960 if EditorSettings::jupyter_enabled(cx) {
1961 key_context.add("jupyter");
1962 }
1963
1964 key_context.set("mode", mode);
1965 if self.pending_rename.is_some() {
1966 key_context.add("renaming");
1967 }
1968
1969 match self.context_menu.borrow().as_ref() {
1970 Some(CodeContextMenu::Completions(_)) => {
1971 key_context.add("menu");
1972 key_context.add("showing_completions");
1973 }
1974 Some(CodeContextMenu::CodeActions(_)) => {
1975 key_context.add("menu");
1976 key_context.add("showing_code_actions")
1977 }
1978 None => {}
1979 }
1980
1981 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1982 if !self.focus_handle(cx).contains_focused(window, cx)
1983 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1984 {
1985 for addon in self.addons.values() {
1986 addon.extend_key_context(&mut key_context, cx)
1987 }
1988 }
1989
1990 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1991 if let Some(extension) = singleton_buffer
1992 .read(cx)
1993 .file()
1994 .and_then(|file| file.path().extension()?.to_str())
1995 {
1996 key_context.set("extension", extension.to_string());
1997 }
1998 } else {
1999 key_context.add("multibuffer");
2000 }
2001
2002 if has_active_edit_prediction {
2003 if self.edit_prediction_in_conflict() {
2004 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2005 } else {
2006 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2007 key_context.add("copilot_suggestion");
2008 }
2009 }
2010
2011 if self.selection_mark_mode {
2012 key_context.add("selection_mode");
2013 }
2014
2015 key_context
2016 }
2017
2018 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2019 self.mouse_cursor_hidden = match origin {
2020 HideMouseCursorOrigin::TypingAction => {
2021 matches!(
2022 self.hide_mouse_mode,
2023 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2024 )
2025 }
2026 HideMouseCursorOrigin::MovementAction => {
2027 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2028 }
2029 };
2030 }
2031
2032 pub fn edit_prediction_in_conflict(&self) -> bool {
2033 if !self.show_edit_predictions_in_menu() {
2034 return false;
2035 }
2036
2037 let showing_completions = self
2038 .context_menu
2039 .borrow()
2040 .as_ref()
2041 .map_or(false, |context| {
2042 matches!(context, CodeContextMenu::Completions(_))
2043 });
2044
2045 showing_completions
2046 || self.edit_prediction_requires_modifier()
2047 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2048 // bindings to insert tab characters.
2049 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2050 }
2051
2052 pub fn accept_edit_prediction_keybind(
2053 &self,
2054 window: &Window,
2055 cx: &App,
2056 ) -> AcceptEditPredictionBinding {
2057 let key_context = self.key_context_internal(true, window, cx);
2058 let in_conflict = self.edit_prediction_in_conflict();
2059
2060 AcceptEditPredictionBinding(
2061 window
2062 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2063 .into_iter()
2064 .filter(|binding| {
2065 !in_conflict
2066 || binding
2067 .keystrokes()
2068 .first()
2069 .map_or(false, |keystroke| keystroke.modifiers.modified())
2070 })
2071 .rev()
2072 .min_by_key(|binding| {
2073 binding
2074 .keystrokes()
2075 .first()
2076 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2077 }),
2078 )
2079 }
2080
2081 pub fn new_file(
2082 workspace: &mut Workspace,
2083 _: &workspace::NewFile,
2084 window: &mut Window,
2085 cx: &mut Context<Workspace>,
2086 ) {
2087 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2088 "Failed to create buffer",
2089 window,
2090 cx,
2091 |e, _, _| match e.error_code() {
2092 ErrorCode::RemoteUpgradeRequired => Some(format!(
2093 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2094 e.error_tag("required").unwrap_or("the latest version")
2095 )),
2096 _ => None,
2097 },
2098 );
2099 }
2100
2101 pub fn new_in_workspace(
2102 workspace: &mut Workspace,
2103 window: &mut Window,
2104 cx: &mut Context<Workspace>,
2105 ) -> Task<Result<Entity<Editor>>> {
2106 let project = workspace.project().clone();
2107 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2108
2109 cx.spawn_in(window, async move |workspace, cx| {
2110 let buffer = create.await?;
2111 workspace.update_in(cx, |workspace, window, cx| {
2112 let editor =
2113 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2114 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2115 editor
2116 })
2117 })
2118 }
2119
2120 fn new_file_vertical(
2121 workspace: &mut Workspace,
2122 _: &workspace::NewFileSplitVertical,
2123 window: &mut Window,
2124 cx: &mut Context<Workspace>,
2125 ) {
2126 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2127 }
2128
2129 fn new_file_horizontal(
2130 workspace: &mut Workspace,
2131 _: &workspace::NewFileSplitHorizontal,
2132 window: &mut Window,
2133 cx: &mut Context<Workspace>,
2134 ) {
2135 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2136 }
2137
2138 fn new_file_in_direction(
2139 workspace: &mut Workspace,
2140 direction: SplitDirection,
2141 window: &mut Window,
2142 cx: &mut Context<Workspace>,
2143 ) {
2144 let project = workspace.project().clone();
2145 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2146
2147 cx.spawn_in(window, async move |workspace, cx| {
2148 let buffer = create.await?;
2149 workspace.update_in(cx, move |workspace, window, cx| {
2150 workspace.split_item(
2151 direction,
2152 Box::new(
2153 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2154 ),
2155 window,
2156 cx,
2157 )
2158 })?;
2159 anyhow::Ok(())
2160 })
2161 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2162 match e.error_code() {
2163 ErrorCode::RemoteUpgradeRequired => Some(format!(
2164 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2165 e.error_tag("required").unwrap_or("the latest version")
2166 )),
2167 _ => None,
2168 }
2169 });
2170 }
2171
2172 pub fn leader_peer_id(&self) -> Option<PeerId> {
2173 self.leader_peer_id
2174 }
2175
2176 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2177 &self.buffer
2178 }
2179
2180 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2181 self.workspace.as_ref()?.0.upgrade()
2182 }
2183
2184 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2185 self.buffer().read(cx).title(cx)
2186 }
2187
2188 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2189 let git_blame_gutter_max_author_length = self
2190 .render_git_blame_gutter(cx)
2191 .then(|| {
2192 if let Some(blame) = self.blame.as_ref() {
2193 let max_author_length =
2194 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2195 Some(max_author_length)
2196 } else {
2197 None
2198 }
2199 })
2200 .flatten();
2201
2202 EditorSnapshot {
2203 mode: self.mode,
2204 show_gutter: self.show_gutter,
2205 show_line_numbers: self.show_line_numbers,
2206 show_git_diff_gutter: self.show_git_diff_gutter,
2207 show_code_actions: self.show_code_actions,
2208 show_runnables: self.show_runnables,
2209 show_breakpoints: self.show_breakpoints,
2210 git_blame_gutter_max_author_length,
2211 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2212 scroll_anchor: self.scroll_manager.anchor(),
2213 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2214 placeholder_text: self.placeholder_text.clone(),
2215 is_focused: self.focus_handle.is_focused(window),
2216 current_line_highlight: self
2217 .current_line_highlight
2218 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2219 gutter_hovered: self.gutter_hovered,
2220 }
2221 }
2222
2223 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2224 self.buffer.read(cx).language_at(point, cx)
2225 }
2226
2227 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2228 self.buffer.read(cx).read(cx).file_at(point).cloned()
2229 }
2230
2231 pub fn active_excerpt(
2232 &self,
2233 cx: &App,
2234 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2235 self.buffer
2236 .read(cx)
2237 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2238 }
2239
2240 pub fn mode(&self) -> EditorMode {
2241 self.mode
2242 }
2243
2244 pub fn set_mode(&mut self, mode: EditorMode) {
2245 self.mode = mode;
2246 }
2247
2248 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2249 self.collaboration_hub.as_deref()
2250 }
2251
2252 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2253 self.collaboration_hub = Some(hub);
2254 }
2255
2256 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2257 self.in_project_search = in_project_search;
2258 }
2259
2260 pub fn set_custom_context_menu(
2261 &mut self,
2262 f: impl 'static
2263 + Fn(
2264 &mut Self,
2265 DisplayPoint,
2266 &mut Window,
2267 &mut Context<Self>,
2268 ) -> Option<Entity<ui::ContextMenu>>,
2269 ) {
2270 self.custom_context_menu = Some(Box::new(f))
2271 }
2272
2273 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2274 self.completion_provider = provider;
2275 }
2276
2277 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2278 self.semantics_provider.clone()
2279 }
2280
2281 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2282 self.semantics_provider = provider;
2283 }
2284
2285 pub fn set_edit_prediction_provider<T>(
2286 &mut self,
2287 provider: Option<Entity<T>>,
2288 window: &mut Window,
2289 cx: &mut Context<Self>,
2290 ) where
2291 T: EditPredictionProvider,
2292 {
2293 self.edit_prediction_provider =
2294 provider.map(|provider| RegisteredInlineCompletionProvider {
2295 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2296 if this.focus_handle.is_focused(window) {
2297 this.update_visible_inline_completion(window, cx);
2298 }
2299 }),
2300 provider: Arc::new(provider),
2301 });
2302 self.update_edit_prediction_settings(cx);
2303 self.refresh_inline_completion(false, false, window, cx);
2304 }
2305
2306 pub fn placeholder_text(&self) -> Option<&str> {
2307 self.placeholder_text.as_deref()
2308 }
2309
2310 pub fn set_placeholder_text(
2311 &mut self,
2312 placeholder_text: impl Into<Arc<str>>,
2313 cx: &mut Context<Self>,
2314 ) {
2315 let placeholder_text = Some(placeholder_text.into());
2316 if self.placeholder_text != placeholder_text {
2317 self.placeholder_text = placeholder_text;
2318 cx.notify();
2319 }
2320 }
2321
2322 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2323 self.cursor_shape = cursor_shape;
2324
2325 // Disrupt blink for immediate user feedback that the cursor shape has changed
2326 self.blink_manager.update(cx, BlinkManager::show_cursor);
2327
2328 cx.notify();
2329 }
2330
2331 pub fn set_current_line_highlight(
2332 &mut self,
2333 current_line_highlight: Option<CurrentLineHighlight>,
2334 ) {
2335 self.current_line_highlight = current_line_highlight;
2336 }
2337
2338 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2339 self.collapse_matches = collapse_matches;
2340 }
2341
2342 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2343 let buffers = self.buffer.read(cx).all_buffers();
2344 let Some(project) = self.project.as_ref() else {
2345 return;
2346 };
2347 project.update(cx, |project, cx| {
2348 for buffer in buffers {
2349 self.registered_buffers
2350 .entry(buffer.read(cx).remote_id())
2351 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2352 }
2353 })
2354 }
2355
2356 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2357 if self.collapse_matches {
2358 return range.start..range.start;
2359 }
2360 range.clone()
2361 }
2362
2363 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2364 if self.display_map.read(cx).clip_at_line_ends != clip {
2365 self.display_map
2366 .update(cx, |map, _| map.clip_at_line_ends = clip);
2367 }
2368 }
2369
2370 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2371 self.input_enabled = input_enabled;
2372 }
2373
2374 pub fn set_inline_completions_hidden_for_vim_mode(
2375 &mut self,
2376 hidden: bool,
2377 window: &mut Window,
2378 cx: &mut Context<Self>,
2379 ) {
2380 if hidden != self.inline_completions_hidden_for_vim_mode {
2381 self.inline_completions_hidden_for_vim_mode = hidden;
2382 if hidden {
2383 self.update_visible_inline_completion(window, cx);
2384 } else {
2385 self.refresh_inline_completion(true, false, window, cx);
2386 }
2387 }
2388 }
2389
2390 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2391 self.menu_inline_completions_policy = value;
2392 }
2393
2394 pub fn set_autoindent(&mut self, autoindent: bool) {
2395 if autoindent {
2396 self.autoindent_mode = Some(AutoindentMode::EachLine);
2397 } else {
2398 self.autoindent_mode = None;
2399 }
2400 }
2401
2402 pub fn read_only(&self, cx: &App) -> bool {
2403 self.read_only || self.buffer.read(cx).read_only()
2404 }
2405
2406 pub fn set_read_only(&mut self, read_only: bool) {
2407 self.read_only = read_only;
2408 }
2409
2410 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2411 self.use_autoclose = autoclose;
2412 }
2413
2414 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2415 self.use_auto_surround = auto_surround;
2416 }
2417
2418 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2419 self.auto_replace_emoji_shortcode = auto_replace;
2420 }
2421
2422 pub fn toggle_edit_predictions(
2423 &mut self,
2424 _: &ToggleEditPrediction,
2425 window: &mut Window,
2426 cx: &mut Context<Self>,
2427 ) {
2428 if self.show_inline_completions_override.is_some() {
2429 self.set_show_edit_predictions(None, window, cx);
2430 } else {
2431 let show_edit_predictions = !self.edit_predictions_enabled();
2432 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2433 }
2434 }
2435
2436 pub fn set_show_edit_predictions(
2437 &mut self,
2438 show_edit_predictions: Option<bool>,
2439 window: &mut Window,
2440 cx: &mut Context<Self>,
2441 ) {
2442 self.show_inline_completions_override = show_edit_predictions;
2443 self.update_edit_prediction_settings(cx);
2444
2445 if let Some(false) = show_edit_predictions {
2446 self.discard_inline_completion(false, cx);
2447 } else {
2448 self.refresh_inline_completion(false, true, window, cx);
2449 }
2450 }
2451
2452 fn inline_completions_disabled_in_scope(
2453 &self,
2454 buffer: &Entity<Buffer>,
2455 buffer_position: language::Anchor,
2456 cx: &App,
2457 ) -> bool {
2458 let snapshot = buffer.read(cx).snapshot();
2459 let settings = snapshot.settings_at(buffer_position, cx);
2460
2461 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2462 return false;
2463 };
2464
2465 scope.override_name().map_or(false, |scope_name| {
2466 settings
2467 .edit_predictions_disabled_in
2468 .iter()
2469 .any(|s| s == scope_name)
2470 })
2471 }
2472
2473 pub fn set_use_modal_editing(&mut self, to: bool) {
2474 self.use_modal_editing = to;
2475 }
2476
2477 pub fn use_modal_editing(&self) -> bool {
2478 self.use_modal_editing
2479 }
2480
2481 fn selections_did_change(
2482 &mut self,
2483 local: bool,
2484 old_cursor_position: &Anchor,
2485 show_completions: bool,
2486 window: &mut Window,
2487 cx: &mut Context<Self>,
2488 ) {
2489 window.invalidate_character_coordinates();
2490
2491 // Copy selections to primary selection buffer
2492 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2493 if local {
2494 let selections = self.selections.all::<usize>(cx);
2495 let buffer_handle = self.buffer.read(cx).read(cx);
2496
2497 let mut text = String::new();
2498 for (index, selection) in selections.iter().enumerate() {
2499 let text_for_selection = buffer_handle
2500 .text_for_range(selection.start..selection.end)
2501 .collect::<String>();
2502
2503 text.push_str(&text_for_selection);
2504 if index != selections.len() - 1 {
2505 text.push('\n');
2506 }
2507 }
2508
2509 if !text.is_empty() {
2510 cx.write_to_primary(ClipboardItem::new_string(text));
2511 }
2512 }
2513
2514 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2515 self.buffer.update(cx, |buffer, cx| {
2516 buffer.set_active_selections(
2517 &self.selections.disjoint_anchors(),
2518 self.selections.line_mode,
2519 self.cursor_shape,
2520 cx,
2521 )
2522 });
2523 }
2524 let display_map = self
2525 .display_map
2526 .update(cx, |display_map, cx| display_map.snapshot(cx));
2527 let buffer = &display_map.buffer_snapshot;
2528 self.add_selections_state = None;
2529 self.select_next_state = None;
2530 self.select_prev_state = None;
2531 self.select_syntax_node_history.try_clear();
2532 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2533 self.snippet_stack
2534 .invalidate(&self.selections.disjoint_anchors(), buffer);
2535 self.take_rename(false, window, cx);
2536
2537 let new_cursor_position = self.selections.newest_anchor().head();
2538
2539 self.push_to_nav_history(
2540 *old_cursor_position,
2541 Some(new_cursor_position.to_point(buffer)),
2542 false,
2543 cx,
2544 );
2545
2546 if local {
2547 let new_cursor_position = self.selections.newest_anchor().head();
2548 let mut context_menu = self.context_menu.borrow_mut();
2549 let completion_menu = match context_menu.as_ref() {
2550 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2551 _ => {
2552 *context_menu = None;
2553 None
2554 }
2555 };
2556 if let Some(buffer_id) = new_cursor_position.buffer_id {
2557 if !self.registered_buffers.contains_key(&buffer_id) {
2558 if let Some(project) = self.project.as_ref() {
2559 project.update(cx, |project, cx| {
2560 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2561 return;
2562 };
2563 self.registered_buffers.insert(
2564 buffer_id,
2565 project.register_buffer_with_language_servers(&buffer, cx),
2566 );
2567 })
2568 }
2569 }
2570 }
2571
2572 if let Some(completion_menu) = completion_menu {
2573 let cursor_position = new_cursor_position.to_offset(buffer);
2574 let (word_range, kind) =
2575 buffer.surrounding_word(completion_menu.initial_position, true);
2576 if kind == Some(CharKind::Word)
2577 && word_range.to_inclusive().contains(&cursor_position)
2578 {
2579 let mut completion_menu = completion_menu.clone();
2580 drop(context_menu);
2581
2582 let query = Self::completion_query(buffer, cursor_position);
2583 cx.spawn(async move |this, cx| {
2584 completion_menu
2585 .filter(query.as_deref(), cx.background_executor().clone())
2586 .await;
2587
2588 this.update(cx, |this, cx| {
2589 let mut context_menu = this.context_menu.borrow_mut();
2590 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2591 else {
2592 return;
2593 };
2594
2595 if menu.id > completion_menu.id {
2596 return;
2597 }
2598
2599 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2600 drop(context_menu);
2601 cx.notify();
2602 })
2603 })
2604 .detach();
2605
2606 if show_completions {
2607 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2608 }
2609 } else {
2610 drop(context_menu);
2611 self.hide_context_menu(window, cx);
2612 }
2613 } else {
2614 drop(context_menu);
2615 }
2616
2617 hide_hover(self, cx);
2618
2619 if old_cursor_position.to_display_point(&display_map).row()
2620 != new_cursor_position.to_display_point(&display_map).row()
2621 {
2622 self.available_code_actions.take();
2623 }
2624 self.refresh_code_actions(window, cx);
2625 self.refresh_document_highlights(cx);
2626 self.refresh_selected_text_highlights(false, window, cx);
2627 refresh_matching_bracket_highlights(self, window, cx);
2628 self.update_visible_inline_completion(window, cx);
2629 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2630 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2631 self.inline_blame_popover.take();
2632 if self.git_blame_inline_enabled {
2633 self.start_inline_blame_timer(window, cx);
2634 }
2635 }
2636
2637 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2638 cx.emit(EditorEvent::SelectionsChanged { local });
2639
2640 let selections = &self.selections.disjoint;
2641 if selections.len() == 1 {
2642 cx.emit(SearchEvent::ActiveMatchChanged)
2643 }
2644 if local {
2645 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2646 let inmemory_selections = selections
2647 .iter()
2648 .map(|s| {
2649 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2650 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2651 })
2652 .collect();
2653 self.update_restoration_data(cx, |data| {
2654 data.selections = inmemory_selections;
2655 });
2656
2657 if WorkspaceSettings::get(None, cx).restore_on_startup
2658 != RestoreOnStartupBehavior::None
2659 {
2660 if let Some(workspace_id) =
2661 self.workspace.as_ref().and_then(|workspace| workspace.1)
2662 {
2663 let snapshot = self.buffer().read(cx).snapshot(cx);
2664 let selections = selections.clone();
2665 let background_executor = cx.background_executor().clone();
2666 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2667 self.serialize_selections = cx.background_spawn(async move {
2668 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2669 let db_selections = selections
2670 .iter()
2671 .map(|selection| {
2672 (
2673 selection.start.to_offset(&snapshot),
2674 selection.end.to_offset(&snapshot),
2675 )
2676 })
2677 .collect();
2678
2679 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2680 .await
2681 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2682 .log_err();
2683 });
2684 }
2685 }
2686 }
2687 }
2688
2689 cx.notify();
2690 }
2691
2692 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2693 use text::ToOffset as _;
2694 use text::ToPoint as _;
2695
2696 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2697 return;
2698 }
2699
2700 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2701 return;
2702 };
2703
2704 let snapshot = singleton.read(cx).snapshot();
2705 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2706 let display_snapshot = display_map.snapshot(cx);
2707
2708 display_snapshot
2709 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2710 .map(|fold| {
2711 fold.range.start.text_anchor.to_point(&snapshot)
2712 ..fold.range.end.text_anchor.to_point(&snapshot)
2713 })
2714 .collect()
2715 });
2716 self.update_restoration_data(cx, |data| {
2717 data.folds = inmemory_folds;
2718 });
2719
2720 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2721 return;
2722 };
2723 let background_executor = cx.background_executor().clone();
2724 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2725 let db_folds = self.display_map.update(cx, |display_map, cx| {
2726 display_map
2727 .snapshot(cx)
2728 .folds_in_range(0..snapshot.len())
2729 .map(|fold| {
2730 (
2731 fold.range.start.text_anchor.to_offset(&snapshot),
2732 fold.range.end.text_anchor.to_offset(&snapshot),
2733 )
2734 })
2735 .collect()
2736 });
2737 self.serialize_folds = cx.background_spawn(async move {
2738 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2739 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2740 .await
2741 .with_context(|| {
2742 format!(
2743 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2744 )
2745 })
2746 .log_err();
2747 });
2748 }
2749
2750 pub fn sync_selections(
2751 &mut self,
2752 other: Entity<Editor>,
2753 cx: &mut Context<Self>,
2754 ) -> gpui::Subscription {
2755 let other_selections = other.read(cx).selections.disjoint.to_vec();
2756 self.selections.change_with(cx, |selections| {
2757 selections.select_anchors(other_selections);
2758 });
2759
2760 let other_subscription =
2761 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2762 EditorEvent::SelectionsChanged { local: true } => {
2763 let other_selections = other.read(cx).selections.disjoint.to_vec();
2764 if other_selections.is_empty() {
2765 return;
2766 }
2767 this.selections.change_with(cx, |selections| {
2768 selections.select_anchors(other_selections);
2769 });
2770 }
2771 _ => {}
2772 });
2773
2774 let this_subscription =
2775 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2776 EditorEvent::SelectionsChanged { local: true } => {
2777 let these_selections = this.selections.disjoint.to_vec();
2778 if these_selections.is_empty() {
2779 return;
2780 }
2781 other.update(cx, |other_editor, cx| {
2782 other_editor.selections.change_with(cx, |selections| {
2783 selections.select_anchors(these_selections);
2784 })
2785 });
2786 }
2787 _ => {}
2788 });
2789
2790 Subscription::join(other_subscription, this_subscription)
2791 }
2792
2793 pub fn change_selections<R>(
2794 &mut self,
2795 autoscroll: Option<Autoscroll>,
2796 window: &mut Window,
2797 cx: &mut Context<Self>,
2798 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2799 ) -> R {
2800 self.change_selections_inner(autoscroll, true, window, cx, change)
2801 }
2802
2803 fn change_selections_inner<R>(
2804 &mut self,
2805 autoscroll: Option<Autoscroll>,
2806 request_completions: bool,
2807 window: &mut Window,
2808 cx: &mut Context<Self>,
2809 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2810 ) -> R {
2811 let old_cursor_position = self.selections.newest_anchor().head();
2812 self.push_to_selection_history();
2813
2814 let (changed, result) = self.selections.change_with(cx, change);
2815
2816 if changed {
2817 if let Some(autoscroll) = autoscroll {
2818 self.request_autoscroll(autoscroll, cx);
2819 }
2820 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2821
2822 if self.should_open_signature_help_automatically(
2823 &old_cursor_position,
2824 self.signature_help_state.backspace_pressed(),
2825 cx,
2826 ) {
2827 self.show_signature_help(&ShowSignatureHelp, window, cx);
2828 }
2829 self.signature_help_state.set_backspace_pressed(false);
2830 }
2831
2832 result
2833 }
2834
2835 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2836 where
2837 I: IntoIterator<Item = (Range<S>, T)>,
2838 S: ToOffset,
2839 T: Into<Arc<str>>,
2840 {
2841 if self.read_only(cx) {
2842 return;
2843 }
2844
2845 self.buffer
2846 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2847 }
2848
2849 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2850 where
2851 I: IntoIterator<Item = (Range<S>, T)>,
2852 S: ToOffset,
2853 T: Into<Arc<str>>,
2854 {
2855 if self.read_only(cx) {
2856 return;
2857 }
2858
2859 self.buffer.update(cx, |buffer, cx| {
2860 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2861 });
2862 }
2863
2864 pub fn edit_with_block_indent<I, S, T>(
2865 &mut self,
2866 edits: I,
2867 original_indent_columns: Vec<Option<u32>>,
2868 cx: &mut Context<Self>,
2869 ) where
2870 I: IntoIterator<Item = (Range<S>, T)>,
2871 S: ToOffset,
2872 T: Into<Arc<str>>,
2873 {
2874 if self.read_only(cx) {
2875 return;
2876 }
2877
2878 self.buffer.update(cx, |buffer, cx| {
2879 buffer.edit(
2880 edits,
2881 Some(AutoindentMode::Block {
2882 original_indent_columns,
2883 }),
2884 cx,
2885 )
2886 });
2887 }
2888
2889 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2890 self.hide_context_menu(window, cx);
2891
2892 match phase {
2893 SelectPhase::Begin {
2894 position,
2895 add,
2896 click_count,
2897 } => self.begin_selection(position, add, click_count, window, cx),
2898 SelectPhase::BeginColumnar {
2899 position,
2900 goal_column,
2901 reset,
2902 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2903 SelectPhase::Extend {
2904 position,
2905 click_count,
2906 } => self.extend_selection(position, click_count, window, cx),
2907 SelectPhase::Update {
2908 position,
2909 goal_column,
2910 scroll_delta,
2911 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2912 SelectPhase::End => self.end_selection(window, cx),
2913 }
2914 }
2915
2916 fn extend_selection(
2917 &mut self,
2918 position: DisplayPoint,
2919 click_count: usize,
2920 window: &mut Window,
2921 cx: &mut Context<Self>,
2922 ) {
2923 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2924 let tail = self.selections.newest::<usize>(cx).tail();
2925 self.begin_selection(position, false, click_count, window, cx);
2926
2927 let position = position.to_offset(&display_map, Bias::Left);
2928 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2929
2930 let mut pending_selection = self
2931 .selections
2932 .pending_anchor()
2933 .expect("extend_selection not called with pending selection");
2934 if position >= tail {
2935 pending_selection.start = tail_anchor;
2936 } else {
2937 pending_selection.end = tail_anchor;
2938 pending_selection.reversed = true;
2939 }
2940
2941 let mut pending_mode = self.selections.pending_mode().unwrap();
2942 match &mut pending_mode {
2943 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2944 _ => {}
2945 }
2946
2947 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2948 s.set_pending(pending_selection, pending_mode)
2949 });
2950 }
2951
2952 fn begin_selection(
2953 &mut self,
2954 position: DisplayPoint,
2955 add: bool,
2956 click_count: usize,
2957 window: &mut Window,
2958 cx: &mut Context<Self>,
2959 ) {
2960 if !self.focus_handle.is_focused(window) {
2961 self.last_focused_descendant = None;
2962 window.focus(&self.focus_handle);
2963 }
2964
2965 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2966 let buffer = &display_map.buffer_snapshot;
2967 let newest_selection = self.selections.newest_anchor().clone();
2968 let position = display_map.clip_point(position, Bias::Left);
2969
2970 let start;
2971 let end;
2972 let mode;
2973 let mut auto_scroll;
2974 match click_count {
2975 1 => {
2976 start = buffer.anchor_before(position.to_point(&display_map));
2977 end = start;
2978 mode = SelectMode::Character;
2979 auto_scroll = true;
2980 }
2981 2 => {
2982 let range = movement::surrounding_word(&display_map, position);
2983 start = buffer.anchor_before(range.start.to_point(&display_map));
2984 end = buffer.anchor_before(range.end.to_point(&display_map));
2985 mode = SelectMode::Word(start..end);
2986 auto_scroll = true;
2987 }
2988 3 => {
2989 let position = display_map
2990 .clip_point(position, Bias::Left)
2991 .to_point(&display_map);
2992 let line_start = display_map.prev_line_boundary(position).0;
2993 let next_line_start = buffer.clip_point(
2994 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2995 Bias::Left,
2996 );
2997 start = buffer.anchor_before(line_start);
2998 end = buffer.anchor_before(next_line_start);
2999 mode = SelectMode::Line(start..end);
3000 auto_scroll = true;
3001 }
3002 _ => {
3003 start = buffer.anchor_before(0);
3004 end = buffer.anchor_before(buffer.len());
3005 mode = SelectMode::All;
3006 auto_scroll = false;
3007 }
3008 }
3009 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3010
3011 let point_to_delete: Option<usize> = {
3012 let selected_points: Vec<Selection<Point>> =
3013 self.selections.disjoint_in_range(start..end, cx);
3014
3015 if !add || click_count > 1 {
3016 None
3017 } else if !selected_points.is_empty() {
3018 Some(selected_points[0].id)
3019 } else {
3020 let clicked_point_already_selected =
3021 self.selections.disjoint.iter().find(|selection| {
3022 selection.start.to_point(buffer) == start.to_point(buffer)
3023 || selection.end.to_point(buffer) == end.to_point(buffer)
3024 });
3025
3026 clicked_point_already_selected.map(|selection| selection.id)
3027 }
3028 };
3029
3030 let selections_count = self.selections.count();
3031
3032 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3033 if let Some(point_to_delete) = point_to_delete {
3034 s.delete(point_to_delete);
3035
3036 if selections_count == 1 {
3037 s.set_pending_anchor_range(start..end, mode);
3038 }
3039 } else {
3040 if !add {
3041 s.clear_disjoint();
3042 } else if click_count > 1 {
3043 s.delete(newest_selection.id)
3044 }
3045
3046 s.set_pending_anchor_range(start..end, mode);
3047 }
3048 });
3049 }
3050
3051 fn begin_columnar_selection(
3052 &mut self,
3053 position: DisplayPoint,
3054 goal_column: u32,
3055 reset: bool,
3056 window: &mut Window,
3057 cx: &mut Context<Self>,
3058 ) {
3059 if !self.focus_handle.is_focused(window) {
3060 self.last_focused_descendant = None;
3061 window.focus(&self.focus_handle);
3062 }
3063
3064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3065
3066 if reset {
3067 let pointer_position = display_map
3068 .buffer_snapshot
3069 .anchor_before(position.to_point(&display_map));
3070
3071 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3072 s.clear_disjoint();
3073 s.set_pending_anchor_range(
3074 pointer_position..pointer_position,
3075 SelectMode::Character,
3076 );
3077 });
3078 }
3079
3080 let tail = self.selections.newest::<Point>(cx).tail();
3081 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3082
3083 if !reset {
3084 self.select_columns(
3085 tail.to_display_point(&display_map),
3086 position,
3087 goal_column,
3088 &display_map,
3089 window,
3090 cx,
3091 );
3092 }
3093 }
3094
3095 fn update_selection(
3096 &mut self,
3097 position: DisplayPoint,
3098 goal_column: u32,
3099 scroll_delta: gpui::Point<f32>,
3100 window: &mut Window,
3101 cx: &mut Context<Self>,
3102 ) {
3103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3104
3105 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3106 let tail = tail.to_display_point(&display_map);
3107 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3108 } else if let Some(mut pending) = self.selections.pending_anchor() {
3109 let buffer = self.buffer.read(cx).snapshot(cx);
3110 let head;
3111 let tail;
3112 let mode = self.selections.pending_mode().unwrap();
3113 match &mode {
3114 SelectMode::Character => {
3115 head = position.to_point(&display_map);
3116 tail = pending.tail().to_point(&buffer);
3117 }
3118 SelectMode::Word(original_range) => {
3119 let original_display_range = original_range.start.to_display_point(&display_map)
3120 ..original_range.end.to_display_point(&display_map);
3121 let original_buffer_range = original_display_range.start.to_point(&display_map)
3122 ..original_display_range.end.to_point(&display_map);
3123 if movement::is_inside_word(&display_map, position)
3124 || original_display_range.contains(&position)
3125 {
3126 let word_range = movement::surrounding_word(&display_map, position);
3127 if word_range.start < original_display_range.start {
3128 head = word_range.start.to_point(&display_map);
3129 } else {
3130 head = word_range.end.to_point(&display_map);
3131 }
3132 } else {
3133 head = position.to_point(&display_map);
3134 }
3135
3136 if head <= original_buffer_range.start {
3137 tail = original_buffer_range.end;
3138 } else {
3139 tail = original_buffer_range.start;
3140 }
3141 }
3142 SelectMode::Line(original_range) => {
3143 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3144
3145 let position = display_map
3146 .clip_point(position, Bias::Left)
3147 .to_point(&display_map);
3148 let line_start = display_map.prev_line_boundary(position).0;
3149 let next_line_start = buffer.clip_point(
3150 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3151 Bias::Left,
3152 );
3153
3154 if line_start < original_range.start {
3155 head = line_start
3156 } else {
3157 head = next_line_start
3158 }
3159
3160 if head <= original_range.start {
3161 tail = original_range.end;
3162 } else {
3163 tail = original_range.start;
3164 }
3165 }
3166 SelectMode::All => {
3167 return;
3168 }
3169 };
3170
3171 if head < tail {
3172 pending.start = buffer.anchor_before(head);
3173 pending.end = buffer.anchor_before(tail);
3174 pending.reversed = true;
3175 } else {
3176 pending.start = buffer.anchor_before(tail);
3177 pending.end = buffer.anchor_before(head);
3178 pending.reversed = false;
3179 }
3180
3181 self.change_selections(None, window, cx, |s| {
3182 s.set_pending(pending, mode);
3183 });
3184 } else {
3185 log::error!("update_selection dispatched with no pending selection");
3186 return;
3187 }
3188
3189 self.apply_scroll_delta(scroll_delta, window, cx);
3190 cx.notify();
3191 }
3192
3193 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3194 self.columnar_selection_tail.take();
3195 if self.selections.pending_anchor().is_some() {
3196 let selections = self.selections.all::<usize>(cx);
3197 self.change_selections(None, window, cx, |s| {
3198 s.select(selections);
3199 s.clear_pending();
3200 });
3201 }
3202 }
3203
3204 fn select_columns(
3205 &mut self,
3206 tail: DisplayPoint,
3207 head: DisplayPoint,
3208 goal_column: u32,
3209 display_map: &DisplaySnapshot,
3210 window: &mut Window,
3211 cx: &mut Context<Self>,
3212 ) {
3213 let start_row = cmp::min(tail.row(), head.row());
3214 let end_row = cmp::max(tail.row(), head.row());
3215 let start_column = cmp::min(tail.column(), goal_column);
3216 let end_column = cmp::max(tail.column(), goal_column);
3217 let reversed = start_column < tail.column();
3218
3219 let selection_ranges = (start_row.0..=end_row.0)
3220 .map(DisplayRow)
3221 .filter_map(|row| {
3222 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3223 let start = display_map
3224 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3225 .to_point(display_map);
3226 let end = display_map
3227 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3228 .to_point(display_map);
3229 if reversed {
3230 Some(end..start)
3231 } else {
3232 Some(start..end)
3233 }
3234 } else {
3235 None
3236 }
3237 })
3238 .collect::<Vec<_>>();
3239
3240 self.change_selections(None, window, cx, |s| {
3241 s.select_ranges(selection_ranges);
3242 });
3243 cx.notify();
3244 }
3245
3246 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3247 self.selections
3248 .all_adjusted(cx)
3249 .iter()
3250 .any(|selection| !selection.is_empty())
3251 }
3252
3253 pub fn has_pending_nonempty_selection(&self) -> bool {
3254 let pending_nonempty_selection = match self.selections.pending_anchor() {
3255 Some(Selection { start, end, .. }) => start != end,
3256 None => false,
3257 };
3258
3259 pending_nonempty_selection
3260 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3261 }
3262
3263 pub fn has_pending_selection(&self) -> bool {
3264 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3265 }
3266
3267 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3268 self.selection_mark_mode = false;
3269
3270 if self.clear_expanded_diff_hunks(cx) {
3271 cx.notify();
3272 return;
3273 }
3274 if self.dismiss_menus_and_popups(true, window, cx) {
3275 return;
3276 }
3277
3278 if self.mode.is_full()
3279 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3280 {
3281 return;
3282 }
3283
3284 cx.propagate();
3285 }
3286
3287 pub fn dismiss_menus_and_popups(
3288 &mut self,
3289 is_user_requested: bool,
3290 window: &mut Window,
3291 cx: &mut Context<Self>,
3292 ) -> bool {
3293 if self.take_rename(false, window, cx).is_some() {
3294 return true;
3295 }
3296
3297 if hide_hover(self, cx) {
3298 return true;
3299 }
3300
3301 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3302 return true;
3303 }
3304
3305 if self.hide_context_menu(window, cx).is_some() {
3306 return true;
3307 }
3308
3309 if self.mouse_context_menu.take().is_some() {
3310 return true;
3311 }
3312
3313 if is_user_requested && self.discard_inline_completion(true, cx) {
3314 return true;
3315 }
3316
3317 if self.snippet_stack.pop().is_some() {
3318 return true;
3319 }
3320
3321 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3322 self.dismiss_diagnostics(cx);
3323 return true;
3324 }
3325
3326 false
3327 }
3328
3329 fn linked_editing_ranges_for(
3330 &self,
3331 selection: Range<text::Anchor>,
3332 cx: &App,
3333 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3334 if self.linked_edit_ranges.is_empty() {
3335 return None;
3336 }
3337 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3338 selection.end.buffer_id.and_then(|end_buffer_id| {
3339 if selection.start.buffer_id != Some(end_buffer_id) {
3340 return None;
3341 }
3342 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3343 let snapshot = buffer.read(cx).snapshot();
3344 self.linked_edit_ranges
3345 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3346 .map(|ranges| (ranges, snapshot, buffer))
3347 })?;
3348 use text::ToOffset as TO;
3349 // find offset from the start of current range to current cursor position
3350 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3351
3352 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3353 let start_difference = start_offset - start_byte_offset;
3354 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3355 let end_difference = end_offset - start_byte_offset;
3356 // Current range has associated linked ranges.
3357 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3358 for range in linked_ranges.iter() {
3359 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3360 let end_offset = start_offset + end_difference;
3361 let start_offset = start_offset + start_difference;
3362 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3363 continue;
3364 }
3365 if self.selections.disjoint_anchor_ranges().any(|s| {
3366 if s.start.buffer_id != selection.start.buffer_id
3367 || s.end.buffer_id != selection.end.buffer_id
3368 {
3369 return false;
3370 }
3371 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3372 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3373 }) {
3374 continue;
3375 }
3376 let start = buffer_snapshot.anchor_after(start_offset);
3377 let end = buffer_snapshot.anchor_after(end_offset);
3378 linked_edits
3379 .entry(buffer.clone())
3380 .or_default()
3381 .push(start..end);
3382 }
3383 Some(linked_edits)
3384 }
3385
3386 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3387 let text: Arc<str> = text.into();
3388
3389 if self.read_only(cx) {
3390 return;
3391 }
3392
3393 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3394
3395 let selections = self.selections.all_adjusted(cx);
3396 let mut bracket_inserted = false;
3397 let mut edits = Vec::new();
3398 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3399 let mut new_selections = Vec::with_capacity(selections.len());
3400 let mut new_autoclose_regions = Vec::new();
3401 let snapshot = self.buffer.read(cx).read(cx);
3402 let mut clear_linked_edit_ranges = false;
3403
3404 for (selection, autoclose_region) in
3405 self.selections_with_autoclose_regions(selections, &snapshot)
3406 {
3407 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3408 // Determine if the inserted text matches the opening or closing
3409 // bracket of any of this language's bracket pairs.
3410 let mut bracket_pair = None;
3411 let mut is_bracket_pair_start = false;
3412 let mut is_bracket_pair_end = false;
3413 if !text.is_empty() {
3414 let mut bracket_pair_matching_end = None;
3415 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3416 // and they are removing the character that triggered IME popup.
3417 for (pair, enabled) in scope.brackets() {
3418 if !pair.close && !pair.surround {
3419 continue;
3420 }
3421
3422 if enabled && pair.start.ends_with(text.as_ref()) {
3423 let prefix_len = pair.start.len() - text.len();
3424 let preceding_text_matches_prefix = prefix_len == 0
3425 || (selection.start.column >= (prefix_len as u32)
3426 && snapshot.contains_str_at(
3427 Point::new(
3428 selection.start.row,
3429 selection.start.column - (prefix_len as u32),
3430 ),
3431 &pair.start[..prefix_len],
3432 ));
3433 if preceding_text_matches_prefix {
3434 bracket_pair = Some(pair.clone());
3435 is_bracket_pair_start = true;
3436 break;
3437 }
3438 }
3439 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3440 {
3441 // take first bracket pair matching end, but don't break in case a later bracket
3442 // pair matches start
3443 bracket_pair_matching_end = Some(pair.clone());
3444 }
3445 }
3446 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3447 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3448 is_bracket_pair_end = true;
3449 }
3450 }
3451
3452 if let Some(bracket_pair) = bracket_pair {
3453 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3454 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3455 let auto_surround =
3456 self.use_auto_surround && snapshot_settings.use_auto_surround;
3457 if selection.is_empty() {
3458 if is_bracket_pair_start {
3459 // If the inserted text is a suffix of an opening bracket and the
3460 // selection is preceded by the rest of the opening bracket, then
3461 // insert the closing bracket.
3462 let following_text_allows_autoclose = snapshot
3463 .chars_at(selection.start)
3464 .next()
3465 .map_or(true, |c| scope.should_autoclose_before(c));
3466
3467 let preceding_text_allows_autoclose = selection.start.column == 0
3468 || snapshot.reversed_chars_at(selection.start).next().map_or(
3469 true,
3470 |c| {
3471 bracket_pair.start != bracket_pair.end
3472 || !snapshot
3473 .char_classifier_at(selection.start)
3474 .is_word(c)
3475 },
3476 );
3477
3478 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3479 && bracket_pair.start.len() == 1
3480 {
3481 let target = bracket_pair.start.chars().next().unwrap();
3482 let current_line_count = snapshot
3483 .reversed_chars_at(selection.start)
3484 .take_while(|&c| c != '\n')
3485 .filter(|&c| c == target)
3486 .count();
3487 current_line_count % 2 == 1
3488 } else {
3489 false
3490 };
3491
3492 if autoclose
3493 && bracket_pair.close
3494 && following_text_allows_autoclose
3495 && preceding_text_allows_autoclose
3496 && !is_closing_quote
3497 {
3498 let anchor = snapshot.anchor_before(selection.end);
3499 new_selections.push((selection.map(|_| anchor), text.len()));
3500 new_autoclose_regions.push((
3501 anchor,
3502 text.len(),
3503 selection.id,
3504 bracket_pair.clone(),
3505 ));
3506 edits.push((
3507 selection.range(),
3508 format!("{}{}", text, bracket_pair.end).into(),
3509 ));
3510 bracket_inserted = true;
3511 continue;
3512 }
3513 }
3514
3515 if let Some(region) = autoclose_region {
3516 // If the selection is followed by an auto-inserted closing bracket,
3517 // then don't insert that closing bracket again; just move the selection
3518 // past the closing bracket.
3519 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3520 && text.as_ref() == region.pair.end.as_str();
3521 if should_skip {
3522 let anchor = snapshot.anchor_after(selection.end);
3523 new_selections
3524 .push((selection.map(|_| anchor), region.pair.end.len()));
3525 continue;
3526 }
3527 }
3528
3529 let always_treat_brackets_as_autoclosed = snapshot
3530 .language_settings_at(selection.start, cx)
3531 .always_treat_brackets_as_autoclosed;
3532 if always_treat_brackets_as_autoclosed
3533 && is_bracket_pair_end
3534 && snapshot.contains_str_at(selection.end, text.as_ref())
3535 {
3536 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3537 // and the inserted text is a closing bracket and the selection is followed
3538 // by the closing bracket then move the selection past the closing bracket.
3539 let anchor = snapshot.anchor_after(selection.end);
3540 new_selections.push((selection.map(|_| anchor), text.len()));
3541 continue;
3542 }
3543 }
3544 // If an opening bracket is 1 character long and is typed while
3545 // text is selected, then surround that text with the bracket pair.
3546 else if auto_surround
3547 && bracket_pair.surround
3548 && is_bracket_pair_start
3549 && bracket_pair.start.chars().count() == 1
3550 {
3551 edits.push((selection.start..selection.start, text.clone()));
3552 edits.push((
3553 selection.end..selection.end,
3554 bracket_pair.end.as_str().into(),
3555 ));
3556 bracket_inserted = true;
3557 new_selections.push((
3558 Selection {
3559 id: selection.id,
3560 start: snapshot.anchor_after(selection.start),
3561 end: snapshot.anchor_before(selection.end),
3562 reversed: selection.reversed,
3563 goal: selection.goal,
3564 },
3565 0,
3566 ));
3567 continue;
3568 }
3569 }
3570 }
3571
3572 if self.auto_replace_emoji_shortcode
3573 && selection.is_empty()
3574 && text.as_ref().ends_with(':')
3575 {
3576 if let Some(possible_emoji_short_code) =
3577 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3578 {
3579 if !possible_emoji_short_code.is_empty() {
3580 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3581 let emoji_shortcode_start = Point::new(
3582 selection.start.row,
3583 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3584 );
3585
3586 // Remove shortcode from buffer
3587 edits.push((
3588 emoji_shortcode_start..selection.start,
3589 "".to_string().into(),
3590 ));
3591 new_selections.push((
3592 Selection {
3593 id: selection.id,
3594 start: snapshot.anchor_after(emoji_shortcode_start),
3595 end: snapshot.anchor_before(selection.start),
3596 reversed: selection.reversed,
3597 goal: selection.goal,
3598 },
3599 0,
3600 ));
3601
3602 // Insert emoji
3603 let selection_start_anchor = snapshot.anchor_after(selection.start);
3604 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3605 edits.push((selection.start..selection.end, emoji.to_string().into()));
3606
3607 continue;
3608 }
3609 }
3610 }
3611 }
3612
3613 // If not handling any auto-close operation, then just replace the selected
3614 // text with the given input and move the selection to the end of the
3615 // newly inserted text.
3616 let anchor = snapshot.anchor_after(selection.end);
3617 if !self.linked_edit_ranges.is_empty() {
3618 let start_anchor = snapshot.anchor_before(selection.start);
3619
3620 let is_word_char = text.chars().next().map_or(true, |char| {
3621 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3622 classifier.is_word(char)
3623 });
3624
3625 if is_word_char {
3626 if let Some(ranges) = self
3627 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3628 {
3629 for (buffer, edits) in ranges {
3630 linked_edits
3631 .entry(buffer.clone())
3632 .or_default()
3633 .extend(edits.into_iter().map(|range| (range, text.clone())));
3634 }
3635 }
3636 } else {
3637 clear_linked_edit_ranges = true;
3638 }
3639 }
3640
3641 new_selections.push((selection.map(|_| anchor), 0));
3642 edits.push((selection.start..selection.end, text.clone()));
3643 }
3644
3645 drop(snapshot);
3646
3647 self.transact(window, cx, |this, window, cx| {
3648 if clear_linked_edit_ranges {
3649 this.linked_edit_ranges.clear();
3650 }
3651 let initial_buffer_versions =
3652 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3653
3654 this.buffer.update(cx, |buffer, cx| {
3655 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3656 });
3657 for (buffer, edits) in linked_edits {
3658 buffer.update(cx, |buffer, cx| {
3659 let snapshot = buffer.snapshot();
3660 let edits = edits
3661 .into_iter()
3662 .map(|(range, text)| {
3663 use text::ToPoint as TP;
3664 let end_point = TP::to_point(&range.end, &snapshot);
3665 let start_point = TP::to_point(&range.start, &snapshot);
3666 (start_point..end_point, text)
3667 })
3668 .sorted_by_key(|(range, _)| range.start);
3669 buffer.edit(edits, None, cx);
3670 })
3671 }
3672 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3673 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3674 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3675 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3676 .zip(new_selection_deltas)
3677 .map(|(selection, delta)| Selection {
3678 id: selection.id,
3679 start: selection.start + delta,
3680 end: selection.end + delta,
3681 reversed: selection.reversed,
3682 goal: SelectionGoal::None,
3683 })
3684 .collect::<Vec<_>>();
3685
3686 let mut i = 0;
3687 for (position, delta, selection_id, pair) in new_autoclose_regions {
3688 let position = position.to_offset(&map.buffer_snapshot) + delta;
3689 let start = map.buffer_snapshot.anchor_before(position);
3690 let end = map.buffer_snapshot.anchor_after(position);
3691 while let Some(existing_state) = this.autoclose_regions.get(i) {
3692 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3693 Ordering::Less => i += 1,
3694 Ordering::Greater => break,
3695 Ordering::Equal => {
3696 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3697 Ordering::Less => i += 1,
3698 Ordering::Equal => break,
3699 Ordering::Greater => break,
3700 }
3701 }
3702 }
3703 }
3704 this.autoclose_regions.insert(
3705 i,
3706 AutocloseRegion {
3707 selection_id,
3708 range: start..end,
3709 pair,
3710 },
3711 );
3712 }
3713
3714 let had_active_inline_completion = this.has_active_inline_completion();
3715 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3716 s.select(new_selections)
3717 });
3718
3719 if !bracket_inserted {
3720 if let Some(on_type_format_task) =
3721 this.trigger_on_type_formatting(text.to_string(), window, cx)
3722 {
3723 on_type_format_task.detach_and_log_err(cx);
3724 }
3725 }
3726
3727 let editor_settings = EditorSettings::get_global(cx);
3728 if bracket_inserted
3729 && (editor_settings.auto_signature_help
3730 || editor_settings.show_signature_help_after_edits)
3731 {
3732 this.show_signature_help(&ShowSignatureHelp, window, cx);
3733 }
3734
3735 let trigger_in_words =
3736 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3737 if this.hard_wrap.is_some() {
3738 let latest: Range<Point> = this.selections.newest(cx).range();
3739 if latest.is_empty()
3740 && this
3741 .buffer()
3742 .read(cx)
3743 .snapshot(cx)
3744 .line_len(MultiBufferRow(latest.start.row))
3745 == latest.start.column
3746 {
3747 this.rewrap_impl(
3748 RewrapOptions {
3749 override_language_settings: true,
3750 preserve_existing_whitespace: true,
3751 },
3752 cx,
3753 )
3754 }
3755 }
3756 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3757 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3758 this.refresh_inline_completion(true, false, window, cx);
3759 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3760 });
3761 }
3762
3763 fn find_possible_emoji_shortcode_at_position(
3764 snapshot: &MultiBufferSnapshot,
3765 position: Point,
3766 ) -> Option<String> {
3767 let mut chars = Vec::new();
3768 let mut found_colon = false;
3769 for char in snapshot.reversed_chars_at(position).take(100) {
3770 // Found a possible emoji shortcode in the middle of the buffer
3771 if found_colon {
3772 if char.is_whitespace() {
3773 chars.reverse();
3774 return Some(chars.iter().collect());
3775 }
3776 // If the previous character is not a whitespace, we are in the middle of a word
3777 // and we only want to complete the shortcode if the word is made up of other emojis
3778 let mut containing_word = String::new();
3779 for ch in snapshot
3780 .reversed_chars_at(position)
3781 .skip(chars.len() + 1)
3782 .take(100)
3783 {
3784 if ch.is_whitespace() {
3785 break;
3786 }
3787 containing_word.push(ch);
3788 }
3789 let containing_word = containing_word.chars().rev().collect::<String>();
3790 if util::word_consists_of_emojis(containing_word.as_str()) {
3791 chars.reverse();
3792 return Some(chars.iter().collect());
3793 }
3794 }
3795
3796 if char.is_whitespace() || !char.is_ascii() {
3797 return None;
3798 }
3799 if char == ':' {
3800 found_colon = true;
3801 } else {
3802 chars.push(char);
3803 }
3804 }
3805 // Found a possible emoji shortcode at the beginning of the buffer
3806 chars.reverse();
3807 Some(chars.iter().collect())
3808 }
3809
3810 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3811 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3812 self.transact(window, cx, |this, window, cx| {
3813 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3814 let selections = this.selections.all::<usize>(cx);
3815 let multi_buffer = this.buffer.read(cx);
3816 let buffer = multi_buffer.snapshot(cx);
3817 selections
3818 .iter()
3819 .map(|selection| {
3820 let start_point = selection.start.to_point(&buffer);
3821 let mut indent =
3822 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3823 indent.len = cmp::min(indent.len, start_point.column);
3824 let start = selection.start;
3825 let end = selection.end;
3826 let selection_is_empty = start == end;
3827 let language_scope = buffer.language_scope_at(start);
3828 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3829 &language_scope
3830 {
3831 let insert_extra_newline =
3832 insert_extra_newline_brackets(&buffer, start..end, language)
3833 || insert_extra_newline_tree_sitter(&buffer, start..end);
3834
3835 // Comment extension on newline is allowed only for cursor selections
3836 let comment_delimiter = maybe!({
3837 if !selection_is_empty {
3838 return None;
3839 }
3840
3841 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3842 return None;
3843 }
3844
3845 let delimiters = language.line_comment_prefixes();
3846 let max_len_of_delimiter =
3847 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3848 let (snapshot, range) =
3849 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3850
3851 let mut index_of_first_non_whitespace = 0;
3852 let comment_candidate = snapshot
3853 .chars_for_range(range)
3854 .skip_while(|c| {
3855 let should_skip = c.is_whitespace();
3856 if should_skip {
3857 index_of_first_non_whitespace += 1;
3858 }
3859 should_skip
3860 })
3861 .take(max_len_of_delimiter)
3862 .collect::<String>();
3863 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3864 comment_candidate.starts_with(comment_prefix.as_ref())
3865 })?;
3866 let cursor_is_placed_after_comment_marker =
3867 index_of_first_non_whitespace + comment_prefix.len()
3868 <= start_point.column as usize;
3869 if cursor_is_placed_after_comment_marker {
3870 Some(comment_prefix.clone())
3871 } else {
3872 None
3873 }
3874 });
3875 (comment_delimiter, insert_extra_newline)
3876 } else {
3877 (None, false)
3878 };
3879
3880 let capacity_for_delimiter = comment_delimiter
3881 .as_deref()
3882 .map(str::len)
3883 .unwrap_or_default();
3884 let mut new_text =
3885 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3886 new_text.push('\n');
3887 new_text.extend(indent.chars());
3888 if let Some(delimiter) = &comment_delimiter {
3889 new_text.push_str(delimiter);
3890 }
3891 if insert_extra_newline {
3892 new_text = new_text.repeat(2);
3893 }
3894
3895 let anchor = buffer.anchor_after(end);
3896 let new_selection = selection.map(|_| anchor);
3897 (
3898 (start..end, new_text),
3899 (insert_extra_newline, new_selection),
3900 )
3901 })
3902 .unzip()
3903 };
3904
3905 this.edit_with_autoindent(edits, cx);
3906 let buffer = this.buffer.read(cx).snapshot(cx);
3907 let new_selections = selection_fixup_info
3908 .into_iter()
3909 .map(|(extra_newline_inserted, new_selection)| {
3910 let mut cursor = new_selection.end.to_point(&buffer);
3911 if extra_newline_inserted {
3912 cursor.row -= 1;
3913 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3914 }
3915 new_selection.map(|_| cursor)
3916 })
3917 .collect();
3918
3919 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3920 s.select(new_selections)
3921 });
3922 this.refresh_inline_completion(true, false, window, cx);
3923 });
3924 }
3925
3926 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3927 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3928
3929 let buffer = self.buffer.read(cx);
3930 let snapshot = buffer.snapshot(cx);
3931
3932 let mut edits = Vec::new();
3933 let mut rows = Vec::new();
3934
3935 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3936 let cursor = selection.head();
3937 let row = cursor.row;
3938
3939 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3940
3941 let newline = "\n".to_string();
3942 edits.push((start_of_line..start_of_line, newline));
3943
3944 rows.push(row + rows_inserted as u32);
3945 }
3946
3947 self.transact(window, cx, |editor, window, cx| {
3948 editor.edit(edits, cx);
3949
3950 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3951 let mut index = 0;
3952 s.move_cursors_with(|map, _, _| {
3953 let row = rows[index];
3954 index += 1;
3955
3956 let point = Point::new(row, 0);
3957 let boundary = map.next_line_boundary(point).1;
3958 let clipped = map.clip_point(boundary, Bias::Left);
3959
3960 (clipped, SelectionGoal::None)
3961 });
3962 });
3963
3964 let mut indent_edits = Vec::new();
3965 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3966 for row in rows {
3967 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3968 for (row, indent) in indents {
3969 if indent.len == 0 {
3970 continue;
3971 }
3972
3973 let text = match indent.kind {
3974 IndentKind::Space => " ".repeat(indent.len as usize),
3975 IndentKind::Tab => "\t".repeat(indent.len as usize),
3976 };
3977 let point = Point::new(row.0, 0);
3978 indent_edits.push((point..point, text));
3979 }
3980 }
3981 editor.edit(indent_edits, cx);
3982 });
3983 }
3984
3985 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3986 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3987
3988 let buffer = self.buffer.read(cx);
3989 let snapshot = buffer.snapshot(cx);
3990
3991 let mut edits = Vec::new();
3992 let mut rows = Vec::new();
3993 let mut rows_inserted = 0;
3994
3995 for selection in self.selections.all_adjusted(cx) {
3996 let cursor = selection.head();
3997 let row = cursor.row;
3998
3999 let point = Point::new(row + 1, 0);
4000 let start_of_line = snapshot.clip_point(point, Bias::Left);
4001
4002 let newline = "\n".to_string();
4003 edits.push((start_of_line..start_of_line, newline));
4004
4005 rows_inserted += 1;
4006 rows.push(row + rows_inserted);
4007 }
4008
4009 self.transact(window, cx, |editor, window, cx| {
4010 editor.edit(edits, cx);
4011
4012 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4013 let mut index = 0;
4014 s.move_cursors_with(|map, _, _| {
4015 let row = rows[index];
4016 index += 1;
4017
4018 let point = Point::new(row, 0);
4019 let boundary = map.next_line_boundary(point).1;
4020 let clipped = map.clip_point(boundary, Bias::Left);
4021
4022 (clipped, SelectionGoal::None)
4023 });
4024 });
4025
4026 let mut indent_edits = Vec::new();
4027 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4028 for row in rows {
4029 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4030 for (row, indent) in indents {
4031 if indent.len == 0 {
4032 continue;
4033 }
4034
4035 let text = match indent.kind {
4036 IndentKind::Space => " ".repeat(indent.len as usize),
4037 IndentKind::Tab => "\t".repeat(indent.len as usize),
4038 };
4039 let point = Point::new(row.0, 0);
4040 indent_edits.push((point..point, text));
4041 }
4042 }
4043 editor.edit(indent_edits, cx);
4044 });
4045 }
4046
4047 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4048 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4049 original_indent_columns: Vec::new(),
4050 });
4051 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4052 }
4053
4054 fn insert_with_autoindent_mode(
4055 &mut self,
4056 text: &str,
4057 autoindent_mode: Option<AutoindentMode>,
4058 window: &mut Window,
4059 cx: &mut Context<Self>,
4060 ) {
4061 if self.read_only(cx) {
4062 return;
4063 }
4064
4065 let text: Arc<str> = text.into();
4066 self.transact(window, cx, |this, window, cx| {
4067 let old_selections = this.selections.all_adjusted(cx);
4068 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4069 let anchors = {
4070 let snapshot = buffer.read(cx);
4071 old_selections
4072 .iter()
4073 .map(|s| {
4074 let anchor = snapshot.anchor_after(s.head());
4075 s.map(|_| anchor)
4076 })
4077 .collect::<Vec<_>>()
4078 };
4079 buffer.edit(
4080 old_selections
4081 .iter()
4082 .map(|s| (s.start..s.end, text.clone())),
4083 autoindent_mode,
4084 cx,
4085 );
4086 anchors
4087 });
4088
4089 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4090 s.select_anchors(selection_anchors);
4091 });
4092
4093 cx.notify();
4094 });
4095 }
4096
4097 fn trigger_completion_on_input(
4098 &mut self,
4099 text: &str,
4100 trigger_in_words: bool,
4101 window: &mut Window,
4102 cx: &mut Context<Self>,
4103 ) {
4104 let ignore_completion_provider = self
4105 .context_menu
4106 .borrow()
4107 .as_ref()
4108 .map(|menu| match menu {
4109 CodeContextMenu::Completions(completions_menu) => {
4110 completions_menu.ignore_completion_provider
4111 }
4112 CodeContextMenu::CodeActions(_) => false,
4113 })
4114 .unwrap_or(false);
4115
4116 if ignore_completion_provider {
4117 self.show_word_completions(&ShowWordCompletions, window, cx);
4118 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4119 self.show_completions(
4120 &ShowCompletions {
4121 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4122 },
4123 window,
4124 cx,
4125 );
4126 } else {
4127 self.hide_context_menu(window, cx);
4128 }
4129 }
4130
4131 fn is_completion_trigger(
4132 &self,
4133 text: &str,
4134 trigger_in_words: bool,
4135 cx: &mut Context<Self>,
4136 ) -> bool {
4137 let position = self.selections.newest_anchor().head();
4138 let multibuffer = self.buffer.read(cx);
4139 let Some(buffer) = position
4140 .buffer_id
4141 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4142 else {
4143 return false;
4144 };
4145
4146 if let Some(completion_provider) = &self.completion_provider {
4147 completion_provider.is_completion_trigger(
4148 &buffer,
4149 position.text_anchor,
4150 text,
4151 trigger_in_words,
4152 cx,
4153 )
4154 } else {
4155 false
4156 }
4157 }
4158
4159 /// If any empty selections is touching the start of its innermost containing autoclose
4160 /// region, expand it to select the brackets.
4161 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4162 let selections = self.selections.all::<usize>(cx);
4163 let buffer = self.buffer.read(cx).read(cx);
4164 let new_selections = self
4165 .selections_with_autoclose_regions(selections, &buffer)
4166 .map(|(mut selection, region)| {
4167 if !selection.is_empty() {
4168 return selection;
4169 }
4170
4171 if let Some(region) = region {
4172 let mut range = region.range.to_offset(&buffer);
4173 if selection.start == range.start && range.start >= region.pair.start.len() {
4174 range.start -= region.pair.start.len();
4175 if buffer.contains_str_at(range.start, ®ion.pair.start)
4176 && buffer.contains_str_at(range.end, ®ion.pair.end)
4177 {
4178 range.end += region.pair.end.len();
4179 selection.start = range.start;
4180 selection.end = range.end;
4181
4182 return selection;
4183 }
4184 }
4185 }
4186
4187 let always_treat_brackets_as_autoclosed = buffer
4188 .language_settings_at(selection.start, cx)
4189 .always_treat_brackets_as_autoclosed;
4190
4191 if !always_treat_brackets_as_autoclosed {
4192 return selection;
4193 }
4194
4195 if let Some(scope) = buffer.language_scope_at(selection.start) {
4196 for (pair, enabled) in scope.brackets() {
4197 if !enabled || !pair.close {
4198 continue;
4199 }
4200
4201 if buffer.contains_str_at(selection.start, &pair.end) {
4202 let pair_start_len = pair.start.len();
4203 if buffer.contains_str_at(
4204 selection.start.saturating_sub(pair_start_len),
4205 &pair.start,
4206 ) {
4207 selection.start -= pair_start_len;
4208 selection.end += pair.end.len();
4209
4210 return selection;
4211 }
4212 }
4213 }
4214 }
4215
4216 selection
4217 })
4218 .collect();
4219
4220 drop(buffer);
4221 self.change_selections(None, window, cx, |selections| {
4222 selections.select(new_selections)
4223 });
4224 }
4225
4226 /// Iterate the given selections, and for each one, find the smallest surrounding
4227 /// autoclose region. This uses the ordering of the selections and the autoclose
4228 /// regions to avoid repeated comparisons.
4229 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4230 &'a self,
4231 selections: impl IntoIterator<Item = Selection<D>>,
4232 buffer: &'a MultiBufferSnapshot,
4233 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4234 let mut i = 0;
4235 let mut regions = self.autoclose_regions.as_slice();
4236 selections.into_iter().map(move |selection| {
4237 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4238
4239 let mut enclosing = None;
4240 while let Some(pair_state) = regions.get(i) {
4241 if pair_state.range.end.to_offset(buffer) < range.start {
4242 regions = ®ions[i + 1..];
4243 i = 0;
4244 } else if pair_state.range.start.to_offset(buffer) > range.end {
4245 break;
4246 } else {
4247 if pair_state.selection_id == selection.id {
4248 enclosing = Some(pair_state);
4249 }
4250 i += 1;
4251 }
4252 }
4253
4254 (selection, enclosing)
4255 })
4256 }
4257
4258 /// Remove any autoclose regions that no longer contain their selection.
4259 fn invalidate_autoclose_regions(
4260 &mut self,
4261 mut selections: &[Selection<Anchor>],
4262 buffer: &MultiBufferSnapshot,
4263 ) {
4264 self.autoclose_regions.retain(|state| {
4265 let mut i = 0;
4266 while let Some(selection) = selections.get(i) {
4267 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4268 selections = &selections[1..];
4269 continue;
4270 }
4271 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4272 break;
4273 }
4274 if selection.id == state.selection_id {
4275 return true;
4276 } else {
4277 i += 1;
4278 }
4279 }
4280 false
4281 });
4282 }
4283
4284 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4285 let offset = position.to_offset(buffer);
4286 let (word_range, kind) = buffer.surrounding_word(offset, true);
4287 if offset > word_range.start && kind == Some(CharKind::Word) {
4288 Some(
4289 buffer
4290 .text_for_range(word_range.start..offset)
4291 .collect::<String>(),
4292 )
4293 } else {
4294 None
4295 }
4296 }
4297
4298 pub fn toggle_inline_values(
4299 &mut self,
4300 _: &ToggleInlineValues,
4301 _: &mut Window,
4302 cx: &mut Context<Self>,
4303 ) {
4304 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4305
4306 self.refresh_inline_values(cx);
4307 }
4308
4309 pub fn toggle_inlay_hints(
4310 &mut self,
4311 _: &ToggleInlayHints,
4312 _: &mut Window,
4313 cx: &mut Context<Self>,
4314 ) {
4315 self.refresh_inlay_hints(
4316 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4317 cx,
4318 );
4319 }
4320
4321 pub fn inlay_hints_enabled(&self) -> bool {
4322 self.inlay_hint_cache.enabled
4323 }
4324
4325 pub fn inline_values_enabled(&self) -> bool {
4326 self.inline_value_cache.enabled
4327 }
4328
4329 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4330 if self.semantics_provider.is_none() || !self.mode.is_full() {
4331 return;
4332 }
4333
4334 let reason_description = reason.description();
4335 let ignore_debounce = matches!(
4336 reason,
4337 InlayHintRefreshReason::SettingsChange(_)
4338 | InlayHintRefreshReason::Toggle(_)
4339 | InlayHintRefreshReason::ExcerptsRemoved(_)
4340 | InlayHintRefreshReason::ModifiersChanged(_)
4341 );
4342 let (invalidate_cache, required_languages) = match reason {
4343 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4344 match self.inlay_hint_cache.modifiers_override(enabled) {
4345 Some(enabled) => {
4346 if enabled {
4347 (InvalidationStrategy::RefreshRequested, None)
4348 } else {
4349 self.splice_inlays(
4350 &self
4351 .visible_inlay_hints(cx)
4352 .iter()
4353 .map(|inlay| inlay.id)
4354 .collect::<Vec<InlayId>>(),
4355 Vec::new(),
4356 cx,
4357 );
4358 return;
4359 }
4360 }
4361 None => return,
4362 }
4363 }
4364 InlayHintRefreshReason::Toggle(enabled) => {
4365 if self.inlay_hint_cache.toggle(enabled) {
4366 if enabled {
4367 (InvalidationStrategy::RefreshRequested, None)
4368 } else {
4369 self.splice_inlays(
4370 &self
4371 .visible_inlay_hints(cx)
4372 .iter()
4373 .map(|inlay| inlay.id)
4374 .collect::<Vec<InlayId>>(),
4375 Vec::new(),
4376 cx,
4377 );
4378 return;
4379 }
4380 } else {
4381 return;
4382 }
4383 }
4384 InlayHintRefreshReason::SettingsChange(new_settings) => {
4385 match self.inlay_hint_cache.update_settings(
4386 &self.buffer,
4387 new_settings,
4388 self.visible_inlay_hints(cx),
4389 cx,
4390 ) {
4391 ControlFlow::Break(Some(InlaySplice {
4392 to_remove,
4393 to_insert,
4394 })) => {
4395 self.splice_inlays(&to_remove, to_insert, cx);
4396 return;
4397 }
4398 ControlFlow::Break(None) => return,
4399 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4400 }
4401 }
4402 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4403 if let Some(InlaySplice {
4404 to_remove,
4405 to_insert,
4406 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4407 {
4408 self.splice_inlays(&to_remove, to_insert, cx);
4409 }
4410 self.display_map.update(cx, |display_map, _| {
4411 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4412 });
4413 return;
4414 }
4415 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4416 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4417 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4418 }
4419 InlayHintRefreshReason::RefreshRequested => {
4420 (InvalidationStrategy::RefreshRequested, None)
4421 }
4422 };
4423
4424 if let Some(InlaySplice {
4425 to_remove,
4426 to_insert,
4427 }) = self.inlay_hint_cache.spawn_hint_refresh(
4428 reason_description,
4429 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4430 invalidate_cache,
4431 ignore_debounce,
4432 cx,
4433 ) {
4434 self.splice_inlays(&to_remove, to_insert, cx);
4435 }
4436 }
4437
4438 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4439 self.display_map
4440 .read(cx)
4441 .current_inlays()
4442 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4443 .cloned()
4444 .collect()
4445 }
4446
4447 pub fn excerpts_for_inlay_hints_query(
4448 &self,
4449 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4450 cx: &mut Context<Editor>,
4451 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4452 let Some(project) = self.project.as_ref() else {
4453 return HashMap::default();
4454 };
4455 let project = project.read(cx);
4456 let multi_buffer = self.buffer().read(cx);
4457 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4458 let multi_buffer_visible_start = self
4459 .scroll_manager
4460 .anchor()
4461 .anchor
4462 .to_point(&multi_buffer_snapshot);
4463 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4464 multi_buffer_visible_start
4465 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4466 Bias::Left,
4467 );
4468 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4469 multi_buffer_snapshot
4470 .range_to_buffer_ranges(multi_buffer_visible_range)
4471 .into_iter()
4472 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4473 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4474 let buffer_file = project::File::from_dyn(buffer.file())?;
4475 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4476 let worktree_entry = buffer_worktree
4477 .read(cx)
4478 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4479 if worktree_entry.is_ignored {
4480 return None;
4481 }
4482
4483 let language = buffer.language()?;
4484 if let Some(restrict_to_languages) = restrict_to_languages {
4485 if !restrict_to_languages.contains(language) {
4486 return None;
4487 }
4488 }
4489 Some((
4490 excerpt_id,
4491 (
4492 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4493 buffer.version().clone(),
4494 excerpt_visible_range,
4495 ),
4496 ))
4497 })
4498 .collect()
4499 }
4500
4501 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4502 TextLayoutDetails {
4503 text_system: window.text_system().clone(),
4504 editor_style: self.style.clone().unwrap(),
4505 rem_size: window.rem_size(),
4506 scroll_anchor: self.scroll_manager.anchor(),
4507 visible_rows: self.visible_line_count(),
4508 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4509 }
4510 }
4511
4512 pub fn splice_inlays(
4513 &self,
4514 to_remove: &[InlayId],
4515 to_insert: Vec<Inlay>,
4516 cx: &mut Context<Self>,
4517 ) {
4518 self.display_map.update(cx, |display_map, cx| {
4519 display_map.splice_inlays(to_remove, to_insert, cx)
4520 });
4521 cx.notify();
4522 }
4523
4524 fn trigger_on_type_formatting(
4525 &self,
4526 input: String,
4527 window: &mut Window,
4528 cx: &mut Context<Self>,
4529 ) -> Option<Task<Result<()>>> {
4530 if input.len() != 1 {
4531 return None;
4532 }
4533
4534 let project = self.project.as_ref()?;
4535 let position = self.selections.newest_anchor().head();
4536 let (buffer, buffer_position) = self
4537 .buffer
4538 .read(cx)
4539 .text_anchor_for_position(position, cx)?;
4540
4541 let settings = language_settings::language_settings(
4542 buffer
4543 .read(cx)
4544 .language_at(buffer_position)
4545 .map(|l| l.name()),
4546 buffer.read(cx).file(),
4547 cx,
4548 );
4549 if !settings.use_on_type_format {
4550 return None;
4551 }
4552
4553 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4554 // hence we do LSP request & edit on host side only — add formats to host's history.
4555 let push_to_lsp_host_history = true;
4556 // If this is not the host, append its history with new edits.
4557 let push_to_client_history = project.read(cx).is_via_collab();
4558
4559 let on_type_formatting = project.update(cx, |project, cx| {
4560 project.on_type_format(
4561 buffer.clone(),
4562 buffer_position,
4563 input,
4564 push_to_lsp_host_history,
4565 cx,
4566 )
4567 });
4568 Some(cx.spawn_in(window, async move |editor, cx| {
4569 if let Some(transaction) = on_type_formatting.await? {
4570 if push_to_client_history {
4571 buffer
4572 .update(cx, |buffer, _| {
4573 buffer.push_transaction(transaction, Instant::now());
4574 buffer.finalize_last_transaction();
4575 })
4576 .ok();
4577 }
4578 editor.update(cx, |editor, cx| {
4579 editor.refresh_document_highlights(cx);
4580 })?;
4581 }
4582 Ok(())
4583 }))
4584 }
4585
4586 pub fn show_word_completions(
4587 &mut self,
4588 _: &ShowWordCompletions,
4589 window: &mut Window,
4590 cx: &mut Context<Self>,
4591 ) {
4592 self.open_completions_menu(true, None, window, cx);
4593 }
4594
4595 pub fn show_completions(
4596 &mut self,
4597 options: &ShowCompletions,
4598 window: &mut Window,
4599 cx: &mut Context<Self>,
4600 ) {
4601 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4602 }
4603
4604 fn open_completions_menu(
4605 &mut self,
4606 ignore_completion_provider: bool,
4607 trigger: Option<&str>,
4608 window: &mut Window,
4609 cx: &mut Context<Self>,
4610 ) {
4611 if self.pending_rename.is_some() {
4612 return;
4613 }
4614 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4615 return;
4616 }
4617
4618 let position = self.selections.newest_anchor().head();
4619 if position.diff_base_anchor.is_some() {
4620 return;
4621 }
4622 let (buffer, buffer_position) =
4623 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4624 output
4625 } else {
4626 return;
4627 };
4628 let buffer_snapshot = buffer.read(cx).snapshot();
4629 let show_completion_documentation = buffer_snapshot
4630 .settings_at(buffer_position, cx)
4631 .show_completion_documentation;
4632
4633 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4634
4635 let trigger_kind = match trigger {
4636 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4637 CompletionTriggerKind::TRIGGER_CHARACTER
4638 }
4639 _ => CompletionTriggerKind::INVOKED,
4640 };
4641 let completion_context = CompletionContext {
4642 trigger_character: trigger.and_then(|trigger| {
4643 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4644 Some(String::from(trigger))
4645 } else {
4646 None
4647 }
4648 }),
4649 trigger_kind,
4650 };
4651
4652 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4653 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4654 let word_to_exclude = buffer_snapshot
4655 .text_for_range(old_range.clone())
4656 .collect::<String>();
4657 (
4658 buffer_snapshot.anchor_before(old_range.start)
4659 ..buffer_snapshot.anchor_after(old_range.end),
4660 Some(word_to_exclude),
4661 )
4662 } else {
4663 (buffer_position..buffer_position, None)
4664 };
4665
4666 let completion_settings = language_settings(
4667 buffer_snapshot
4668 .language_at(buffer_position)
4669 .map(|language| language.name()),
4670 buffer_snapshot.file(),
4671 cx,
4672 )
4673 .completions;
4674
4675 // The document can be large, so stay in reasonable bounds when searching for words,
4676 // otherwise completion pop-up might be slow to appear.
4677 const WORD_LOOKUP_ROWS: u32 = 5_000;
4678 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4679 let min_word_search = buffer_snapshot.clip_point(
4680 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4681 Bias::Left,
4682 );
4683 let max_word_search = buffer_snapshot.clip_point(
4684 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4685 Bias::Right,
4686 );
4687 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4688 ..buffer_snapshot.point_to_offset(max_word_search);
4689
4690 let provider = self
4691 .completion_provider
4692 .as_ref()
4693 .filter(|_| !ignore_completion_provider);
4694 let skip_digits = query
4695 .as_ref()
4696 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4697
4698 let (mut words, provided_completions) = match provider {
4699 Some(provider) => {
4700 let completions = provider.completions(
4701 position.excerpt_id,
4702 &buffer,
4703 buffer_position,
4704 completion_context,
4705 window,
4706 cx,
4707 );
4708
4709 let words = match completion_settings.words {
4710 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4711 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4712 .background_spawn(async move {
4713 buffer_snapshot.words_in_range(WordsQuery {
4714 fuzzy_contents: None,
4715 range: word_search_range,
4716 skip_digits,
4717 })
4718 }),
4719 };
4720
4721 (words, completions)
4722 }
4723 None => (
4724 cx.background_spawn(async move {
4725 buffer_snapshot.words_in_range(WordsQuery {
4726 fuzzy_contents: None,
4727 range: word_search_range,
4728 skip_digits,
4729 })
4730 }),
4731 Task::ready(Ok(None)),
4732 ),
4733 };
4734
4735 let sort_completions = provider
4736 .as_ref()
4737 .map_or(false, |provider| provider.sort_completions());
4738
4739 let filter_completions = provider
4740 .as_ref()
4741 .map_or(true, |provider| provider.filter_completions());
4742
4743 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4744
4745 let id = post_inc(&mut self.next_completion_id);
4746 let task = cx.spawn_in(window, async move |editor, cx| {
4747 async move {
4748 editor.update(cx, |this, _| {
4749 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4750 })?;
4751
4752 let mut completions = Vec::new();
4753 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4754 completions.extend(provided_completions);
4755 if completion_settings.words == WordsCompletionMode::Fallback {
4756 words = Task::ready(BTreeMap::default());
4757 }
4758 }
4759
4760 let mut words = words.await;
4761 if let Some(word_to_exclude) = &word_to_exclude {
4762 words.remove(word_to_exclude);
4763 }
4764 for lsp_completion in &completions {
4765 words.remove(&lsp_completion.new_text);
4766 }
4767 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4768 replace_range: old_range.clone(),
4769 new_text: word.clone(),
4770 label: CodeLabel::plain(word, None),
4771 icon_path: None,
4772 documentation: None,
4773 source: CompletionSource::BufferWord {
4774 word_range,
4775 resolved: false,
4776 },
4777 insert_text_mode: Some(InsertTextMode::AS_IS),
4778 confirm: None,
4779 }));
4780
4781 let menu = if completions.is_empty() {
4782 None
4783 } else {
4784 let mut menu = CompletionsMenu::new(
4785 id,
4786 sort_completions,
4787 show_completion_documentation,
4788 ignore_completion_provider,
4789 position,
4790 buffer.clone(),
4791 completions.into(),
4792 snippet_sort_order,
4793 );
4794
4795 menu.filter(
4796 if filter_completions {
4797 query.as_deref()
4798 } else {
4799 None
4800 },
4801 cx.background_executor().clone(),
4802 )
4803 .await;
4804
4805 menu.visible().then_some(menu)
4806 };
4807
4808 editor.update_in(cx, |editor, window, cx| {
4809 match editor.context_menu.borrow().as_ref() {
4810 None => {}
4811 Some(CodeContextMenu::Completions(prev_menu)) => {
4812 if prev_menu.id > id {
4813 return;
4814 }
4815 }
4816 _ => return,
4817 }
4818
4819 if editor.focus_handle.is_focused(window) && menu.is_some() {
4820 let mut menu = menu.unwrap();
4821 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4822
4823 *editor.context_menu.borrow_mut() =
4824 Some(CodeContextMenu::Completions(menu));
4825
4826 if editor.show_edit_predictions_in_menu() {
4827 editor.update_visible_inline_completion(window, cx);
4828 } else {
4829 editor.discard_inline_completion(false, cx);
4830 }
4831
4832 cx.notify();
4833 } else if editor.completion_tasks.len() <= 1 {
4834 // If there are no more completion tasks and the last menu was
4835 // empty, we should hide it.
4836 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4837 // If it was already hidden and we don't show inline
4838 // completions in the menu, we should also show the
4839 // inline-completion when available.
4840 if was_hidden && editor.show_edit_predictions_in_menu() {
4841 editor.update_visible_inline_completion(window, cx);
4842 }
4843 }
4844 })?;
4845
4846 anyhow::Ok(())
4847 }
4848 .log_err()
4849 .await
4850 });
4851
4852 self.completion_tasks.push((id, task));
4853 }
4854
4855 #[cfg(feature = "test-support")]
4856 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4857 let menu = self.context_menu.borrow();
4858 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4859 let completions = menu.completions.borrow();
4860 Some(completions.to_vec())
4861 } else {
4862 None
4863 }
4864 }
4865
4866 pub fn confirm_completion(
4867 &mut self,
4868 action: &ConfirmCompletion,
4869 window: &mut Window,
4870 cx: &mut Context<Self>,
4871 ) -> Option<Task<Result<()>>> {
4872 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4873 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4874 }
4875
4876 pub fn confirm_completion_insert(
4877 &mut self,
4878 _: &ConfirmCompletionInsert,
4879 window: &mut Window,
4880 cx: &mut Context<Self>,
4881 ) -> Option<Task<Result<()>>> {
4882 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4883 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4884 }
4885
4886 pub fn confirm_completion_replace(
4887 &mut self,
4888 _: &ConfirmCompletionReplace,
4889 window: &mut Window,
4890 cx: &mut Context<Self>,
4891 ) -> Option<Task<Result<()>>> {
4892 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4893 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4894 }
4895
4896 pub fn compose_completion(
4897 &mut self,
4898 action: &ComposeCompletion,
4899 window: &mut Window,
4900 cx: &mut Context<Self>,
4901 ) -> Option<Task<Result<()>>> {
4902 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4903 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4904 }
4905
4906 fn do_completion(
4907 &mut self,
4908 item_ix: Option<usize>,
4909 intent: CompletionIntent,
4910 window: &mut Window,
4911 cx: &mut Context<Editor>,
4912 ) -> Option<Task<Result<()>>> {
4913 use language::ToOffset as _;
4914
4915 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4916 else {
4917 return None;
4918 };
4919
4920 let candidate_id = {
4921 let entries = completions_menu.entries.borrow();
4922 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4923 if self.show_edit_predictions_in_menu() {
4924 self.discard_inline_completion(true, cx);
4925 }
4926 mat.candidate_id
4927 };
4928
4929 let buffer_handle = completions_menu.buffer;
4930 let completion = completions_menu
4931 .completions
4932 .borrow()
4933 .get(candidate_id)?
4934 .clone();
4935 cx.stop_propagation();
4936
4937 let snippet;
4938 let new_text;
4939 if completion.is_snippet() {
4940 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4941 new_text = snippet.as_ref().unwrap().text.clone();
4942 } else {
4943 snippet = None;
4944 new_text = completion.new_text.clone();
4945 };
4946
4947 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4948 let buffer = buffer_handle.read(cx);
4949 let snapshot = self.buffer.read(cx).snapshot(cx);
4950 let replace_range_multibuffer = {
4951 let excerpt = snapshot
4952 .excerpt_containing(self.selections.newest_anchor().range())
4953 .unwrap();
4954 let multibuffer_anchor = snapshot
4955 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4956 .unwrap()
4957 ..snapshot
4958 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4959 .unwrap();
4960 multibuffer_anchor.start.to_offset(&snapshot)
4961 ..multibuffer_anchor.end.to_offset(&snapshot)
4962 };
4963 let newest_anchor = self.selections.newest_anchor();
4964 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4965 return None;
4966 }
4967
4968 let old_text = buffer
4969 .text_for_range(replace_range.clone())
4970 .collect::<String>();
4971 let lookbehind = newest_anchor
4972 .start
4973 .text_anchor
4974 .to_offset(buffer)
4975 .saturating_sub(replace_range.start);
4976 let lookahead = replace_range
4977 .end
4978 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4979 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4980 let suffix = &old_text[lookbehind.min(old_text.len())..];
4981
4982 let selections = self.selections.all::<usize>(cx);
4983 let mut ranges = Vec::new();
4984 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4985
4986 for selection in &selections {
4987 let range = if selection.id == newest_anchor.id {
4988 replace_range_multibuffer.clone()
4989 } else {
4990 let mut range = selection.range();
4991
4992 // if prefix is present, don't duplicate it
4993 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4994 range.start = range.start.saturating_sub(lookbehind);
4995
4996 // if suffix is also present, mimic the newest cursor and replace it
4997 if selection.id != newest_anchor.id
4998 && snapshot.contains_str_at(range.end, suffix)
4999 {
5000 range.end += lookahead;
5001 }
5002 }
5003 range
5004 };
5005
5006 ranges.push(range);
5007
5008 if !self.linked_edit_ranges.is_empty() {
5009 let start_anchor = snapshot.anchor_before(selection.head());
5010 let end_anchor = snapshot.anchor_after(selection.tail());
5011 if let Some(ranges) = self
5012 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5013 {
5014 for (buffer, edits) in ranges {
5015 linked_edits
5016 .entry(buffer.clone())
5017 .or_default()
5018 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5019 }
5020 }
5021 }
5022 }
5023
5024 cx.emit(EditorEvent::InputHandled {
5025 utf16_range_to_replace: None,
5026 text: new_text.clone().into(),
5027 });
5028
5029 self.transact(window, cx, |this, window, cx| {
5030 if let Some(mut snippet) = snippet {
5031 snippet.text = new_text.to_string();
5032 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5033 } else {
5034 this.buffer.update(cx, |buffer, cx| {
5035 let auto_indent = match completion.insert_text_mode {
5036 Some(InsertTextMode::AS_IS) => None,
5037 _ => this.autoindent_mode.clone(),
5038 };
5039 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5040 buffer.edit(edits, auto_indent, cx);
5041 });
5042 }
5043 for (buffer, edits) in linked_edits {
5044 buffer.update(cx, |buffer, cx| {
5045 let snapshot = buffer.snapshot();
5046 let edits = edits
5047 .into_iter()
5048 .map(|(range, text)| {
5049 use text::ToPoint as TP;
5050 let end_point = TP::to_point(&range.end, &snapshot);
5051 let start_point = TP::to_point(&range.start, &snapshot);
5052 (start_point..end_point, text)
5053 })
5054 .sorted_by_key(|(range, _)| range.start);
5055 buffer.edit(edits, None, cx);
5056 })
5057 }
5058
5059 this.refresh_inline_completion(true, false, window, cx);
5060 });
5061
5062 let show_new_completions_on_confirm = completion
5063 .confirm
5064 .as_ref()
5065 .map_or(false, |confirm| confirm(intent, window, cx));
5066 if show_new_completions_on_confirm {
5067 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5068 }
5069
5070 let provider = self.completion_provider.as_ref()?;
5071 drop(completion);
5072 let apply_edits = provider.apply_additional_edits_for_completion(
5073 buffer_handle,
5074 completions_menu.completions.clone(),
5075 candidate_id,
5076 true,
5077 cx,
5078 );
5079
5080 let editor_settings = EditorSettings::get_global(cx);
5081 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5082 // After the code completion is finished, users often want to know what signatures are needed.
5083 // so we should automatically call signature_help
5084 self.show_signature_help(&ShowSignatureHelp, window, cx);
5085 }
5086
5087 Some(cx.foreground_executor().spawn(async move {
5088 apply_edits.await?;
5089 Ok(())
5090 }))
5091 }
5092
5093 pub fn toggle_code_actions(
5094 &mut self,
5095 action: &ToggleCodeActions,
5096 window: &mut Window,
5097 cx: &mut Context<Self>,
5098 ) {
5099 let quick_launch = action.quick_launch;
5100 let mut context_menu = self.context_menu.borrow_mut();
5101 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5102 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5103 // Toggle if we're selecting the same one
5104 *context_menu = None;
5105 cx.notify();
5106 return;
5107 } else {
5108 // Otherwise, clear it and start a new one
5109 *context_menu = None;
5110 cx.notify();
5111 }
5112 }
5113 drop(context_menu);
5114 let snapshot = self.snapshot(window, cx);
5115 let deployed_from_indicator = action.deployed_from_indicator;
5116 let mut task = self.code_actions_task.take();
5117 let action = action.clone();
5118 cx.spawn_in(window, async move |editor, cx| {
5119 while let Some(prev_task) = task {
5120 prev_task.await.log_err();
5121 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5122 }
5123
5124 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5125 if editor.focus_handle.is_focused(window) {
5126 let multibuffer_point = action
5127 .deployed_from_indicator
5128 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5129 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5130 let (buffer, buffer_row) = snapshot
5131 .buffer_snapshot
5132 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5133 .and_then(|(buffer_snapshot, range)| {
5134 editor
5135 .buffer
5136 .read(cx)
5137 .buffer(buffer_snapshot.remote_id())
5138 .map(|buffer| (buffer, range.start.row))
5139 })?;
5140 let (_, code_actions) = editor
5141 .available_code_actions
5142 .clone()
5143 .and_then(|(location, code_actions)| {
5144 let snapshot = location.buffer.read(cx).snapshot();
5145 let point_range = location.range.to_point(&snapshot);
5146 let point_range = point_range.start.row..=point_range.end.row;
5147 if point_range.contains(&buffer_row) {
5148 Some((location, code_actions))
5149 } else {
5150 None
5151 }
5152 })
5153 .unzip();
5154 let buffer_id = buffer.read(cx).remote_id();
5155 let tasks = editor
5156 .tasks
5157 .get(&(buffer_id, buffer_row))
5158 .map(|t| Arc::new(t.to_owned()));
5159 if tasks.is_none() && code_actions.is_none() {
5160 return None;
5161 }
5162
5163 editor.completion_tasks.clear();
5164 editor.discard_inline_completion(false, cx);
5165 let task_context =
5166 tasks
5167 .as_ref()
5168 .zip(editor.project.clone())
5169 .map(|(tasks, project)| {
5170 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5171 });
5172
5173 Some(cx.spawn_in(window, async move |editor, cx| {
5174 let task_context = match task_context {
5175 Some(task_context) => task_context.await,
5176 None => None,
5177 };
5178 let resolved_tasks =
5179 tasks
5180 .zip(task_context.clone())
5181 .map(|(tasks, task_context)| ResolvedTasks {
5182 templates: tasks.resolve(&task_context).collect(),
5183 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5184 multibuffer_point.row,
5185 tasks.column,
5186 )),
5187 });
5188 let spawn_straight_away = quick_launch
5189 && resolved_tasks
5190 .as_ref()
5191 .map_or(false, |tasks| tasks.templates.len() == 1)
5192 && code_actions
5193 .as_ref()
5194 .map_or(true, |actions| actions.is_empty());
5195 let debug_scenarios = editor.update(cx, |editor, cx| {
5196 if cx.has_flag::<DebuggerFeatureFlag>() {
5197 maybe!({
5198 let project = editor.project.as_ref()?;
5199 let dap_store = project.read(cx).dap_store();
5200 let mut scenarios = vec![];
5201 let resolved_tasks = resolved_tasks.as_ref()?;
5202 let debug_adapter: SharedString = buffer
5203 .read(cx)
5204 .language()?
5205 .context_provider()?
5206 .debug_adapter()?
5207 .into();
5208 dap_store.update(cx, |this, cx| {
5209 for (_, task) in &resolved_tasks.templates {
5210 if let Some(scenario) = this
5211 .debug_scenario_for_build_task(
5212 task.resolved.clone(),
5213 SharedString::from(
5214 task.original_task().label.clone(),
5215 ),
5216 debug_adapter.clone(),
5217 cx,
5218 )
5219 {
5220 scenarios.push(scenario);
5221 }
5222 }
5223 });
5224 Some(scenarios)
5225 })
5226 .unwrap_or_default()
5227 } else {
5228 vec![]
5229 }
5230 })?;
5231 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5232 *editor.context_menu.borrow_mut() =
5233 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5234 buffer,
5235 actions: CodeActionContents::new(
5236 resolved_tasks,
5237 code_actions,
5238 debug_scenarios,
5239 task_context.unwrap_or_default(),
5240 ),
5241 selected_item: Default::default(),
5242 scroll_handle: UniformListScrollHandle::default(),
5243 deployed_from_indicator,
5244 }));
5245 if spawn_straight_away {
5246 if let Some(task) = editor.confirm_code_action(
5247 &ConfirmCodeAction { item_ix: Some(0) },
5248 window,
5249 cx,
5250 ) {
5251 cx.notify();
5252 return task;
5253 }
5254 }
5255 cx.notify();
5256 Task::ready(Ok(()))
5257 }) {
5258 task.await
5259 } else {
5260 Ok(())
5261 }
5262 }))
5263 } else {
5264 Some(Task::ready(Ok(())))
5265 }
5266 })?;
5267 if let Some(task) = spawned_test_task {
5268 task.await?;
5269 }
5270
5271 Ok::<_, anyhow::Error>(())
5272 })
5273 .detach_and_log_err(cx);
5274 }
5275
5276 pub fn confirm_code_action(
5277 &mut self,
5278 action: &ConfirmCodeAction,
5279 window: &mut Window,
5280 cx: &mut Context<Self>,
5281 ) -> Option<Task<Result<()>>> {
5282 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5283
5284 let actions_menu =
5285 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5286 menu
5287 } else {
5288 return None;
5289 };
5290
5291 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5292 let action = actions_menu.actions.get(action_ix)?;
5293 let title = action.label();
5294 let buffer = actions_menu.buffer;
5295 let workspace = self.workspace()?;
5296
5297 match action {
5298 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5299 workspace.update(cx, |workspace, cx| {
5300 workspace.schedule_resolved_task(
5301 task_source_kind,
5302 resolved_task,
5303 false,
5304 window,
5305 cx,
5306 );
5307
5308 Some(Task::ready(Ok(())))
5309 })
5310 }
5311 CodeActionsItem::CodeAction {
5312 excerpt_id,
5313 action,
5314 provider,
5315 } => {
5316 let apply_code_action =
5317 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5318 let workspace = workspace.downgrade();
5319 Some(cx.spawn_in(window, async move |editor, cx| {
5320 let project_transaction = apply_code_action.await?;
5321 Self::open_project_transaction(
5322 &editor,
5323 workspace,
5324 project_transaction,
5325 title,
5326 cx,
5327 )
5328 .await
5329 }))
5330 }
5331 CodeActionsItem::DebugScenario(scenario) => {
5332 let context = actions_menu.actions.context.clone();
5333
5334 workspace.update(cx, |workspace, cx| {
5335 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5336 });
5337 Some(Task::ready(Ok(())))
5338 }
5339 }
5340 }
5341
5342 pub async fn open_project_transaction(
5343 this: &WeakEntity<Editor>,
5344 workspace: WeakEntity<Workspace>,
5345 transaction: ProjectTransaction,
5346 title: String,
5347 cx: &mut AsyncWindowContext,
5348 ) -> Result<()> {
5349 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5350 cx.update(|_, cx| {
5351 entries.sort_unstable_by_key(|(buffer, _)| {
5352 buffer.read(cx).file().map(|f| f.path().clone())
5353 });
5354 })?;
5355
5356 // If the project transaction's edits are all contained within this editor, then
5357 // avoid opening a new editor to display them.
5358
5359 if let Some((buffer, transaction)) = entries.first() {
5360 if entries.len() == 1 {
5361 let excerpt = this.update(cx, |editor, cx| {
5362 editor
5363 .buffer()
5364 .read(cx)
5365 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5366 })?;
5367 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5368 if excerpted_buffer == *buffer {
5369 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5370 let excerpt_range = excerpt_range.to_offset(buffer);
5371 buffer
5372 .edited_ranges_for_transaction::<usize>(transaction)
5373 .all(|range| {
5374 excerpt_range.start <= range.start
5375 && excerpt_range.end >= range.end
5376 })
5377 })?;
5378
5379 if all_edits_within_excerpt {
5380 return Ok(());
5381 }
5382 }
5383 }
5384 }
5385 } else {
5386 return Ok(());
5387 }
5388
5389 let mut ranges_to_highlight = Vec::new();
5390 let excerpt_buffer = cx.new(|cx| {
5391 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5392 for (buffer_handle, transaction) in &entries {
5393 let edited_ranges = buffer_handle
5394 .read(cx)
5395 .edited_ranges_for_transaction::<Point>(transaction)
5396 .collect::<Vec<_>>();
5397 let (ranges, _) = multibuffer.set_excerpts_for_path(
5398 PathKey::for_buffer(buffer_handle, cx),
5399 buffer_handle.clone(),
5400 edited_ranges,
5401 DEFAULT_MULTIBUFFER_CONTEXT,
5402 cx,
5403 );
5404
5405 ranges_to_highlight.extend(ranges);
5406 }
5407 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5408 multibuffer
5409 })?;
5410
5411 workspace.update_in(cx, |workspace, window, cx| {
5412 let project = workspace.project().clone();
5413 let editor =
5414 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5415 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5416 editor.update(cx, |editor, cx| {
5417 editor.highlight_background::<Self>(
5418 &ranges_to_highlight,
5419 |theme| theme.editor_highlighted_line_background,
5420 cx,
5421 );
5422 });
5423 })?;
5424
5425 Ok(())
5426 }
5427
5428 pub fn clear_code_action_providers(&mut self) {
5429 self.code_action_providers.clear();
5430 self.available_code_actions.take();
5431 }
5432
5433 pub fn add_code_action_provider(
5434 &mut self,
5435 provider: Rc<dyn CodeActionProvider>,
5436 window: &mut Window,
5437 cx: &mut Context<Self>,
5438 ) {
5439 if self
5440 .code_action_providers
5441 .iter()
5442 .any(|existing_provider| existing_provider.id() == provider.id())
5443 {
5444 return;
5445 }
5446
5447 self.code_action_providers.push(provider);
5448 self.refresh_code_actions(window, cx);
5449 }
5450
5451 pub fn remove_code_action_provider(
5452 &mut self,
5453 id: Arc<str>,
5454 window: &mut Window,
5455 cx: &mut Context<Self>,
5456 ) {
5457 self.code_action_providers
5458 .retain(|provider| provider.id() != id);
5459 self.refresh_code_actions(window, cx);
5460 }
5461
5462 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5463 let newest_selection = self.selections.newest_anchor().clone();
5464 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5465 let buffer = self.buffer.read(cx);
5466 if newest_selection.head().diff_base_anchor.is_some() {
5467 return None;
5468 }
5469 let (start_buffer, start) =
5470 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5471 let (end_buffer, end) =
5472 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5473 if start_buffer != end_buffer {
5474 return None;
5475 }
5476
5477 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5478 cx.background_executor()
5479 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5480 .await;
5481
5482 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5483 let providers = this.code_action_providers.clone();
5484 let tasks = this
5485 .code_action_providers
5486 .iter()
5487 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5488 .collect::<Vec<_>>();
5489 (providers, tasks)
5490 })?;
5491
5492 let mut actions = Vec::new();
5493 for (provider, provider_actions) in
5494 providers.into_iter().zip(future::join_all(tasks).await)
5495 {
5496 if let Some(provider_actions) = provider_actions.log_err() {
5497 actions.extend(provider_actions.into_iter().map(|action| {
5498 AvailableCodeAction {
5499 excerpt_id: newest_selection.start.excerpt_id,
5500 action,
5501 provider: provider.clone(),
5502 }
5503 }));
5504 }
5505 }
5506
5507 this.update(cx, |this, cx| {
5508 this.available_code_actions = if actions.is_empty() {
5509 None
5510 } else {
5511 Some((
5512 Location {
5513 buffer: start_buffer,
5514 range: start..end,
5515 },
5516 actions.into(),
5517 ))
5518 };
5519 cx.notify();
5520 })
5521 }));
5522 None
5523 }
5524
5525 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5526 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5527 self.show_git_blame_inline = false;
5528
5529 self.show_git_blame_inline_delay_task =
5530 Some(cx.spawn_in(window, async move |this, cx| {
5531 cx.background_executor().timer(delay).await;
5532
5533 this.update(cx, |this, cx| {
5534 this.show_git_blame_inline = true;
5535 cx.notify();
5536 })
5537 .log_err();
5538 }));
5539 }
5540 }
5541
5542 fn show_blame_popover(
5543 &mut self,
5544 blame_entry: &BlameEntry,
5545 position: gpui::Point<Pixels>,
5546 cx: &mut Context<Self>,
5547 ) {
5548 if let Some(state) = &mut self.inline_blame_popover {
5549 state.hide_task.take();
5550 cx.notify();
5551 } else {
5552 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5553 let show_task = cx.spawn(async move |editor, cx| {
5554 cx.background_executor()
5555 .timer(std::time::Duration::from_millis(delay))
5556 .await;
5557 editor
5558 .update(cx, |editor, cx| {
5559 if let Some(state) = &mut editor.inline_blame_popover {
5560 state.show_task = None;
5561 cx.notify();
5562 }
5563 })
5564 .ok();
5565 });
5566 let Some(blame) = self.blame.as_ref() else {
5567 return;
5568 };
5569 let blame = blame.read(cx);
5570 let details = blame.details_for_entry(&blame_entry);
5571 let markdown = cx.new(|cx| {
5572 Markdown::new(
5573 details
5574 .as_ref()
5575 .map(|message| message.message.clone())
5576 .unwrap_or_default(),
5577 None,
5578 None,
5579 cx,
5580 )
5581 });
5582 self.inline_blame_popover = Some(InlineBlamePopover {
5583 position,
5584 show_task: Some(show_task),
5585 hide_task: None,
5586 popover_bounds: None,
5587 popover_state: InlineBlamePopoverState {
5588 scroll_handle: ScrollHandle::new(),
5589 commit_message: details,
5590 markdown,
5591 },
5592 });
5593 }
5594 }
5595
5596 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5597 if let Some(state) = &mut self.inline_blame_popover {
5598 if state.show_task.is_some() {
5599 self.inline_blame_popover.take();
5600 cx.notify();
5601 } else {
5602 let hide_task = cx.spawn(async move |editor, cx| {
5603 cx.background_executor()
5604 .timer(std::time::Duration::from_millis(100))
5605 .await;
5606 editor
5607 .update(cx, |editor, cx| {
5608 editor.inline_blame_popover.take();
5609 cx.notify();
5610 })
5611 .ok();
5612 });
5613 state.hide_task = Some(hide_task);
5614 }
5615 }
5616 }
5617
5618 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5619 if self.pending_rename.is_some() {
5620 return None;
5621 }
5622
5623 let provider = self.semantics_provider.clone()?;
5624 let buffer = self.buffer.read(cx);
5625 let newest_selection = self.selections.newest_anchor().clone();
5626 let cursor_position = newest_selection.head();
5627 let (cursor_buffer, cursor_buffer_position) =
5628 buffer.text_anchor_for_position(cursor_position, cx)?;
5629 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5630 if cursor_buffer != tail_buffer {
5631 return None;
5632 }
5633 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5634 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5635 cx.background_executor()
5636 .timer(Duration::from_millis(debounce))
5637 .await;
5638
5639 let highlights = if let Some(highlights) = cx
5640 .update(|cx| {
5641 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5642 })
5643 .ok()
5644 .flatten()
5645 {
5646 highlights.await.log_err()
5647 } else {
5648 None
5649 };
5650
5651 if let Some(highlights) = highlights {
5652 this.update(cx, |this, cx| {
5653 if this.pending_rename.is_some() {
5654 return;
5655 }
5656
5657 let buffer_id = cursor_position.buffer_id;
5658 let buffer = this.buffer.read(cx);
5659 if !buffer
5660 .text_anchor_for_position(cursor_position, cx)
5661 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5662 {
5663 return;
5664 }
5665
5666 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5667 let mut write_ranges = Vec::new();
5668 let mut read_ranges = Vec::new();
5669 for highlight in highlights {
5670 for (excerpt_id, excerpt_range) in
5671 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5672 {
5673 let start = highlight
5674 .range
5675 .start
5676 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5677 let end = highlight
5678 .range
5679 .end
5680 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5681 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5682 continue;
5683 }
5684
5685 let range = Anchor {
5686 buffer_id,
5687 excerpt_id,
5688 text_anchor: start,
5689 diff_base_anchor: None,
5690 }..Anchor {
5691 buffer_id,
5692 excerpt_id,
5693 text_anchor: end,
5694 diff_base_anchor: None,
5695 };
5696 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5697 write_ranges.push(range);
5698 } else {
5699 read_ranges.push(range);
5700 }
5701 }
5702 }
5703
5704 this.highlight_background::<DocumentHighlightRead>(
5705 &read_ranges,
5706 |theme| theme.editor_document_highlight_read_background,
5707 cx,
5708 );
5709 this.highlight_background::<DocumentHighlightWrite>(
5710 &write_ranges,
5711 |theme| theme.editor_document_highlight_write_background,
5712 cx,
5713 );
5714 cx.notify();
5715 })
5716 .log_err();
5717 }
5718 }));
5719 None
5720 }
5721
5722 fn prepare_highlight_query_from_selection(
5723 &mut self,
5724 cx: &mut Context<Editor>,
5725 ) -> Option<(String, Range<Anchor>)> {
5726 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5727 return None;
5728 }
5729 if !EditorSettings::get_global(cx).selection_highlight {
5730 return None;
5731 }
5732 if self.selections.count() != 1 || self.selections.line_mode {
5733 return None;
5734 }
5735 let selection = self.selections.newest::<Point>(cx);
5736 if selection.is_empty() || selection.start.row != selection.end.row {
5737 return None;
5738 }
5739 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5740 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5741 let query = multi_buffer_snapshot
5742 .text_for_range(selection_anchor_range.clone())
5743 .collect::<String>();
5744 if query.trim().is_empty() {
5745 return None;
5746 }
5747 Some((query, selection_anchor_range))
5748 }
5749
5750 fn update_selection_occurrence_highlights(
5751 &mut self,
5752 query_text: String,
5753 query_range: Range<Anchor>,
5754 multi_buffer_range_to_query: Range<Point>,
5755 use_debounce: bool,
5756 window: &mut Window,
5757 cx: &mut Context<Editor>,
5758 ) -> Task<()> {
5759 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5760 cx.spawn_in(window, async move |editor, cx| {
5761 if use_debounce {
5762 cx.background_executor()
5763 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5764 .await;
5765 }
5766 let match_task = cx.background_spawn(async move {
5767 let buffer_ranges = multi_buffer_snapshot
5768 .range_to_buffer_ranges(multi_buffer_range_to_query)
5769 .into_iter()
5770 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5771 let mut match_ranges = Vec::new();
5772 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5773 match_ranges.extend(
5774 project::search::SearchQuery::text(
5775 query_text.clone(),
5776 false,
5777 false,
5778 false,
5779 Default::default(),
5780 Default::default(),
5781 false,
5782 None,
5783 )
5784 .unwrap()
5785 .search(&buffer_snapshot, Some(search_range.clone()))
5786 .await
5787 .into_iter()
5788 .filter_map(|match_range| {
5789 let match_start = buffer_snapshot
5790 .anchor_after(search_range.start + match_range.start);
5791 let match_end =
5792 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5793 let match_anchor_range = Anchor::range_in_buffer(
5794 excerpt_id,
5795 buffer_snapshot.remote_id(),
5796 match_start..match_end,
5797 );
5798 (match_anchor_range != query_range).then_some(match_anchor_range)
5799 }),
5800 );
5801 }
5802 match_ranges
5803 });
5804 let match_ranges = match_task.await;
5805 editor
5806 .update_in(cx, |editor, _, cx| {
5807 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5808 if !match_ranges.is_empty() {
5809 editor.highlight_background::<SelectedTextHighlight>(
5810 &match_ranges,
5811 |theme| theme.editor_document_highlight_bracket_background,
5812 cx,
5813 )
5814 }
5815 })
5816 .log_err();
5817 })
5818 }
5819
5820 fn refresh_selected_text_highlights(
5821 &mut self,
5822 on_buffer_edit: bool,
5823 window: &mut Window,
5824 cx: &mut Context<Editor>,
5825 ) {
5826 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5827 else {
5828 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5829 self.quick_selection_highlight_task.take();
5830 self.debounced_selection_highlight_task.take();
5831 return;
5832 };
5833 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5834 if on_buffer_edit
5835 || self
5836 .quick_selection_highlight_task
5837 .as_ref()
5838 .map_or(true, |(prev_anchor_range, _)| {
5839 prev_anchor_range != &query_range
5840 })
5841 {
5842 let multi_buffer_visible_start = self
5843 .scroll_manager
5844 .anchor()
5845 .anchor
5846 .to_point(&multi_buffer_snapshot);
5847 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5848 multi_buffer_visible_start
5849 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5850 Bias::Left,
5851 );
5852 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5853 self.quick_selection_highlight_task = Some((
5854 query_range.clone(),
5855 self.update_selection_occurrence_highlights(
5856 query_text.clone(),
5857 query_range.clone(),
5858 multi_buffer_visible_range,
5859 false,
5860 window,
5861 cx,
5862 ),
5863 ));
5864 }
5865 if on_buffer_edit
5866 || self
5867 .debounced_selection_highlight_task
5868 .as_ref()
5869 .map_or(true, |(prev_anchor_range, _)| {
5870 prev_anchor_range != &query_range
5871 })
5872 {
5873 let multi_buffer_start = multi_buffer_snapshot
5874 .anchor_before(0)
5875 .to_point(&multi_buffer_snapshot);
5876 let multi_buffer_end = multi_buffer_snapshot
5877 .anchor_after(multi_buffer_snapshot.len())
5878 .to_point(&multi_buffer_snapshot);
5879 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5880 self.debounced_selection_highlight_task = Some((
5881 query_range.clone(),
5882 self.update_selection_occurrence_highlights(
5883 query_text,
5884 query_range,
5885 multi_buffer_full_range,
5886 true,
5887 window,
5888 cx,
5889 ),
5890 ));
5891 }
5892 }
5893
5894 pub fn refresh_inline_completion(
5895 &mut self,
5896 debounce: bool,
5897 user_requested: bool,
5898 window: &mut Window,
5899 cx: &mut Context<Self>,
5900 ) -> Option<()> {
5901 let provider = self.edit_prediction_provider()?;
5902 let cursor = self.selections.newest_anchor().head();
5903 let (buffer, cursor_buffer_position) =
5904 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5905
5906 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5907 self.discard_inline_completion(false, cx);
5908 return None;
5909 }
5910
5911 if !user_requested
5912 && (!self.should_show_edit_predictions()
5913 || !self.is_focused(window)
5914 || buffer.read(cx).is_empty())
5915 {
5916 self.discard_inline_completion(false, cx);
5917 return None;
5918 }
5919
5920 self.update_visible_inline_completion(window, cx);
5921 provider.refresh(
5922 self.project.clone(),
5923 buffer,
5924 cursor_buffer_position,
5925 debounce,
5926 cx,
5927 );
5928 Some(())
5929 }
5930
5931 fn show_edit_predictions_in_menu(&self) -> bool {
5932 match self.edit_prediction_settings {
5933 EditPredictionSettings::Disabled => false,
5934 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5935 }
5936 }
5937
5938 pub fn edit_predictions_enabled(&self) -> bool {
5939 match self.edit_prediction_settings {
5940 EditPredictionSettings::Disabled => false,
5941 EditPredictionSettings::Enabled { .. } => true,
5942 }
5943 }
5944
5945 fn edit_prediction_requires_modifier(&self) -> bool {
5946 match self.edit_prediction_settings {
5947 EditPredictionSettings::Disabled => false,
5948 EditPredictionSettings::Enabled {
5949 preview_requires_modifier,
5950 ..
5951 } => preview_requires_modifier,
5952 }
5953 }
5954
5955 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5956 if self.edit_prediction_provider.is_none() {
5957 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5958 } else {
5959 let selection = self.selections.newest_anchor();
5960 let cursor = selection.head();
5961
5962 if let Some((buffer, cursor_buffer_position)) =
5963 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5964 {
5965 self.edit_prediction_settings =
5966 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5967 }
5968 }
5969 }
5970
5971 fn edit_prediction_settings_at_position(
5972 &self,
5973 buffer: &Entity<Buffer>,
5974 buffer_position: language::Anchor,
5975 cx: &App,
5976 ) -> EditPredictionSettings {
5977 if !self.mode.is_full()
5978 || !self.show_inline_completions_override.unwrap_or(true)
5979 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5980 {
5981 return EditPredictionSettings::Disabled;
5982 }
5983
5984 let buffer = buffer.read(cx);
5985
5986 let file = buffer.file();
5987
5988 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5989 return EditPredictionSettings::Disabled;
5990 };
5991
5992 let by_provider = matches!(
5993 self.menu_inline_completions_policy,
5994 MenuInlineCompletionsPolicy::ByProvider
5995 );
5996
5997 let show_in_menu = by_provider
5998 && self
5999 .edit_prediction_provider
6000 .as_ref()
6001 .map_or(false, |provider| {
6002 provider.provider.show_completions_in_menu()
6003 });
6004
6005 let preview_requires_modifier =
6006 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6007
6008 EditPredictionSettings::Enabled {
6009 show_in_menu,
6010 preview_requires_modifier,
6011 }
6012 }
6013
6014 fn should_show_edit_predictions(&self) -> bool {
6015 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6016 }
6017
6018 pub fn edit_prediction_preview_is_active(&self) -> bool {
6019 matches!(
6020 self.edit_prediction_preview,
6021 EditPredictionPreview::Active { .. }
6022 )
6023 }
6024
6025 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6026 let cursor = self.selections.newest_anchor().head();
6027 if let Some((buffer, cursor_position)) =
6028 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6029 {
6030 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6031 } else {
6032 false
6033 }
6034 }
6035
6036 fn edit_predictions_enabled_in_buffer(
6037 &self,
6038 buffer: &Entity<Buffer>,
6039 buffer_position: language::Anchor,
6040 cx: &App,
6041 ) -> bool {
6042 maybe!({
6043 if self.read_only(cx) {
6044 return Some(false);
6045 }
6046 let provider = self.edit_prediction_provider()?;
6047 if !provider.is_enabled(&buffer, buffer_position, cx) {
6048 return Some(false);
6049 }
6050 let buffer = buffer.read(cx);
6051 let Some(file) = buffer.file() else {
6052 return Some(true);
6053 };
6054 let settings = all_language_settings(Some(file), cx);
6055 Some(settings.edit_predictions_enabled_for_file(file, cx))
6056 })
6057 .unwrap_or(false)
6058 }
6059
6060 fn cycle_inline_completion(
6061 &mut self,
6062 direction: Direction,
6063 window: &mut Window,
6064 cx: &mut Context<Self>,
6065 ) -> Option<()> {
6066 let provider = self.edit_prediction_provider()?;
6067 let cursor = self.selections.newest_anchor().head();
6068 let (buffer, cursor_buffer_position) =
6069 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6070 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6071 return None;
6072 }
6073
6074 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6075 self.update_visible_inline_completion(window, cx);
6076
6077 Some(())
6078 }
6079
6080 pub fn show_inline_completion(
6081 &mut self,
6082 _: &ShowEditPrediction,
6083 window: &mut Window,
6084 cx: &mut Context<Self>,
6085 ) {
6086 if !self.has_active_inline_completion() {
6087 self.refresh_inline_completion(false, true, window, cx);
6088 return;
6089 }
6090
6091 self.update_visible_inline_completion(window, cx);
6092 }
6093
6094 pub fn display_cursor_names(
6095 &mut self,
6096 _: &DisplayCursorNames,
6097 window: &mut Window,
6098 cx: &mut Context<Self>,
6099 ) {
6100 self.show_cursor_names(window, cx);
6101 }
6102
6103 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6104 self.show_cursor_names = true;
6105 cx.notify();
6106 cx.spawn_in(window, async move |this, cx| {
6107 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6108 this.update(cx, |this, cx| {
6109 this.show_cursor_names = false;
6110 cx.notify()
6111 })
6112 .ok()
6113 })
6114 .detach();
6115 }
6116
6117 pub fn next_edit_prediction(
6118 &mut self,
6119 _: &NextEditPrediction,
6120 window: &mut Window,
6121 cx: &mut Context<Self>,
6122 ) {
6123 if self.has_active_inline_completion() {
6124 self.cycle_inline_completion(Direction::Next, window, cx);
6125 } else {
6126 let is_copilot_disabled = self
6127 .refresh_inline_completion(false, true, window, cx)
6128 .is_none();
6129 if is_copilot_disabled {
6130 cx.propagate();
6131 }
6132 }
6133 }
6134
6135 pub fn previous_edit_prediction(
6136 &mut self,
6137 _: &PreviousEditPrediction,
6138 window: &mut Window,
6139 cx: &mut Context<Self>,
6140 ) {
6141 if self.has_active_inline_completion() {
6142 self.cycle_inline_completion(Direction::Prev, window, cx);
6143 } else {
6144 let is_copilot_disabled = self
6145 .refresh_inline_completion(false, true, window, cx)
6146 .is_none();
6147 if is_copilot_disabled {
6148 cx.propagate();
6149 }
6150 }
6151 }
6152
6153 pub fn accept_edit_prediction(
6154 &mut self,
6155 _: &AcceptEditPrediction,
6156 window: &mut Window,
6157 cx: &mut Context<Self>,
6158 ) {
6159 if self.show_edit_predictions_in_menu() {
6160 self.hide_context_menu(window, cx);
6161 }
6162
6163 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6164 return;
6165 };
6166
6167 self.report_inline_completion_event(
6168 active_inline_completion.completion_id.clone(),
6169 true,
6170 cx,
6171 );
6172
6173 match &active_inline_completion.completion {
6174 InlineCompletion::Move { target, .. } => {
6175 let target = *target;
6176
6177 if let Some(position_map) = &self.last_position_map {
6178 if position_map
6179 .visible_row_range
6180 .contains(&target.to_display_point(&position_map.snapshot).row())
6181 || !self.edit_prediction_requires_modifier()
6182 {
6183 self.unfold_ranges(&[target..target], true, false, cx);
6184 // Note that this is also done in vim's handler of the Tab action.
6185 self.change_selections(
6186 Some(Autoscroll::newest()),
6187 window,
6188 cx,
6189 |selections| {
6190 selections.select_anchor_ranges([target..target]);
6191 },
6192 );
6193 self.clear_row_highlights::<EditPredictionPreview>();
6194
6195 self.edit_prediction_preview
6196 .set_previous_scroll_position(None);
6197 } else {
6198 self.edit_prediction_preview
6199 .set_previous_scroll_position(Some(
6200 position_map.snapshot.scroll_anchor,
6201 ));
6202
6203 self.highlight_rows::<EditPredictionPreview>(
6204 target..target,
6205 cx.theme().colors().editor_highlighted_line_background,
6206 RowHighlightOptions {
6207 autoscroll: true,
6208 ..Default::default()
6209 },
6210 cx,
6211 );
6212 self.request_autoscroll(Autoscroll::fit(), cx);
6213 }
6214 }
6215 }
6216 InlineCompletion::Edit { edits, .. } => {
6217 if let Some(provider) = self.edit_prediction_provider() {
6218 provider.accept(cx);
6219 }
6220
6221 let snapshot = self.buffer.read(cx).snapshot(cx);
6222 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6223
6224 self.buffer.update(cx, |buffer, cx| {
6225 buffer.edit(edits.iter().cloned(), None, cx)
6226 });
6227
6228 self.change_selections(None, window, cx, |s| {
6229 s.select_anchor_ranges([last_edit_end..last_edit_end])
6230 });
6231
6232 self.update_visible_inline_completion(window, cx);
6233 if self.active_inline_completion.is_none() {
6234 self.refresh_inline_completion(true, true, window, cx);
6235 }
6236
6237 cx.notify();
6238 }
6239 }
6240
6241 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6242 }
6243
6244 pub fn accept_partial_inline_completion(
6245 &mut self,
6246 _: &AcceptPartialEditPrediction,
6247 window: &mut Window,
6248 cx: &mut Context<Self>,
6249 ) {
6250 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6251 return;
6252 };
6253 if self.selections.count() != 1 {
6254 return;
6255 }
6256
6257 self.report_inline_completion_event(
6258 active_inline_completion.completion_id.clone(),
6259 true,
6260 cx,
6261 );
6262
6263 match &active_inline_completion.completion {
6264 InlineCompletion::Move { target, .. } => {
6265 let target = *target;
6266 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6267 selections.select_anchor_ranges([target..target]);
6268 });
6269 }
6270 InlineCompletion::Edit { edits, .. } => {
6271 // Find an insertion that starts at the cursor position.
6272 let snapshot = self.buffer.read(cx).snapshot(cx);
6273 let cursor_offset = self.selections.newest::<usize>(cx).head();
6274 let insertion = edits.iter().find_map(|(range, text)| {
6275 let range = range.to_offset(&snapshot);
6276 if range.is_empty() && range.start == cursor_offset {
6277 Some(text)
6278 } else {
6279 None
6280 }
6281 });
6282
6283 if let Some(text) = insertion {
6284 let mut partial_completion = text
6285 .chars()
6286 .by_ref()
6287 .take_while(|c| c.is_alphabetic())
6288 .collect::<String>();
6289 if partial_completion.is_empty() {
6290 partial_completion = text
6291 .chars()
6292 .by_ref()
6293 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6294 .collect::<String>();
6295 }
6296
6297 cx.emit(EditorEvent::InputHandled {
6298 utf16_range_to_replace: None,
6299 text: partial_completion.clone().into(),
6300 });
6301
6302 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6303
6304 self.refresh_inline_completion(true, true, window, cx);
6305 cx.notify();
6306 } else {
6307 self.accept_edit_prediction(&Default::default(), window, cx);
6308 }
6309 }
6310 }
6311 }
6312
6313 fn discard_inline_completion(
6314 &mut self,
6315 should_report_inline_completion_event: bool,
6316 cx: &mut Context<Self>,
6317 ) -> bool {
6318 if should_report_inline_completion_event {
6319 let completion_id = self
6320 .active_inline_completion
6321 .as_ref()
6322 .and_then(|active_completion| active_completion.completion_id.clone());
6323
6324 self.report_inline_completion_event(completion_id, false, cx);
6325 }
6326
6327 if let Some(provider) = self.edit_prediction_provider() {
6328 provider.discard(cx);
6329 }
6330
6331 self.take_active_inline_completion(cx)
6332 }
6333
6334 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6335 let Some(provider) = self.edit_prediction_provider() else {
6336 return;
6337 };
6338
6339 let Some((_, buffer, _)) = self
6340 .buffer
6341 .read(cx)
6342 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6343 else {
6344 return;
6345 };
6346
6347 let extension = buffer
6348 .read(cx)
6349 .file()
6350 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6351
6352 let event_type = match accepted {
6353 true => "Edit Prediction Accepted",
6354 false => "Edit Prediction Discarded",
6355 };
6356 telemetry::event!(
6357 event_type,
6358 provider = provider.name(),
6359 prediction_id = id,
6360 suggestion_accepted = accepted,
6361 file_extension = extension,
6362 );
6363 }
6364
6365 pub fn has_active_inline_completion(&self) -> bool {
6366 self.active_inline_completion.is_some()
6367 }
6368
6369 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6370 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6371 return false;
6372 };
6373
6374 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6375 self.clear_highlights::<InlineCompletionHighlight>(cx);
6376 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6377 true
6378 }
6379
6380 /// Returns true when we're displaying the edit prediction popover below the cursor
6381 /// like we are not previewing and the LSP autocomplete menu is visible
6382 /// or we are in `when_holding_modifier` mode.
6383 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6384 if self.edit_prediction_preview_is_active()
6385 || !self.show_edit_predictions_in_menu()
6386 || !self.edit_predictions_enabled()
6387 {
6388 return false;
6389 }
6390
6391 if self.has_visible_completions_menu() {
6392 return true;
6393 }
6394
6395 has_completion && self.edit_prediction_requires_modifier()
6396 }
6397
6398 fn handle_modifiers_changed(
6399 &mut self,
6400 modifiers: Modifiers,
6401 position_map: &PositionMap,
6402 window: &mut Window,
6403 cx: &mut Context<Self>,
6404 ) {
6405 if self.show_edit_predictions_in_menu() {
6406 self.update_edit_prediction_preview(&modifiers, window, cx);
6407 }
6408
6409 self.update_selection_mode(&modifiers, position_map, window, cx);
6410
6411 let mouse_position = window.mouse_position();
6412 if !position_map.text_hitbox.is_hovered(window) {
6413 return;
6414 }
6415
6416 self.update_hovered_link(
6417 position_map.point_for_position(mouse_position),
6418 &position_map.snapshot,
6419 modifiers,
6420 window,
6421 cx,
6422 )
6423 }
6424
6425 fn update_selection_mode(
6426 &mut self,
6427 modifiers: &Modifiers,
6428 position_map: &PositionMap,
6429 window: &mut Window,
6430 cx: &mut Context<Self>,
6431 ) {
6432 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6433 return;
6434 }
6435
6436 let mouse_position = window.mouse_position();
6437 let point_for_position = position_map.point_for_position(mouse_position);
6438 let position = point_for_position.previous_valid;
6439
6440 self.select(
6441 SelectPhase::BeginColumnar {
6442 position,
6443 reset: false,
6444 goal_column: point_for_position.exact_unclipped.column(),
6445 },
6446 window,
6447 cx,
6448 );
6449 }
6450
6451 fn update_edit_prediction_preview(
6452 &mut self,
6453 modifiers: &Modifiers,
6454 window: &mut Window,
6455 cx: &mut Context<Self>,
6456 ) {
6457 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6458 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6459 return;
6460 };
6461
6462 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6463 if matches!(
6464 self.edit_prediction_preview,
6465 EditPredictionPreview::Inactive { .. }
6466 ) {
6467 self.edit_prediction_preview = EditPredictionPreview::Active {
6468 previous_scroll_position: None,
6469 since: Instant::now(),
6470 };
6471
6472 self.update_visible_inline_completion(window, cx);
6473 cx.notify();
6474 }
6475 } else if let EditPredictionPreview::Active {
6476 previous_scroll_position,
6477 since,
6478 } = self.edit_prediction_preview
6479 {
6480 if let (Some(previous_scroll_position), Some(position_map)) =
6481 (previous_scroll_position, self.last_position_map.as_ref())
6482 {
6483 self.set_scroll_position(
6484 previous_scroll_position
6485 .scroll_position(&position_map.snapshot.display_snapshot),
6486 window,
6487 cx,
6488 );
6489 }
6490
6491 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6492 released_too_fast: since.elapsed() < Duration::from_millis(200),
6493 };
6494 self.clear_row_highlights::<EditPredictionPreview>();
6495 self.update_visible_inline_completion(window, cx);
6496 cx.notify();
6497 }
6498 }
6499
6500 fn update_visible_inline_completion(
6501 &mut self,
6502 _window: &mut Window,
6503 cx: &mut Context<Self>,
6504 ) -> Option<()> {
6505 let selection = self.selections.newest_anchor();
6506 let cursor = selection.head();
6507 let multibuffer = self.buffer.read(cx).snapshot(cx);
6508 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6509 let excerpt_id = cursor.excerpt_id;
6510
6511 let show_in_menu = self.show_edit_predictions_in_menu();
6512 let completions_menu_has_precedence = !show_in_menu
6513 && (self.context_menu.borrow().is_some()
6514 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6515
6516 if completions_menu_has_precedence
6517 || !offset_selection.is_empty()
6518 || self
6519 .active_inline_completion
6520 .as_ref()
6521 .map_or(false, |completion| {
6522 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6523 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6524 !invalidation_range.contains(&offset_selection.head())
6525 })
6526 {
6527 self.discard_inline_completion(false, cx);
6528 return None;
6529 }
6530
6531 self.take_active_inline_completion(cx);
6532 let Some(provider) = self.edit_prediction_provider() else {
6533 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6534 return None;
6535 };
6536
6537 let (buffer, cursor_buffer_position) =
6538 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6539
6540 self.edit_prediction_settings =
6541 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6542
6543 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6544
6545 if self.edit_prediction_indent_conflict {
6546 let cursor_point = cursor.to_point(&multibuffer);
6547
6548 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6549
6550 if let Some((_, indent)) = indents.iter().next() {
6551 if indent.len == cursor_point.column {
6552 self.edit_prediction_indent_conflict = false;
6553 }
6554 }
6555 }
6556
6557 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6558 let edits = inline_completion
6559 .edits
6560 .into_iter()
6561 .flat_map(|(range, new_text)| {
6562 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6563 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6564 Some((start..end, new_text))
6565 })
6566 .collect::<Vec<_>>();
6567 if edits.is_empty() {
6568 return None;
6569 }
6570
6571 let first_edit_start = edits.first().unwrap().0.start;
6572 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6573 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6574
6575 let last_edit_end = edits.last().unwrap().0.end;
6576 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6577 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6578
6579 let cursor_row = cursor.to_point(&multibuffer).row;
6580
6581 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6582
6583 let mut inlay_ids = Vec::new();
6584 let invalidation_row_range;
6585 let move_invalidation_row_range = if cursor_row < edit_start_row {
6586 Some(cursor_row..edit_end_row)
6587 } else if cursor_row > edit_end_row {
6588 Some(edit_start_row..cursor_row)
6589 } else {
6590 None
6591 };
6592 let is_move =
6593 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6594 let completion = if is_move {
6595 invalidation_row_range =
6596 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6597 let target = first_edit_start;
6598 InlineCompletion::Move { target, snapshot }
6599 } else {
6600 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6601 && !self.inline_completions_hidden_for_vim_mode;
6602
6603 if show_completions_in_buffer {
6604 if edits
6605 .iter()
6606 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6607 {
6608 let mut inlays = Vec::new();
6609 for (range, new_text) in &edits {
6610 let inlay = Inlay::inline_completion(
6611 post_inc(&mut self.next_inlay_id),
6612 range.start,
6613 new_text.as_str(),
6614 );
6615 inlay_ids.push(inlay.id);
6616 inlays.push(inlay);
6617 }
6618
6619 self.splice_inlays(&[], inlays, cx);
6620 } else {
6621 let background_color = cx.theme().status().deleted_background;
6622 self.highlight_text::<InlineCompletionHighlight>(
6623 edits.iter().map(|(range, _)| range.clone()).collect(),
6624 HighlightStyle {
6625 background_color: Some(background_color),
6626 ..Default::default()
6627 },
6628 cx,
6629 );
6630 }
6631 }
6632
6633 invalidation_row_range = edit_start_row..edit_end_row;
6634
6635 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6636 if provider.show_tab_accept_marker() {
6637 EditDisplayMode::TabAccept
6638 } else {
6639 EditDisplayMode::Inline
6640 }
6641 } else {
6642 EditDisplayMode::DiffPopover
6643 };
6644
6645 InlineCompletion::Edit {
6646 edits,
6647 edit_preview: inline_completion.edit_preview,
6648 display_mode,
6649 snapshot,
6650 }
6651 };
6652
6653 let invalidation_range = multibuffer
6654 .anchor_before(Point::new(invalidation_row_range.start, 0))
6655 ..multibuffer.anchor_after(Point::new(
6656 invalidation_row_range.end,
6657 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6658 ));
6659
6660 self.stale_inline_completion_in_menu = None;
6661 self.active_inline_completion = Some(InlineCompletionState {
6662 inlay_ids,
6663 completion,
6664 completion_id: inline_completion.id,
6665 invalidation_range,
6666 });
6667
6668 cx.notify();
6669
6670 Some(())
6671 }
6672
6673 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6674 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6675 }
6676
6677 fn render_code_actions_indicator(
6678 &self,
6679 _style: &EditorStyle,
6680 row: DisplayRow,
6681 is_active: bool,
6682 breakpoint: Option<&(Anchor, Breakpoint)>,
6683 cx: &mut Context<Self>,
6684 ) -> Option<IconButton> {
6685 let color = Color::Muted;
6686 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6687 let show_tooltip = !self.context_menu_visible();
6688
6689 if self.available_code_actions.is_some() {
6690 Some(
6691 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6692 .shape(ui::IconButtonShape::Square)
6693 .icon_size(IconSize::XSmall)
6694 .icon_color(color)
6695 .toggle_state(is_active)
6696 .when(show_tooltip, |this| {
6697 this.tooltip({
6698 let focus_handle = self.focus_handle.clone();
6699 move |window, cx| {
6700 Tooltip::for_action_in(
6701 "Toggle Code Actions",
6702 &ToggleCodeActions {
6703 deployed_from_indicator: None,
6704 quick_launch: false,
6705 },
6706 &focus_handle,
6707 window,
6708 cx,
6709 )
6710 }
6711 })
6712 })
6713 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
6714 let quick_launch = e.down.button == MouseButton::Left;
6715 window.focus(&editor.focus_handle(cx));
6716 editor.toggle_code_actions(
6717 &ToggleCodeActions {
6718 deployed_from_indicator: Some(row),
6719 quick_launch,
6720 },
6721 window,
6722 cx,
6723 );
6724 }))
6725 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6726 editor.set_breakpoint_context_menu(
6727 row,
6728 position,
6729 event.down.position,
6730 window,
6731 cx,
6732 );
6733 })),
6734 )
6735 } else {
6736 None
6737 }
6738 }
6739
6740 fn clear_tasks(&mut self) {
6741 self.tasks.clear()
6742 }
6743
6744 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6745 if self.tasks.insert(key, value).is_some() {
6746 // This case should hopefully be rare, but just in case...
6747 log::error!(
6748 "multiple different run targets found on a single line, only the last target will be rendered"
6749 )
6750 }
6751 }
6752
6753 /// Get all display points of breakpoints that will be rendered within editor
6754 ///
6755 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6756 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6757 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6758 fn active_breakpoints(
6759 &self,
6760 range: Range<DisplayRow>,
6761 window: &mut Window,
6762 cx: &mut Context<Self>,
6763 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6764 let mut breakpoint_display_points = HashMap::default();
6765
6766 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6767 return breakpoint_display_points;
6768 };
6769
6770 let snapshot = self.snapshot(window, cx);
6771
6772 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6773 let Some(project) = self.project.as_ref() else {
6774 return breakpoint_display_points;
6775 };
6776
6777 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6778 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6779
6780 for (buffer_snapshot, range, excerpt_id) in
6781 multi_buffer_snapshot.range_to_buffer_ranges(range)
6782 {
6783 let Some(buffer) = project.read_with(cx, |this, cx| {
6784 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6785 }) else {
6786 continue;
6787 };
6788 let breakpoints = breakpoint_store.read(cx).breakpoints(
6789 &buffer,
6790 Some(
6791 buffer_snapshot.anchor_before(range.start)
6792 ..buffer_snapshot.anchor_after(range.end),
6793 ),
6794 buffer_snapshot,
6795 cx,
6796 );
6797 for (anchor, breakpoint) in breakpoints {
6798 let multi_buffer_anchor =
6799 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6800 let position = multi_buffer_anchor
6801 .to_point(&multi_buffer_snapshot)
6802 .to_display_point(&snapshot);
6803
6804 breakpoint_display_points
6805 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6806 }
6807 }
6808
6809 breakpoint_display_points
6810 }
6811
6812 fn breakpoint_context_menu(
6813 &self,
6814 anchor: Anchor,
6815 window: &mut Window,
6816 cx: &mut Context<Self>,
6817 ) -> Entity<ui::ContextMenu> {
6818 let weak_editor = cx.weak_entity();
6819 let focus_handle = self.focus_handle(cx);
6820
6821 let row = self
6822 .buffer
6823 .read(cx)
6824 .snapshot(cx)
6825 .summary_for_anchor::<Point>(&anchor)
6826 .row;
6827
6828 let breakpoint = self
6829 .breakpoint_at_row(row, window, cx)
6830 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6831
6832 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6833 "Edit Log Breakpoint"
6834 } else {
6835 "Set Log Breakpoint"
6836 };
6837
6838 let condition_breakpoint_msg = if breakpoint
6839 .as_ref()
6840 .is_some_and(|bp| bp.1.condition.is_some())
6841 {
6842 "Edit Condition Breakpoint"
6843 } else {
6844 "Set Condition Breakpoint"
6845 };
6846
6847 let hit_condition_breakpoint_msg = if breakpoint
6848 .as_ref()
6849 .is_some_and(|bp| bp.1.hit_condition.is_some())
6850 {
6851 "Edit Hit Condition Breakpoint"
6852 } else {
6853 "Set Hit Condition Breakpoint"
6854 };
6855
6856 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6857 "Unset Breakpoint"
6858 } else {
6859 "Set Breakpoint"
6860 };
6861
6862 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6863 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6864
6865 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6866 BreakpointState::Enabled => Some("Disable"),
6867 BreakpointState::Disabled => Some("Enable"),
6868 });
6869
6870 let (anchor, breakpoint) =
6871 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6872
6873 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6874 menu.on_blur_subscription(Subscription::new(|| {}))
6875 .context(focus_handle)
6876 .when(run_to_cursor, |this| {
6877 let weak_editor = weak_editor.clone();
6878 this.entry("Run to cursor", None, move |window, cx| {
6879 weak_editor
6880 .update(cx, |editor, cx| {
6881 editor.change_selections(None, window, cx, |s| {
6882 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6883 });
6884 })
6885 .ok();
6886
6887 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6888 })
6889 .separator()
6890 })
6891 .when_some(toggle_state_msg, |this, msg| {
6892 this.entry(msg, None, {
6893 let weak_editor = weak_editor.clone();
6894 let breakpoint = breakpoint.clone();
6895 move |_window, cx| {
6896 weak_editor
6897 .update(cx, |this, cx| {
6898 this.edit_breakpoint_at_anchor(
6899 anchor,
6900 breakpoint.as_ref().clone(),
6901 BreakpointEditAction::InvertState,
6902 cx,
6903 );
6904 })
6905 .log_err();
6906 }
6907 })
6908 })
6909 .entry(set_breakpoint_msg, None, {
6910 let weak_editor = weak_editor.clone();
6911 let breakpoint = breakpoint.clone();
6912 move |_window, cx| {
6913 weak_editor
6914 .update(cx, |this, cx| {
6915 this.edit_breakpoint_at_anchor(
6916 anchor,
6917 breakpoint.as_ref().clone(),
6918 BreakpointEditAction::Toggle,
6919 cx,
6920 );
6921 })
6922 .log_err();
6923 }
6924 })
6925 .entry(log_breakpoint_msg, None, {
6926 let breakpoint = breakpoint.clone();
6927 let weak_editor = weak_editor.clone();
6928 move |window, cx| {
6929 weak_editor
6930 .update(cx, |this, cx| {
6931 this.add_edit_breakpoint_block(
6932 anchor,
6933 breakpoint.as_ref(),
6934 BreakpointPromptEditAction::Log,
6935 window,
6936 cx,
6937 );
6938 })
6939 .log_err();
6940 }
6941 })
6942 .entry(condition_breakpoint_msg, None, {
6943 let breakpoint = breakpoint.clone();
6944 let weak_editor = weak_editor.clone();
6945 move |window, cx| {
6946 weak_editor
6947 .update(cx, |this, cx| {
6948 this.add_edit_breakpoint_block(
6949 anchor,
6950 breakpoint.as_ref(),
6951 BreakpointPromptEditAction::Condition,
6952 window,
6953 cx,
6954 );
6955 })
6956 .log_err();
6957 }
6958 })
6959 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6960 weak_editor
6961 .update(cx, |this, cx| {
6962 this.add_edit_breakpoint_block(
6963 anchor,
6964 breakpoint.as_ref(),
6965 BreakpointPromptEditAction::HitCondition,
6966 window,
6967 cx,
6968 );
6969 })
6970 .log_err();
6971 })
6972 })
6973 }
6974
6975 fn render_breakpoint(
6976 &self,
6977 position: Anchor,
6978 row: DisplayRow,
6979 breakpoint: &Breakpoint,
6980 cx: &mut Context<Self>,
6981 ) -> IconButton {
6982 // Is it a breakpoint that shows up when hovering over gutter?
6983 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
6984 (false, false),
6985 |PhantomBreakpointIndicator {
6986 is_active,
6987 display_row,
6988 collides_with_existing_breakpoint,
6989 }| {
6990 (
6991 is_active && display_row == row,
6992 collides_with_existing_breakpoint,
6993 )
6994 },
6995 );
6996
6997 let (color, icon) = {
6998 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6999 (false, false) => ui::IconName::DebugBreakpoint,
7000 (true, false) => ui::IconName::DebugLogBreakpoint,
7001 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7002 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7003 };
7004
7005 let color = if is_phantom {
7006 Color::Hint
7007 } else {
7008 Color::Debugger
7009 };
7010
7011 (color, icon)
7012 };
7013
7014 let breakpoint = Arc::from(breakpoint.clone());
7015
7016 let alt_as_text = gpui::Keystroke {
7017 modifiers: Modifiers::secondary_key(),
7018 ..Default::default()
7019 };
7020 let primary_action_text = if breakpoint.is_disabled() {
7021 "enable"
7022 } else if is_phantom && !collides_with_existing {
7023 "set"
7024 } else {
7025 "unset"
7026 };
7027 let mut primary_text = format!("Click to {primary_action_text}");
7028 if collides_with_existing && !breakpoint.is_disabled() {
7029 use std::fmt::Write;
7030 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7031 }
7032 let primary_text = SharedString::from(primary_text);
7033 let focus_handle = self.focus_handle.clone();
7034 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7035 .icon_size(IconSize::XSmall)
7036 .size(ui::ButtonSize::None)
7037 .icon_color(color)
7038 .style(ButtonStyle::Transparent)
7039 .on_click(cx.listener({
7040 let breakpoint = breakpoint.clone();
7041
7042 move |editor, event: &ClickEvent, window, cx| {
7043 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7044 BreakpointEditAction::InvertState
7045 } else {
7046 BreakpointEditAction::Toggle
7047 };
7048
7049 window.focus(&editor.focus_handle(cx));
7050 editor.edit_breakpoint_at_anchor(
7051 position,
7052 breakpoint.as_ref().clone(),
7053 edit_action,
7054 cx,
7055 );
7056 }
7057 }))
7058 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7059 editor.set_breakpoint_context_menu(
7060 row,
7061 Some(position),
7062 event.down.position,
7063 window,
7064 cx,
7065 );
7066 }))
7067 .tooltip(move |window, cx| {
7068 Tooltip::with_meta_in(
7069 primary_text.clone(),
7070 None,
7071 "Right-click for more options",
7072 &focus_handle,
7073 window,
7074 cx,
7075 )
7076 })
7077 }
7078
7079 fn build_tasks_context(
7080 project: &Entity<Project>,
7081 buffer: &Entity<Buffer>,
7082 buffer_row: u32,
7083 tasks: &Arc<RunnableTasks>,
7084 cx: &mut Context<Self>,
7085 ) -> Task<Option<task::TaskContext>> {
7086 let position = Point::new(buffer_row, tasks.column);
7087 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7088 let location = Location {
7089 buffer: buffer.clone(),
7090 range: range_start..range_start,
7091 };
7092 // Fill in the environmental variables from the tree-sitter captures
7093 let mut captured_task_variables = TaskVariables::default();
7094 for (capture_name, value) in tasks.extra_variables.clone() {
7095 captured_task_variables.insert(
7096 task::VariableName::Custom(capture_name.into()),
7097 value.clone(),
7098 );
7099 }
7100 project.update(cx, |project, cx| {
7101 project.task_store().update(cx, |task_store, cx| {
7102 task_store.task_context_for_location(captured_task_variables, location, cx)
7103 })
7104 })
7105 }
7106
7107 pub fn spawn_nearest_task(
7108 &mut self,
7109 action: &SpawnNearestTask,
7110 window: &mut Window,
7111 cx: &mut Context<Self>,
7112 ) {
7113 let Some((workspace, _)) = self.workspace.clone() else {
7114 return;
7115 };
7116 let Some(project) = self.project.clone() else {
7117 return;
7118 };
7119
7120 // Try to find a closest, enclosing node using tree-sitter that has a
7121 // task
7122 let Some((buffer, buffer_row, tasks)) = self
7123 .find_enclosing_node_task(cx)
7124 // Or find the task that's closest in row-distance.
7125 .or_else(|| self.find_closest_task(cx))
7126 else {
7127 return;
7128 };
7129
7130 let reveal_strategy = action.reveal;
7131 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7132 cx.spawn_in(window, async move |_, cx| {
7133 let context = task_context.await?;
7134 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7135
7136 let resolved = &mut resolved_task.resolved;
7137 resolved.reveal = reveal_strategy;
7138
7139 workspace
7140 .update_in(cx, |workspace, window, cx| {
7141 workspace.schedule_resolved_task(
7142 task_source_kind,
7143 resolved_task,
7144 false,
7145 window,
7146 cx,
7147 );
7148 })
7149 .ok()
7150 })
7151 .detach();
7152 }
7153
7154 fn find_closest_task(
7155 &mut self,
7156 cx: &mut Context<Self>,
7157 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7158 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7159
7160 let ((buffer_id, row), tasks) = self
7161 .tasks
7162 .iter()
7163 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7164
7165 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7166 let tasks = Arc::new(tasks.to_owned());
7167 Some((buffer, *row, tasks))
7168 }
7169
7170 fn find_enclosing_node_task(
7171 &mut self,
7172 cx: &mut Context<Self>,
7173 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7174 let snapshot = self.buffer.read(cx).snapshot(cx);
7175 let offset = self.selections.newest::<usize>(cx).head();
7176 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7177 let buffer_id = excerpt.buffer().remote_id();
7178
7179 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7180 let mut cursor = layer.node().walk();
7181
7182 while cursor.goto_first_child_for_byte(offset).is_some() {
7183 if cursor.node().end_byte() == offset {
7184 cursor.goto_next_sibling();
7185 }
7186 }
7187
7188 // Ascend to the smallest ancestor that contains the range and has a task.
7189 loop {
7190 let node = cursor.node();
7191 let node_range = node.byte_range();
7192 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7193
7194 // Check if this node contains our offset
7195 if node_range.start <= offset && node_range.end >= offset {
7196 // If it contains offset, check for task
7197 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7198 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7199 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7200 }
7201 }
7202
7203 if !cursor.goto_parent() {
7204 break;
7205 }
7206 }
7207 None
7208 }
7209
7210 fn render_run_indicator(
7211 &self,
7212 _style: &EditorStyle,
7213 is_active: bool,
7214 row: DisplayRow,
7215 breakpoint: Option<(Anchor, Breakpoint)>,
7216 cx: &mut Context<Self>,
7217 ) -> IconButton {
7218 let color = Color::Muted;
7219 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7220
7221 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7222 .shape(ui::IconButtonShape::Square)
7223 .icon_size(IconSize::XSmall)
7224 .icon_color(color)
7225 .toggle_state(is_active)
7226 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7227 let quick_launch = e.down.button == MouseButton::Left;
7228 window.focus(&editor.focus_handle(cx));
7229 editor.toggle_code_actions(
7230 &ToggleCodeActions {
7231 deployed_from_indicator: Some(row),
7232 quick_launch,
7233 },
7234 window,
7235 cx,
7236 );
7237 }))
7238 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7239 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7240 }))
7241 }
7242
7243 pub fn context_menu_visible(&self) -> bool {
7244 !self.edit_prediction_preview_is_active()
7245 && self
7246 .context_menu
7247 .borrow()
7248 .as_ref()
7249 .map_or(false, |menu| menu.visible())
7250 }
7251
7252 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7253 self.context_menu
7254 .borrow()
7255 .as_ref()
7256 .map(|menu| menu.origin())
7257 }
7258
7259 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7260 self.context_menu_options = Some(options);
7261 }
7262
7263 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7264 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7265
7266 fn render_edit_prediction_popover(
7267 &mut self,
7268 text_bounds: &Bounds<Pixels>,
7269 content_origin: gpui::Point<Pixels>,
7270 editor_snapshot: &EditorSnapshot,
7271 visible_row_range: Range<DisplayRow>,
7272 scroll_top: f32,
7273 scroll_bottom: f32,
7274 line_layouts: &[LineWithInvisibles],
7275 line_height: Pixels,
7276 scroll_pixel_position: gpui::Point<Pixels>,
7277 newest_selection_head: Option<DisplayPoint>,
7278 editor_width: Pixels,
7279 style: &EditorStyle,
7280 window: &mut Window,
7281 cx: &mut App,
7282 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7283 let active_inline_completion = self.active_inline_completion.as_ref()?;
7284
7285 if self.edit_prediction_visible_in_cursor_popover(true) {
7286 return None;
7287 }
7288
7289 match &active_inline_completion.completion {
7290 InlineCompletion::Move { target, .. } => {
7291 let target_display_point = target.to_display_point(editor_snapshot);
7292
7293 if self.edit_prediction_requires_modifier() {
7294 if !self.edit_prediction_preview_is_active() {
7295 return None;
7296 }
7297
7298 self.render_edit_prediction_modifier_jump_popover(
7299 text_bounds,
7300 content_origin,
7301 visible_row_range,
7302 line_layouts,
7303 line_height,
7304 scroll_pixel_position,
7305 newest_selection_head,
7306 target_display_point,
7307 window,
7308 cx,
7309 )
7310 } else {
7311 self.render_edit_prediction_eager_jump_popover(
7312 text_bounds,
7313 content_origin,
7314 editor_snapshot,
7315 visible_row_range,
7316 scroll_top,
7317 scroll_bottom,
7318 line_height,
7319 scroll_pixel_position,
7320 target_display_point,
7321 editor_width,
7322 window,
7323 cx,
7324 )
7325 }
7326 }
7327 InlineCompletion::Edit {
7328 display_mode: EditDisplayMode::Inline,
7329 ..
7330 } => None,
7331 InlineCompletion::Edit {
7332 display_mode: EditDisplayMode::TabAccept,
7333 edits,
7334 ..
7335 } => {
7336 let range = &edits.first()?.0;
7337 let target_display_point = range.end.to_display_point(editor_snapshot);
7338
7339 self.render_edit_prediction_end_of_line_popover(
7340 "Accept",
7341 editor_snapshot,
7342 visible_row_range,
7343 target_display_point,
7344 line_height,
7345 scroll_pixel_position,
7346 content_origin,
7347 editor_width,
7348 window,
7349 cx,
7350 )
7351 }
7352 InlineCompletion::Edit {
7353 edits,
7354 edit_preview,
7355 display_mode: EditDisplayMode::DiffPopover,
7356 snapshot,
7357 } => self.render_edit_prediction_diff_popover(
7358 text_bounds,
7359 content_origin,
7360 editor_snapshot,
7361 visible_row_range,
7362 line_layouts,
7363 line_height,
7364 scroll_pixel_position,
7365 newest_selection_head,
7366 editor_width,
7367 style,
7368 edits,
7369 edit_preview,
7370 snapshot,
7371 window,
7372 cx,
7373 ),
7374 }
7375 }
7376
7377 fn render_edit_prediction_modifier_jump_popover(
7378 &mut self,
7379 text_bounds: &Bounds<Pixels>,
7380 content_origin: gpui::Point<Pixels>,
7381 visible_row_range: Range<DisplayRow>,
7382 line_layouts: &[LineWithInvisibles],
7383 line_height: Pixels,
7384 scroll_pixel_position: gpui::Point<Pixels>,
7385 newest_selection_head: Option<DisplayPoint>,
7386 target_display_point: DisplayPoint,
7387 window: &mut Window,
7388 cx: &mut App,
7389 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7390 let scrolled_content_origin =
7391 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7392
7393 const SCROLL_PADDING_Y: Pixels = px(12.);
7394
7395 if target_display_point.row() < visible_row_range.start {
7396 return self.render_edit_prediction_scroll_popover(
7397 |_| SCROLL_PADDING_Y,
7398 IconName::ArrowUp,
7399 visible_row_range,
7400 line_layouts,
7401 newest_selection_head,
7402 scrolled_content_origin,
7403 window,
7404 cx,
7405 );
7406 } else if target_display_point.row() >= visible_row_range.end {
7407 return self.render_edit_prediction_scroll_popover(
7408 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7409 IconName::ArrowDown,
7410 visible_row_range,
7411 line_layouts,
7412 newest_selection_head,
7413 scrolled_content_origin,
7414 window,
7415 cx,
7416 );
7417 }
7418
7419 const POLE_WIDTH: Pixels = px(2.);
7420
7421 let line_layout =
7422 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7423 let target_column = target_display_point.column() as usize;
7424
7425 let target_x = line_layout.x_for_index(target_column);
7426 let target_y =
7427 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7428
7429 let flag_on_right = target_x < text_bounds.size.width / 2.;
7430
7431 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7432 border_color.l += 0.001;
7433
7434 let mut element = v_flex()
7435 .items_end()
7436 .when(flag_on_right, |el| el.items_start())
7437 .child(if flag_on_right {
7438 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7439 .rounded_bl(px(0.))
7440 .rounded_tl(px(0.))
7441 .border_l_2()
7442 .border_color(border_color)
7443 } else {
7444 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7445 .rounded_br(px(0.))
7446 .rounded_tr(px(0.))
7447 .border_r_2()
7448 .border_color(border_color)
7449 })
7450 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7451 .into_any();
7452
7453 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7454
7455 let mut origin = scrolled_content_origin + point(target_x, target_y)
7456 - point(
7457 if flag_on_right {
7458 POLE_WIDTH
7459 } else {
7460 size.width - POLE_WIDTH
7461 },
7462 size.height - line_height,
7463 );
7464
7465 origin.x = origin.x.max(content_origin.x);
7466
7467 element.prepaint_at(origin, window, cx);
7468
7469 Some((element, origin))
7470 }
7471
7472 fn render_edit_prediction_scroll_popover(
7473 &mut self,
7474 to_y: impl Fn(Size<Pixels>) -> Pixels,
7475 scroll_icon: IconName,
7476 visible_row_range: Range<DisplayRow>,
7477 line_layouts: &[LineWithInvisibles],
7478 newest_selection_head: Option<DisplayPoint>,
7479 scrolled_content_origin: gpui::Point<Pixels>,
7480 window: &mut Window,
7481 cx: &mut App,
7482 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7483 let mut element = self
7484 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7485 .into_any();
7486
7487 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7488
7489 let cursor = newest_selection_head?;
7490 let cursor_row_layout =
7491 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7492 let cursor_column = cursor.column() as usize;
7493
7494 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7495
7496 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7497
7498 element.prepaint_at(origin, window, cx);
7499 Some((element, origin))
7500 }
7501
7502 fn render_edit_prediction_eager_jump_popover(
7503 &mut self,
7504 text_bounds: &Bounds<Pixels>,
7505 content_origin: gpui::Point<Pixels>,
7506 editor_snapshot: &EditorSnapshot,
7507 visible_row_range: Range<DisplayRow>,
7508 scroll_top: f32,
7509 scroll_bottom: f32,
7510 line_height: Pixels,
7511 scroll_pixel_position: gpui::Point<Pixels>,
7512 target_display_point: DisplayPoint,
7513 editor_width: Pixels,
7514 window: &mut Window,
7515 cx: &mut App,
7516 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7517 if target_display_point.row().as_f32() < scroll_top {
7518 let mut element = self
7519 .render_edit_prediction_line_popover(
7520 "Jump to Edit",
7521 Some(IconName::ArrowUp),
7522 window,
7523 cx,
7524 )?
7525 .into_any();
7526
7527 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7528 let offset = point(
7529 (text_bounds.size.width - size.width) / 2.,
7530 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7531 );
7532
7533 let origin = text_bounds.origin + offset;
7534 element.prepaint_at(origin, window, cx);
7535 Some((element, origin))
7536 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7537 let mut element = self
7538 .render_edit_prediction_line_popover(
7539 "Jump to Edit",
7540 Some(IconName::ArrowDown),
7541 window,
7542 cx,
7543 )?
7544 .into_any();
7545
7546 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7547 let offset = point(
7548 (text_bounds.size.width - size.width) / 2.,
7549 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7550 );
7551
7552 let origin = text_bounds.origin + offset;
7553 element.prepaint_at(origin, window, cx);
7554 Some((element, origin))
7555 } else {
7556 self.render_edit_prediction_end_of_line_popover(
7557 "Jump to Edit",
7558 editor_snapshot,
7559 visible_row_range,
7560 target_display_point,
7561 line_height,
7562 scroll_pixel_position,
7563 content_origin,
7564 editor_width,
7565 window,
7566 cx,
7567 )
7568 }
7569 }
7570
7571 fn render_edit_prediction_end_of_line_popover(
7572 self: &mut Editor,
7573 label: &'static str,
7574 editor_snapshot: &EditorSnapshot,
7575 visible_row_range: Range<DisplayRow>,
7576 target_display_point: DisplayPoint,
7577 line_height: Pixels,
7578 scroll_pixel_position: gpui::Point<Pixels>,
7579 content_origin: gpui::Point<Pixels>,
7580 editor_width: Pixels,
7581 window: &mut Window,
7582 cx: &mut App,
7583 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7584 let target_line_end = DisplayPoint::new(
7585 target_display_point.row(),
7586 editor_snapshot.line_len(target_display_point.row()),
7587 );
7588
7589 let mut element = self
7590 .render_edit_prediction_line_popover(label, None, window, cx)?
7591 .into_any();
7592
7593 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7594
7595 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7596
7597 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7598 let mut origin = start_point
7599 + line_origin
7600 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7601 origin.x = origin.x.max(content_origin.x);
7602
7603 let max_x = content_origin.x + editor_width - size.width;
7604
7605 if origin.x > max_x {
7606 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7607
7608 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7609 origin.y += offset;
7610 IconName::ArrowUp
7611 } else {
7612 origin.y -= offset;
7613 IconName::ArrowDown
7614 };
7615
7616 element = self
7617 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7618 .into_any();
7619
7620 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7621
7622 origin.x = content_origin.x + editor_width - size.width - px(2.);
7623 }
7624
7625 element.prepaint_at(origin, window, cx);
7626 Some((element, origin))
7627 }
7628
7629 fn render_edit_prediction_diff_popover(
7630 self: &Editor,
7631 text_bounds: &Bounds<Pixels>,
7632 content_origin: gpui::Point<Pixels>,
7633 editor_snapshot: &EditorSnapshot,
7634 visible_row_range: Range<DisplayRow>,
7635 line_layouts: &[LineWithInvisibles],
7636 line_height: Pixels,
7637 scroll_pixel_position: gpui::Point<Pixels>,
7638 newest_selection_head: Option<DisplayPoint>,
7639 editor_width: Pixels,
7640 style: &EditorStyle,
7641 edits: &Vec<(Range<Anchor>, String)>,
7642 edit_preview: &Option<language::EditPreview>,
7643 snapshot: &language::BufferSnapshot,
7644 window: &mut Window,
7645 cx: &mut App,
7646 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7647 let edit_start = edits
7648 .first()
7649 .unwrap()
7650 .0
7651 .start
7652 .to_display_point(editor_snapshot);
7653 let edit_end = edits
7654 .last()
7655 .unwrap()
7656 .0
7657 .end
7658 .to_display_point(editor_snapshot);
7659
7660 let is_visible = visible_row_range.contains(&edit_start.row())
7661 || visible_row_range.contains(&edit_end.row());
7662 if !is_visible {
7663 return None;
7664 }
7665
7666 let highlighted_edits =
7667 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7668
7669 let styled_text = highlighted_edits.to_styled_text(&style.text);
7670 let line_count = highlighted_edits.text.lines().count();
7671
7672 const BORDER_WIDTH: Pixels = px(1.);
7673
7674 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7675 let has_keybind = keybind.is_some();
7676
7677 let mut element = h_flex()
7678 .items_start()
7679 .child(
7680 h_flex()
7681 .bg(cx.theme().colors().editor_background)
7682 .border(BORDER_WIDTH)
7683 .shadow_sm()
7684 .border_color(cx.theme().colors().border)
7685 .rounded_l_lg()
7686 .when(line_count > 1, |el| el.rounded_br_lg())
7687 .pr_1()
7688 .child(styled_text),
7689 )
7690 .child(
7691 h_flex()
7692 .h(line_height + BORDER_WIDTH * 2.)
7693 .px_1p5()
7694 .gap_1()
7695 // Workaround: For some reason, there's a gap if we don't do this
7696 .ml(-BORDER_WIDTH)
7697 .shadow(smallvec![gpui::BoxShadow {
7698 color: gpui::black().opacity(0.05),
7699 offset: point(px(1.), px(1.)),
7700 blur_radius: px(2.),
7701 spread_radius: px(0.),
7702 }])
7703 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7704 .border(BORDER_WIDTH)
7705 .border_color(cx.theme().colors().border)
7706 .rounded_r_lg()
7707 .id("edit_prediction_diff_popover_keybind")
7708 .when(!has_keybind, |el| {
7709 let status_colors = cx.theme().status();
7710
7711 el.bg(status_colors.error_background)
7712 .border_color(status_colors.error.opacity(0.6))
7713 .child(Icon::new(IconName::Info).color(Color::Error))
7714 .cursor_default()
7715 .hoverable_tooltip(move |_window, cx| {
7716 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7717 })
7718 })
7719 .children(keybind),
7720 )
7721 .into_any();
7722
7723 let longest_row =
7724 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7725 let longest_line_width = if visible_row_range.contains(&longest_row) {
7726 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7727 } else {
7728 layout_line(
7729 longest_row,
7730 editor_snapshot,
7731 style,
7732 editor_width,
7733 |_| false,
7734 window,
7735 cx,
7736 )
7737 .width
7738 };
7739
7740 let viewport_bounds =
7741 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7742 right: -EditorElement::SCROLLBAR_WIDTH,
7743 ..Default::default()
7744 });
7745
7746 let x_after_longest =
7747 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7748 - scroll_pixel_position.x;
7749
7750 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7751
7752 // Fully visible if it can be displayed within the window (allow overlapping other
7753 // panes). However, this is only allowed if the popover starts within text_bounds.
7754 let can_position_to_the_right = x_after_longest < text_bounds.right()
7755 && x_after_longest + element_bounds.width < viewport_bounds.right();
7756
7757 let mut origin = if can_position_to_the_right {
7758 point(
7759 x_after_longest,
7760 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7761 - scroll_pixel_position.y,
7762 )
7763 } else {
7764 let cursor_row = newest_selection_head.map(|head| head.row());
7765 let above_edit = edit_start
7766 .row()
7767 .0
7768 .checked_sub(line_count as u32)
7769 .map(DisplayRow);
7770 let below_edit = Some(edit_end.row() + 1);
7771 let above_cursor =
7772 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7773 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7774
7775 // Place the edit popover adjacent to the edit if there is a location
7776 // available that is onscreen and does not obscure the cursor. Otherwise,
7777 // place it adjacent to the cursor.
7778 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7779 .into_iter()
7780 .flatten()
7781 .find(|&start_row| {
7782 let end_row = start_row + line_count as u32;
7783 visible_row_range.contains(&start_row)
7784 && visible_row_range.contains(&end_row)
7785 && cursor_row.map_or(true, |cursor_row| {
7786 !((start_row..end_row).contains(&cursor_row))
7787 })
7788 })?;
7789
7790 content_origin
7791 + point(
7792 -scroll_pixel_position.x,
7793 row_target.as_f32() * line_height - scroll_pixel_position.y,
7794 )
7795 };
7796
7797 origin.x -= BORDER_WIDTH;
7798
7799 window.defer_draw(element, origin, 1);
7800
7801 // Do not return an element, since it will already be drawn due to defer_draw.
7802 None
7803 }
7804
7805 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7806 px(30.)
7807 }
7808
7809 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7810 if self.read_only(cx) {
7811 cx.theme().players().read_only()
7812 } else {
7813 self.style.as_ref().unwrap().local_player
7814 }
7815 }
7816
7817 fn render_edit_prediction_accept_keybind(
7818 &self,
7819 window: &mut Window,
7820 cx: &App,
7821 ) -> Option<AnyElement> {
7822 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7823 let accept_keystroke = accept_binding.keystroke()?;
7824
7825 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7826
7827 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7828 Color::Accent
7829 } else {
7830 Color::Muted
7831 };
7832
7833 h_flex()
7834 .px_0p5()
7835 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7836 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7837 .text_size(TextSize::XSmall.rems(cx))
7838 .child(h_flex().children(ui::render_modifiers(
7839 &accept_keystroke.modifiers,
7840 PlatformStyle::platform(),
7841 Some(modifiers_color),
7842 Some(IconSize::XSmall.rems().into()),
7843 true,
7844 )))
7845 .when(is_platform_style_mac, |parent| {
7846 parent.child(accept_keystroke.key.clone())
7847 })
7848 .when(!is_platform_style_mac, |parent| {
7849 parent.child(
7850 Key::new(
7851 util::capitalize(&accept_keystroke.key),
7852 Some(Color::Default),
7853 )
7854 .size(Some(IconSize::XSmall.rems().into())),
7855 )
7856 })
7857 .into_any()
7858 .into()
7859 }
7860
7861 fn render_edit_prediction_line_popover(
7862 &self,
7863 label: impl Into<SharedString>,
7864 icon: Option<IconName>,
7865 window: &mut Window,
7866 cx: &App,
7867 ) -> Option<Stateful<Div>> {
7868 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7869
7870 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7871 let has_keybind = keybind.is_some();
7872
7873 let result = h_flex()
7874 .id("ep-line-popover")
7875 .py_0p5()
7876 .pl_1()
7877 .pr(padding_right)
7878 .gap_1()
7879 .rounded_md()
7880 .border_1()
7881 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7882 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7883 .shadow_sm()
7884 .when(!has_keybind, |el| {
7885 let status_colors = cx.theme().status();
7886
7887 el.bg(status_colors.error_background)
7888 .border_color(status_colors.error.opacity(0.6))
7889 .pl_2()
7890 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7891 .cursor_default()
7892 .hoverable_tooltip(move |_window, cx| {
7893 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7894 })
7895 })
7896 .children(keybind)
7897 .child(
7898 Label::new(label)
7899 .size(LabelSize::Small)
7900 .when(!has_keybind, |el| {
7901 el.color(cx.theme().status().error.into()).strikethrough()
7902 }),
7903 )
7904 .when(!has_keybind, |el| {
7905 el.child(
7906 h_flex().ml_1().child(
7907 Icon::new(IconName::Info)
7908 .size(IconSize::Small)
7909 .color(cx.theme().status().error.into()),
7910 ),
7911 )
7912 })
7913 .when_some(icon, |element, icon| {
7914 element.child(
7915 div()
7916 .mt(px(1.5))
7917 .child(Icon::new(icon).size(IconSize::Small)),
7918 )
7919 });
7920
7921 Some(result)
7922 }
7923
7924 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7925 let accent_color = cx.theme().colors().text_accent;
7926 let editor_bg_color = cx.theme().colors().editor_background;
7927 editor_bg_color.blend(accent_color.opacity(0.1))
7928 }
7929
7930 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7931 let accent_color = cx.theme().colors().text_accent;
7932 let editor_bg_color = cx.theme().colors().editor_background;
7933 editor_bg_color.blend(accent_color.opacity(0.6))
7934 }
7935
7936 fn render_edit_prediction_cursor_popover(
7937 &self,
7938 min_width: Pixels,
7939 max_width: Pixels,
7940 cursor_point: Point,
7941 style: &EditorStyle,
7942 accept_keystroke: Option<&gpui::Keystroke>,
7943 _window: &Window,
7944 cx: &mut Context<Editor>,
7945 ) -> Option<AnyElement> {
7946 let provider = self.edit_prediction_provider.as_ref()?;
7947
7948 if provider.provider.needs_terms_acceptance(cx) {
7949 return Some(
7950 h_flex()
7951 .min_w(min_width)
7952 .flex_1()
7953 .px_2()
7954 .py_1()
7955 .gap_3()
7956 .elevation_2(cx)
7957 .hover(|style| style.bg(cx.theme().colors().element_hover))
7958 .id("accept-terms")
7959 .cursor_pointer()
7960 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7961 .on_click(cx.listener(|this, _event, window, cx| {
7962 cx.stop_propagation();
7963 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7964 window.dispatch_action(
7965 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7966 cx,
7967 );
7968 }))
7969 .child(
7970 h_flex()
7971 .flex_1()
7972 .gap_2()
7973 .child(Icon::new(IconName::ZedPredict))
7974 .child(Label::new("Accept Terms of Service"))
7975 .child(div().w_full())
7976 .child(
7977 Icon::new(IconName::ArrowUpRight)
7978 .color(Color::Muted)
7979 .size(IconSize::Small),
7980 )
7981 .into_any_element(),
7982 )
7983 .into_any(),
7984 );
7985 }
7986
7987 let is_refreshing = provider.provider.is_refreshing(cx);
7988
7989 fn pending_completion_container() -> Div {
7990 h_flex()
7991 .h_full()
7992 .flex_1()
7993 .gap_2()
7994 .child(Icon::new(IconName::ZedPredict))
7995 }
7996
7997 let completion = match &self.active_inline_completion {
7998 Some(prediction) => {
7999 if !self.has_visible_completions_menu() {
8000 const RADIUS: Pixels = px(6.);
8001 const BORDER_WIDTH: Pixels = px(1.);
8002
8003 return Some(
8004 h_flex()
8005 .elevation_2(cx)
8006 .border(BORDER_WIDTH)
8007 .border_color(cx.theme().colors().border)
8008 .when(accept_keystroke.is_none(), |el| {
8009 el.border_color(cx.theme().status().error)
8010 })
8011 .rounded(RADIUS)
8012 .rounded_tl(px(0.))
8013 .overflow_hidden()
8014 .child(div().px_1p5().child(match &prediction.completion {
8015 InlineCompletion::Move { target, snapshot } => {
8016 use text::ToPoint as _;
8017 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8018 {
8019 Icon::new(IconName::ZedPredictDown)
8020 } else {
8021 Icon::new(IconName::ZedPredictUp)
8022 }
8023 }
8024 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8025 }))
8026 .child(
8027 h_flex()
8028 .gap_1()
8029 .py_1()
8030 .px_2()
8031 .rounded_r(RADIUS - BORDER_WIDTH)
8032 .border_l_1()
8033 .border_color(cx.theme().colors().border)
8034 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8035 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8036 el.child(
8037 Label::new("Hold")
8038 .size(LabelSize::Small)
8039 .when(accept_keystroke.is_none(), |el| {
8040 el.strikethrough()
8041 })
8042 .line_height_style(LineHeightStyle::UiLabel),
8043 )
8044 })
8045 .id("edit_prediction_cursor_popover_keybind")
8046 .when(accept_keystroke.is_none(), |el| {
8047 let status_colors = cx.theme().status();
8048
8049 el.bg(status_colors.error_background)
8050 .border_color(status_colors.error.opacity(0.6))
8051 .child(Icon::new(IconName::Info).color(Color::Error))
8052 .cursor_default()
8053 .hoverable_tooltip(move |_window, cx| {
8054 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8055 .into()
8056 })
8057 })
8058 .when_some(
8059 accept_keystroke.as_ref(),
8060 |el, accept_keystroke| {
8061 el.child(h_flex().children(ui::render_modifiers(
8062 &accept_keystroke.modifiers,
8063 PlatformStyle::platform(),
8064 Some(Color::Default),
8065 Some(IconSize::XSmall.rems().into()),
8066 false,
8067 )))
8068 },
8069 ),
8070 )
8071 .into_any(),
8072 );
8073 }
8074
8075 self.render_edit_prediction_cursor_popover_preview(
8076 prediction,
8077 cursor_point,
8078 style,
8079 cx,
8080 )?
8081 }
8082
8083 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8084 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8085 stale_completion,
8086 cursor_point,
8087 style,
8088 cx,
8089 )?,
8090
8091 None => {
8092 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8093 }
8094 },
8095
8096 None => pending_completion_container().child(Label::new("No Prediction")),
8097 };
8098
8099 let completion = if is_refreshing {
8100 completion
8101 .with_animation(
8102 "loading-completion",
8103 Animation::new(Duration::from_secs(2))
8104 .repeat()
8105 .with_easing(pulsating_between(0.4, 0.8)),
8106 |label, delta| label.opacity(delta),
8107 )
8108 .into_any_element()
8109 } else {
8110 completion.into_any_element()
8111 };
8112
8113 let has_completion = self.active_inline_completion.is_some();
8114
8115 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8116 Some(
8117 h_flex()
8118 .min_w(min_width)
8119 .max_w(max_width)
8120 .flex_1()
8121 .elevation_2(cx)
8122 .border_color(cx.theme().colors().border)
8123 .child(
8124 div()
8125 .flex_1()
8126 .py_1()
8127 .px_2()
8128 .overflow_hidden()
8129 .child(completion),
8130 )
8131 .when_some(accept_keystroke, |el, accept_keystroke| {
8132 if !accept_keystroke.modifiers.modified() {
8133 return el;
8134 }
8135
8136 el.child(
8137 h_flex()
8138 .h_full()
8139 .border_l_1()
8140 .rounded_r_lg()
8141 .border_color(cx.theme().colors().border)
8142 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8143 .gap_1()
8144 .py_1()
8145 .px_2()
8146 .child(
8147 h_flex()
8148 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8149 .when(is_platform_style_mac, |parent| parent.gap_1())
8150 .child(h_flex().children(ui::render_modifiers(
8151 &accept_keystroke.modifiers,
8152 PlatformStyle::platform(),
8153 Some(if !has_completion {
8154 Color::Muted
8155 } else {
8156 Color::Default
8157 }),
8158 None,
8159 false,
8160 ))),
8161 )
8162 .child(Label::new("Preview").into_any_element())
8163 .opacity(if has_completion { 1.0 } else { 0.4 }),
8164 )
8165 })
8166 .into_any(),
8167 )
8168 }
8169
8170 fn render_edit_prediction_cursor_popover_preview(
8171 &self,
8172 completion: &InlineCompletionState,
8173 cursor_point: Point,
8174 style: &EditorStyle,
8175 cx: &mut Context<Editor>,
8176 ) -> Option<Div> {
8177 use text::ToPoint as _;
8178
8179 fn render_relative_row_jump(
8180 prefix: impl Into<String>,
8181 current_row: u32,
8182 target_row: u32,
8183 ) -> Div {
8184 let (row_diff, arrow) = if target_row < current_row {
8185 (current_row - target_row, IconName::ArrowUp)
8186 } else {
8187 (target_row - current_row, IconName::ArrowDown)
8188 };
8189
8190 h_flex()
8191 .child(
8192 Label::new(format!("{}{}", prefix.into(), row_diff))
8193 .color(Color::Muted)
8194 .size(LabelSize::Small),
8195 )
8196 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8197 }
8198
8199 match &completion.completion {
8200 InlineCompletion::Move {
8201 target, snapshot, ..
8202 } => Some(
8203 h_flex()
8204 .px_2()
8205 .gap_2()
8206 .flex_1()
8207 .child(
8208 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8209 Icon::new(IconName::ZedPredictDown)
8210 } else {
8211 Icon::new(IconName::ZedPredictUp)
8212 },
8213 )
8214 .child(Label::new("Jump to Edit")),
8215 ),
8216
8217 InlineCompletion::Edit {
8218 edits,
8219 edit_preview,
8220 snapshot,
8221 display_mode: _,
8222 } => {
8223 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8224
8225 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8226 &snapshot,
8227 &edits,
8228 edit_preview.as_ref()?,
8229 true,
8230 cx,
8231 )
8232 .first_line_preview();
8233
8234 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8235 .with_default_highlights(&style.text, highlighted_edits.highlights);
8236
8237 let preview = h_flex()
8238 .gap_1()
8239 .min_w_16()
8240 .child(styled_text)
8241 .when(has_more_lines, |parent| parent.child("…"));
8242
8243 let left = if first_edit_row != cursor_point.row {
8244 render_relative_row_jump("", cursor_point.row, first_edit_row)
8245 .into_any_element()
8246 } else {
8247 Icon::new(IconName::ZedPredict).into_any_element()
8248 };
8249
8250 Some(
8251 h_flex()
8252 .h_full()
8253 .flex_1()
8254 .gap_2()
8255 .pr_1()
8256 .overflow_x_hidden()
8257 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8258 .child(left)
8259 .child(preview),
8260 )
8261 }
8262 }
8263 }
8264
8265 fn render_context_menu(
8266 &self,
8267 style: &EditorStyle,
8268 max_height_in_lines: u32,
8269 window: &mut Window,
8270 cx: &mut Context<Editor>,
8271 ) -> Option<AnyElement> {
8272 let menu = self.context_menu.borrow();
8273 let menu = menu.as_ref()?;
8274 if !menu.visible() {
8275 return None;
8276 };
8277 Some(menu.render(style, max_height_in_lines, window, cx))
8278 }
8279
8280 fn render_context_menu_aside(
8281 &mut self,
8282 max_size: Size<Pixels>,
8283 window: &mut Window,
8284 cx: &mut Context<Editor>,
8285 ) -> Option<AnyElement> {
8286 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8287 if menu.visible() {
8288 menu.render_aside(self, max_size, window, cx)
8289 } else {
8290 None
8291 }
8292 })
8293 }
8294
8295 fn hide_context_menu(
8296 &mut self,
8297 window: &mut Window,
8298 cx: &mut Context<Self>,
8299 ) -> Option<CodeContextMenu> {
8300 cx.notify();
8301 self.completion_tasks.clear();
8302 let context_menu = self.context_menu.borrow_mut().take();
8303 self.stale_inline_completion_in_menu.take();
8304 self.update_visible_inline_completion(window, cx);
8305 context_menu
8306 }
8307
8308 fn show_snippet_choices(
8309 &mut self,
8310 choices: &Vec<String>,
8311 selection: Range<Anchor>,
8312 cx: &mut Context<Self>,
8313 ) {
8314 if selection.start.buffer_id.is_none() {
8315 return;
8316 }
8317 let buffer_id = selection.start.buffer_id.unwrap();
8318 let buffer = self.buffer().read(cx).buffer(buffer_id);
8319 let id = post_inc(&mut self.next_completion_id);
8320 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8321
8322 if let Some(buffer) = buffer {
8323 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8324 CompletionsMenu::new_snippet_choices(
8325 id,
8326 true,
8327 choices,
8328 selection,
8329 buffer,
8330 snippet_sort_order,
8331 ),
8332 ));
8333 }
8334 }
8335
8336 pub fn insert_snippet(
8337 &mut self,
8338 insertion_ranges: &[Range<usize>],
8339 snippet: Snippet,
8340 window: &mut Window,
8341 cx: &mut Context<Self>,
8342 ) -> Result<()> {
8343 struct Tabstop<T> {
8344 is_end_tabstop: bool,
8345 ranges: Vec<Range<T>>,
8346 choices: Option<Vec<String>>,
8347 }
8348
8349 let tabstops = self.buffer.update(cx, |buffer, cx| {
8350 let snippet_text: Arc<str> = snippet.text.clone().into();
8351 let edits = insertion_ranges
8352 .iter()
8353 .cloned()
8354 .map(|range| (range, snippet_text.clone()));
8355 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8356
8357 let snapshot = &*buffer.read(cx);
8358 let snippet = &snippet;
8359 snippet
8360 .tabstops
8361 .iter()
8362 .map(|tabstop| {
8363 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8364 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8365 });
8366 let mut tabstop_ranges = tabstop
8367 .ranges
8368 .iter()
8369 .flat_map(|tabstop_range| {
8370 let mut delta = 0_isize;
8371 insertion_ranges.iter().map(move |insertion_range| {
8372 let insertion_start = insertion_range.start as isize + delta;
8373 delta +=
8374 snippet.text.len() as isize - insertion_range.len() as isize;
8375
8376 let start = ((insertion_start + tabstop_range.start) as usize)
8377 .min(snapshot.len());
8378 let end = ((insertion_start + tabstop_range.end) as usize)
8379 .min(snapshot.len());
8380 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8381 })
8382 })
8383 .collect::<Vec<_>>();
8384 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8385
8386 Tabstop {
8387 is_end_tabstop,
8388 ranges: tabstop_ranges,
8389 choices: tabstop.choices.clone(),
8390 }
8391 })
8392 .collect::<Vec<_>>()
8393 });
8394 if let Some(tabstop) = tabstops.first() {
8395 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8396 s.select_ranges(tabstop.ranges.iter().cloned());
8397 });
8398
8399 if let Some(choices) = &tabstop.choices {
8400 if let Some(selection) = tabstop.ranges.first() {
8401 self.show_snippet_choices(choices, selection.clone(), cx)
8402 }
8403 }
8404
8405 // If we're already at the last tabstop and it's at the end of the snippet,
8406 // we're done, we don't need to keep the state around.
8407 if !tabstop.is_end_tabstop {
8408 let choices = tabstops
8409 .iter()
8410 .map(|tabstop| tabstop.choices.clone())
8411 .collect();
8412
8413 let ranges = tabstops
8414 .into_iter()
8415 .map(|tabstop| tabstop.ranges)
8416 .collect::<Vec<_>>();
8417
8418 self.snippet_stack.push(SnippetState {
8419 active_index: 0,
8420 ranges,
8421 choices,
8422 });
8423 }
8424
8425 // Check whether the just-entered snippet ends with an auto-closable bracket.
8426 if self.autoclose_regions.is_empty() {
8427 let snapshot = self.buffer.read(cx).snapshot(cx);
8428 for selection in &mut self.selections.all::<Point>(cx) {
8429 let selection_head = selection.head();
8430 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8431 continue;
8432 };
8433
8434 let mut bracket_pair = None;
8435 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8436 let prev_chars = snapshot
8437 .reversed_chars_at(selection_head)
8438 .collect::<String>();
8439 for (pair, enabled) in scope.brackets() {
8440 if enabled
8441 && pair.close
8442 && prev_chars.starts_with(pair.start.as_str())
8443 && next_chars.starts_with(pair.end.as_str())
8444 {
8445 bracket_pair = Some(pair.clone());
8446 break;
8447 }
8448 }
8449 if let Some(pair) = bracket_pair {
8450 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8451 let autoclose_enabled =
8452 self.use_autoclose && snapshot_settings.use_autoclose;
8453 if autoclose_enabled {
8454 let start = snapshot.anchor_after(selection_head);
8455 let end = snapshot.anchor_after(selection_head);
8456 self.autoclose_regions.push(AutocloseRegion {
8457 selection_id: selection.id,
8458 range: start..end,
8459 pair,
8460 });
8461 }
8462 }
8463 }
8464 }
8465 }
8466 Ok(())
8467 }
8468
8469 pub fn move_to_next_snippet_tabstop(
8470 &mut self,
8471 window: &mut Window,
8472 cx: &mut Context<Self>,
8473 ) -> bool {
8474 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8475 }
8476
8477 pub fn move_to_prev_snippet_tabstop(
8478 &mut self,
8479 window: &mut Window,
8480 cx: &mut Context<Self>,
8481 ) -> bool {
8482 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8483 }
8484
8485 pub fn move_to_snippet_tabstop(
8486 &mut self,
8487 bias: Bias,
8488 window: &mut Window,
8489 cx: &mut Context<Self>,
8490 ) -> bool {
8491 if let Some(mut snippet) = self.snippet_stack.pop() {
8492 match bias {
8493 Bias::Left => {
8494 if snippet.active_index > 0 {
8495 snippet.active_index -= 1;
8496 } else {
8497 self.snippet_stack.push(snippet);
8498 return false;
8499 }
8500 }
8501 Bias::Right => {
8502 if snippet.active_index + 1 < snippet.ranges.len() {
8503 snippet.active_index += 1;
8504 } else {
8505 self.snippet_stack.push(snippet);
8506 return false;
8507 }
8508 }
8509 }
8510 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8511 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8512 s.select_anchor_ranges(current_ranges.iter().cloned())
8513 });
8514
8515 if let Some(choices) = &snippet.choices[snippet.active_index] {
8516 if let Some(selection) = current_ranges.first() {
8517 self.show_snippet_choices(&choices, selection.clone(), cx);
8518 }
8519 }
8520
8521 // If snippet state is not at the last tabstop, push it back on the stack
8522 if snippet.active_index + 1 < snippet.ranges.len() {
8523 self.snippet_stack.push(snippet);
8524 }
8525 return true;
8526 }
8527 }
8528
8529 false
8530 }
8531
8532 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8533 self.transact(window, cx, |this, window, cx| {
8534 this.select_all(&SelectAll, window, cx);
8535 this.insert("", window, cx);
8536 });
8537 }
8538
8539 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8540 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8541 self.transact(window, cx, |this, window, cx| {
8542 this.select_autoclose_pair(window, cx);
8543 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8544 if !this.linked_edit_ranges.is_empty() {
8545 let selections = this.selections.all::<MultiBufferPoint>(cx);
8546 let snapshot = this.buffer.read(cx).snapshot(cx);
8547
8548 for selection in selections.iter() {
8549 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8550 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8551 if selection_start.buffer_id != selection_end.buffer_id {
8552 continue;
8553 }
8554 if let Some(ranges) =
8555 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8556 {
8557 for (buffer, entries) in ranges {
8558 linked_ranges.entry(buffer).or_default().extend(entries);
8559 }
8560 }
8561 }
8562 }
8563
8564 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8565 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8566 for selection in &mut selections {
8567 if selection.is_empty() {
8568 let old_head = selection.head();
8569 let mut new_head =
8570 movement::left(&display_map, old_head.to_display_point(&display_map))
8571 .to_point(&display_map);
8572 if let Some((buffer, line_buffer_range)) = display_map
8573 .buffer_snapshot
8574 .buffer_line_for_row(MultiBufferRow(old_head.row))
8575 {
8576 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8577 let indent_len = match indent_size.kind {
8578 IndentKind::Space => {
8579 buffer.settings_at(line_buffer_range.start, cx).tab_size
8580 }
8581 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8582 };
8583 if old_head.column <= indent_size.len && old_head.column > 0 {
8584 let indent_len = indent_len.get();
8585 new_head = cmp::min(
8586 new_head,
8587 MultiBufferPoint::new(
8588 old_head.row,
8589 ((old_head.column - 1) / indent_len) * indent_len,
8590 ),
8591 );
8592 }
8593 }
8594
8595 selection.set_head(new_head, SelectionGoal::None);
8596 }
8597 }
8598
8599 this.signature_help_state.set_backspace_pressed(true);
8600 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8601 s.select(selections)
8602 });
8603 this.insert("", window, cx);
8604 let empty_str: Arc<str> = Arc::from("");
8605 for (buffer, edits) in linked_ranges {
8606 let snapshot = buffer.read(cx).snapshot();
8607 use text::ToPoint as TP;
8608
8609 let edits = edits
8610 .into_iter()
8611 .map(|range| {
8612 let end_point = TP::to_point(&range.end, &snapshot);
8613 let mut start_point = TP::to_point(&range.start, &snapshot);
8614
8615 if end_point == start_point {
8616 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8617 .saturating_sub(1);
8618 start_point =
8619 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8620 };
8621
8622 (start_point..end_point, empty_str.clone())
8623 })
8624 .sorted_by_key(|(range, _)| range.start)
8625 .collect::<Vec<_>>();
8626 buffer.update(cx, |this, cx| {
8627 this.edit(edits, None, cx);
8628 })
8629 }
8630 this.refresh_inline_completion(true, false, window, cx);
8631 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8632 });
8633 }
8634
8635 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8636 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8637 self.transact(window, cx, |this, window, cx| {
8638 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8639 s.move_with(|map, selection| {
8640 if selection.is_empty() {
8641 let cursor = movement::right(map, selection.head());
8642 selection.end = cursor;
8643 selection.reversed = true;
8644 selection.goal = SelectionGoal::None;
8645 }
8646 })
8647 });
8648 this.insert("", window, cx);
8649 this.refresh_inline_completion(true, false, window, cx);
8650 });
8651 }
8652
8653 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8654 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8655 if self.move_to_prev_snippet_tabstop(window, cx) {
8656 return;
8657 }
8658 self.outdent(&Outdent, window, cx);
8659 }
8660
8661 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8662 if self.move_to_next_snippet_tabstop(window, cx) {
8663 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8664 return;
8665 }
8666 if self.read_only(cx) {
8667 return;
8668 }
8669 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8670 let mut selections = self.selections.all_adjusted(cx);
8671 let buffer = self.buffer.read(cx);
8672 let snapshot = buffer.snapshot(cx);
8673 let rows_iter = selections.iter().map(|s| s.head().row);
8674 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8675
8676 let has_some_cursor_in_whitespace = selections
8677 .iter()
8678 .filter(|selection| selection.is_empty())
8679 .any(|selection| {
8680 let cursor = selection.head();
8681 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8682 cursor.column < current_indent.len
8683 });
8684
8685 let mut edits = Vec::new();
8686 let mut prev_edited_row = 0;
8687 let mut row_delta = 0;
8688 for selection in &mut selections {
8689 if selection.start.row != prev_edited_row {
8690 row_delta = 0;
8691 }
8692 prev_edited_row = selection.end.row;
8693
8694 // If the selection is non-empty, then increase the indentation of the selected lines.
8695 if !selection.is_empty() {
8696 row_delta =
8697 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8698 continue;
8699 }
8700
8701 // If the selection is empty and the cursor is in the leading whitespace before the
8702 // suggested indentation, then auto-indent the line.
8703 let cursor = selection.head();
8704 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8705 if let Some(suggested_indent) =
8706 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8707 {
8708 // If there exist any empty selection in the leading whitespace, then skip
8709 // indent for selections at the boundary.
8710 if has_some_cursor_in_whitespace
8711 && cursor.column == current_indent.len
8712 && current_indent.len == suggested_indent.len
8713 {
8714 continue;
8715 }
8716
8717 if cursor.column < suggested_indent.len
8718 && cursor.column <= current_indent.len
8719 && current_indent.len <= suggested_indent.len
8720 {
8721 selection.start = Point::new(cursor.row, suggested_indent.len);
8722 selection.end = selection.start;
8723 if row_delta == 0 {
8724 edits.extend(Buffer::edit_for_indent_size_adjustment(
8725 cursor.row,
8726 current_indent,
8727 suggested_indent,
8728 ));
8729 row_delta = suggested_indent.len - current_indent.len;
8730 }
8731 continue;
8732 }
8733 }
8734
8735 // Otherwise, insert a hard or soft tab.
8736 let settings = buffer.language_settings_at(cursor, cx);
8737 let tab_size = if settings.hard_tabs {
8738 IndentSize::tab()
8739 } else {
8740 let tab_size = settings.tab_size.get();
8741 let indent_remainder = snapshot
8742 .text_for_range(Point::new(cursor.row, 0)..cursor)
8743 .flat_map(str::chars)
8744 .fold(row_delta % tab_size, |counter: u32, c| {
8745 if c == '\t' {
8746 0
8747 } else {
8748 (counter + 1) % tab_size
8749 }
8750 });
8751
8752 let chars_to_next_tab_stop = tab_size - indent_remainder;
8753 IndentSize::spaces(chars_to_next_tab_stop)
8754 };
8755 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8756 selection.end = selection.start;
8757 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8758 row_delta += tab_size.len;
8759 }
8760
8761 self.transact(window, cx, |this, window, cx| {
8762 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8763 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8764 s.select(selections)
8765 });
8766 this.refresh_inline_completion(true, false, window, cx);
8767 });
8768 }
8769
8770 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8771 if self.read_only(cx) {
8772 return;
8773 }
8774 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8775 let mut selections = self.selections.all::<Point>(cx);
8776 let mut prev_edited_row = 0;
8777 let mut row_delta = 0;
8778 let mut edits = Vec::new();
8779 let buffer = self.buffer.read(cx);
8780 let snapshot = buffer.snapshot(cx);
8781 for selection in &mut selections {
8782 if selection.start.row != prev_edited_row {
8783 row_delta = 0;
8784 }
8785 prev_edited_row = selection.end.row;
8786
8787 row_delta =
8788 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8789 }
8790
8791 self.transact(window, cx, |this, window, cx| {
8792 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8793 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8794 s.select(selections)
8795 });
8796 });
8797 }
8798
8799 fn indent_selection(
8800 buffer: &MultiBuffer,
8801 snapshot: &MultiBufferSnapshot,
8802 selection: &mut Selection<Point>,
8803 edits: &mut Vec<(Range<Point>, String)>,
8804 delta_for_start_row: u32,
8805 cx: &App,
8806 ) -> u32 {
8807 let settings = buffer.language_settings_at(selection.start, cx);
8808 let tab_size = settings.tab_size.get();
8809 let indent_kind = if settings.hard_tabs {
8810 IndentKind::Tab
8811 } else {
8812 IndentKind::Space
8813 };
8814 let mut start_row = selection.start.row;
8815 let mut end_row = selection.end.row + 1;
8816
8817 // If a selection ends at the beginning of a line, don't indent
8818 // that last line.
8819 if selection.end.column == 0 && selection.end.row > selection.start.row {
8820 end_row -= 1;
8821 }
8822
8823 // Avoid re-indenting a row that has already been indented by a
8824 // previous selection, but still update this selection's column
8825 // to reflect that indentation.
8826 if delta_for_start_row > 0 {
8827 start_row += 1;
8828 selection.start.column += delta_for_start_row;
8829 if selection.end.row == selection.start.row {
8830 selection.end.column += delta_for_start_row;
8831 }
8832 }
8833
8834 let mut delta_for_end_row = 0;
8835 let has_multiple_rows = start_row + 1 != end_row;
8836 for row in start_row..end_row {
8837 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8838 let indent_delta = match (current_indent.kind, indent_kind) {
8839 (IndentKind::Space, IndentKind::Space) => {
8840 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8841 IndentSize::spaces(columns_to_next_tab_stop)
8842 }
8843 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8844 (_, IndentKind::Tab) => IndentSize::tab(),
8845 };
8846
8847 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8848 0
8849 } else {
8850 selection.start.column
8851 };
8852 let row_start = Point::new(row, start);
8853 edits.push((
8854 row_start..row_start,
8855 indent_delta.chars().collect::<String>(),
8856 ));
8857
8858 // Update this selection's endpoints to reflect the indentation.
8859 if row == selection.start.row {
8860 selection.start.column += indent_delta.len;
8861 }
8862 if row == selection.end.row {
8863 selection.end.column += indent_delta.len;
8864 delta_for_end_row = indent_delta.len;
8865 }
8866 }
8867
8868 if selection.start.row == selection.end.row {
8869 delta_for_start_row + delta_for_end_row
8870 } else {
8871 delta_for_end_row
8872 }
8873 }
8874
8875 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8876 if self.read_only(cx) {
8877 return;
8878 }
8879 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8881 let selections = self.selections.all::<Point>(cx);
8882 let mut deletion_ranges = Vec::new();
8883 let mut last_outdent = None;
8884 {
8885 let buffer = self.buffer.read(cx);
8886 let snapshot = buffer.snapshot(cx);
8887 for selection in &selections {
8888 let settings = buffer.language_settings_at(selection.start, cx);
8889 let tab_size = settings.tab_size.get();
8890 let mut rows = selection.spanned_rows(false, &display_map);
8891
8892 // Avoid re-outdenting a row that has already been outdented by a
8893 // previous selection.
8894 if let Some(last_row) = last_outdent {
8895 if last_row == rows.start {
8896 rows.start = rows.start.next_row();
8897 }
8898 }
8899 let has_multiple_rows = rows.len() > 1;
8900 for row in rows.iter_rows() {
8901 let indent_size = snapshot.indent_size_for_line(row);
8902 if indent_size.len > 0 {
8903 let deletion_len = match indent_size.kind {
8904 IndentKind::Space => {
8905 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8906 if columns_to_prev_tab_stop == 0 {
8907 tab_size
8908 } else {
8909 columns_to_prev_tab_stop
8910 }
8911 }
8912 IndentKind::Tab => 1,
8913 };
8914 let start = if has_multiple_rows
8915 || deletion_len > selection.start.column
8916 || indent_size.len < selection.start.column
8917 {
8918 0
8919 } else {
8920 selection.start.column - deletion_len
8921 };
8922 deletion_ranges.push(
8923 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8924 );
8925 last_outdent = Some(row);
8926 }
8927 }
8928 }
8929 }
8930
8931 self.transact(window, cx, |this, window, cx| {
8932 this.buffer.update(cx, |buffer, cx| {
8933 let empty_str: Arc<str> = Arc::default();
8934 buffer.edit(
8935 deletion_ranges
8936 .into_iter()
8937 .map(|range| (range, empty_str.clone())),
8938 None,
8939 cx,
8940 );
8941 });
8942 let selections = this.selections.all::<usize>(cx);
8943 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8944 s.select(selections)
8945 });
8946 });
8947 }
8948
8949 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8950 if self.read_only(cx) {
8951 return;
8952 }
8953 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8954 let selections = self
8955 .selections
8956 .all::<usize>(cx)
8957 .into_iter()
8958 .map(|s| s.range());
8959
8960 self.transact(window, cx, |this, window, cx| {
8961 this.buffer.update(cx, |buffer, cx| {
8962 buffer.autoindent_ranges(selections, cx);
8963 });
8964 let selections = this.selections.all::<usize>(cx);
8965 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8966 s.select(selections)
8967 });
8968 });
8969 }
8970
8971 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8972 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8973 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8974 let selections = self.selections.all::<Point>(cx);
8975
8976 let mut new_cursors = Vec::new();
8977 let mut edit_ranges = Vec::new();
8978 let mut selections = selections.iter().peekable();
8979 while let Some(selection) = selections.next() {
8980 let mut rows = selection.spanned_rows(false, &display_map);
8981 let goal_display_column = selection.head().to_display_point(&display_map).column();
8982
8983 // Accumulate contiguous regions of rows that we want to delete.
8984 while let Some(next_selection) = selections.peek() {
8985 let next_rows = next_selection.spanned_rows(false, &display_map);
8986 if next_rows.start <= rows.end {
8987 rows.end = next_rows.end;
8988 selections.next().unwrap();
8989 } else {
8990 break;
8991 }
8992 }
8993
8994 let buffer = &display_map.buffer_snapshot;
8995 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8996 let edit_end;
8997 let cursor_buffer_row;
8998 if buffer.max_point().row >= rows.end.0 {
8999 // If there's a line after the range, delete the \n from the end of the row range
9000 // and position the cursor on the next line.
9001 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9002 cursor_buffer_row = rows.end;
9003 } else {
9004 // If there isn't a line after the range, delete the \n from the line before the
9005 // start of the row range and position the cursor there.
9006 edit_start = edit_start.saturating_sub(1);
9007 edit_end = buffer.len();
9008 cursor_buffer_row = rows.start.previous_row();
9009 }
9010
9011 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9012 *cursor.column_mut() =
9013 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9014
9015 new_cursors.push((
9016 selection.id,
9017 buffer.anchor_after(cursor.to_point(&display_map)),
9018 ));
9019 edit_ranges.push(edit_start..edit_end);
9020 }
9021
9022 self.transact(window, cx, |this, window, cx| {
9023 let buffer = this.buffer.update(cx, |buffer, cx| {
9024 let empty_str: Arc<str> = Arc::default();
9025 buffer.edit(
9026 edit_ranges
9027 .into_iter()
9028 .map(|range| (range, empty_str.clone())),
9029 None,
9030 cx,
9031 );
9032 buffer.snapshot(cx)
9033 });
9034 let new_selections = new_cursors
9035 .into_iter()
9036 .map(|(id, cursor)| {
9037 let cursor = cursor.to_point(&buffer);
9038 Selection {
9039 id,
9040 start: cursor,
9041 end: cursor,
9042 reversed: false,
9043 goal: SelectionGoal::None,
9044 }
9045 })
9046 .collect();
9047
9048 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9049 s.select(new_selections);
9050 });
9051 });
9052 }
9053
9054 pub fn join_lines_impl(
9055 &mut self,
9056 insert_whitespace: bool,
9057 window: &mut Window,
9058 cx: &mut Context<Self>,
9059 ) {
9060 if self.read_only(cx) {
9061 return;
9062 }
9063 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9064 for selection in self.selections.all::<Point>(cx) {
9065 let start = MultiBufferRow(selection.start.row);
9066 // Treat single line selections as if they include the next line. Otherwise this action
9067 // would do nothing for single line selections individual cursors.
9068 let end = if selection.start.row == selection.end.row {
9069 MultiBufferRow(selection.start.row + 1)
9070 } else {
9071 MultiBufferRow(selection.end.row)
9072 };
9073
9074 if let Some(last_row_range) = row_ranges.last_mut() {
9075 if start <= last_row_range.end {
9076 last_row_range.end = end;
9077 continue;
9078 }
9079 }
9080 row_ranges.push(start..end);
9081 }
9082
9083 let snapshot = self.buffer.read(cx).snapshot(cx);
9084 let mut cursor_positions = Vec::new();
9085 for row_range in &row_ranges {
9086 let anchor = snapshot.anchor_before(Point::new(
9087 row_range.end.previous_row().0,
9088 snapshot.line_len(row_range.end.previous_row()),
9089 ));
9090 cursor_positions.push(anchor..anchor);
9091 }
9092
9093 self.transact(window, cx, |this, window, cx| {
9094 for row_range in row_ranges.into_iter().rev() {
9095 for row in row_range.iter_rows().rev() {
9096 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9097 let next_line_row = row.next_row();
9098 let indent = snapshot.indent_size_for_line(next_line_row);
9099 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9100
9101 let replace =
9102 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9103 " "
9104 } else {
9105 ""
9106 };
9107
9108 this.buffer.update(cx, |buffer, cx| {
9109 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9110 });
9111 }
9112 }
9113
9114 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9115 s.select_anchor_ranges(cursor_positions)
9116 });
9117 });
9118 }
9119
9120 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9121 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9122 self.join_lines_impl(true, window, cx);
9123 }
9124
9125 pub fn sort_lines_case_sensitive(
9126 &mut self,
9127 _: &SortLinesCaseSensitive,
9128 window: &mut Window,
9129 cx: &mut Context<Self>,
9130 ) {
9131 self.manipulate_lines(window, cx, |lines| lines.sort())
9132 }
9133
9134 pub fn sort_lines_case_insensitive(
9135 &mut self,
9136 _: &SortLinesCaseInsensitive,
9137 window: &mut Window,
9138 cx: &mut Context<Self>,
9139 ) {
9140 self.manipulate_lines(window, cx, |lines| {
9141 lines.sort_by_key(|line| line.to_lowercase())
9142 })
9143 }
9144
9145 pub fn unique_lines_case_insensitive(
9146 &mut self,
9147 _: &UniqueLinesCaseInsensitive,
9148 window: &mut Window,
9149 cx: &mut Context<Self>,
9150 ) {
9151 self.manipulate_lines(window, cx, |lines| {
9152 let mut seen = HashSet::default();
9153 lines.retain(|line| seen.insert(line.to_lowercase()));
9154 })
9155 }
9156
9157 pub fn unique_lines_case_sensitive(
9158 &mut self,
9159 _: &UniqueLinesCaseSensitive,
9160 window: &mut Window,
9161 cx: &mut Context<Self>,
9162 ) {
9163 self.manipulate_lines(window, cx, |lines| {
9164 let mut seen = HashSet::default();
9165 lines.retain(|line| seen.insert(*line));
9166 })
9167 }
9168
9169 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9170 let Some(project) = self.project.clone() else {
9171 return;
9172 };
9173 self.reload(project, window, cx)
9174 .detach_and_notify_err(window, cx);
9175 }
9176
9177 pub fn restore_file(
9178 &mut self,
9179 _: &::git::RestoreFile,
9180 window: &mut Window,
9181 cx: &mut Context<Self>,
9182 ) {
9183 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9184 let mut buffer_ids = HashSet::default();
9185 let snapshot = self.buffer().read(cx).snapshot(cx);
9186 for selection in self.selections.all::<usize>(cx) {
9187 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9188 }
9189
9190 let buffer = self.buffer().read(cx);
9191 let ranges = buffer_ids
9192 .into_iter()
9193 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9194 .collect::<Vec<_>>();
9195
9196 self.restore_hunks_in_ranges(ranges, window, cx);
9197 }
9198
9199 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9200 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9201 let selections = self
9202 .selections
9203 .all(cx)
9204 .into_iter()
9205 .map(|s| s.range())
9206 .collect();
9207 self.restore_hunks_in_ranges(selections, window, cx);
9208 }
9209
9210 pub fn restore_hunks_in_ranges(
9211 &mut self,
9212 ranges: Vec<Range<Point>>,
9213 window: &mut Window,
9214 cx: &mut Context<Editor>,
9215 ) {
9216 let mut revert_changes = HashMap::default();
9217 let chunk_by = self
9218 .snapshot(window, cx)
9219 .hunks_for_ranges(ranges)
9220 .into_iter()
9221 .chunk_by(|hunk| hunk.buffer_id);
9222 for (buffer_id, hunks) in &chunk_by {
9223 let hunks = hunks.collect::<Vec<_>>();
9224 for hunk in &hunks {
9225 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9226 }
9227 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9228 }
9229 drop(chunk_by);
9230 if !revert_changes.is_empty() {
9231 self.transact(window, cx, |editor, window, cx| {
9232 editor.restore(revert_changes, window, cx);
9233 });
9234 }
9235 }
9236
9237 pub fn open_active_item_in_terminal(
9238 &mut self,
9239 _: &OpenInTerminal,
9240 window: &mut Window,
9241 cx: &mut Context<Self>,
9242 ) {
9243 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9244 let project_path = buffer.read(cx).project_path(cx)?;
9245 let project = self.project.as_ref()?.read(cx);
9246 let entry = project.entry_for_path(&project_path, cx)?;
9247 let parent = match &entry.canonical_path {
9248 Some(canonical_path) => canonical_path.to_path_buf(),
9249 None => project.absolute_path(&project_path, cx)?,
9250 }
9251 .parent()?
9252 .to_path_buf();
9253 Some(parent)
9254 }) {
9255 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9256 }
9257 }
9258
9259 fn set_breakpoint_context_menu(
9260 &mut self,
9261 display_row: DisplayRow,
9262 position: Option<Anchor>,
9263 clicked_point: gpui::Point<Pixels>,
9264 window: &mut Window,
9265 cx: &mut Context<Self>,
9266 ) {
9267 if !cx.has_flag::<DebuggerFeatureFlag>() {
9268 return;
9269 }
9270 let source = self
9271 .buffer
9272 .read(cx)
9273 .snapshot(cx)
9274 .anchor_before(Point::new(display_row.0, 0u32));
9275
9276 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9277
9278 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9279 self,
9280 source,
9281 clicked_point,
9282 context_menu,
9283 window,
9284 cx,
9285 );
9286 }
9287
9288 fn add_edit_breakpoint_block(
9289 &mut self,
9290 anchor: Anchor,
9291 breakpoint: &Breakpoint,
9292 edit_action: BreakpointPromptEditAction,
9293 window: &mut Window,
9294 cx: &mut Context<Self>,
9295 ) {
9296 let weak_editor = cx.weak_entity();
9297 let bp_prompt = cx.new(|cx| {
9298 BreakpointPromptEditor::new(
9299 weak_editor,
9300 anchor,
9301 breakpoint.clone(),
9302 edit_action,
9303 window,
9304 cx,
9305 )
9306 });
9307
9308 let height = bp_prompt.update(cx, |this, cx| {
9309 this.prompt
9310 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9311 });
9312 let cloned_prompt = bp_prompt.clone();
9313 let blocks = vec![BlockProperties {
9314 style: BlockStyle::Sticky,
9315 placement: BlockPlacement::Above(anchor),
9316 height: Some(height),
9317 render: Arc::new(move |cx| {
9318 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9319 cloned_prompt.clone().into_any_element()
9320 }),
9321 priority: 0,
9322 }];
9323
9324 let focus_handle = bp_prompt.focus_handle(cx);
9325 window.focus(&focus_handle);
9326
9327 let block_ids = self.insert_blocks(blocks, None, cx);
9328 bp_prompt.update(cx, |prompt, _| {
9329 prompt.add_block_ids(block_ids);
9330 });
9331 }
9332
9333 pub(crate) fn breakpoint_at_row(
9334 &self,
9335 row: u32,
9336 window: &mut Window,
9337 cx: &mut Context<Self>,
9338 ) -> Option<(Anchor, Breakpoint)> {
9339 let snapshot = self.snapshot(window, cx);
9340 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9341
9342 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9343 }
9344
9345 pub(crate) fn breakpoint_at_anchor(
9346 &self,
9347 breakpoint_position: Anchor,
9348 snapshot: &EditorSnapshot,
9349 cx: &mut Context<Self>,
9350 ) -> Option<(Anchor, Breakpoint)> {
9351 let project = self.project.clone()?;
9352
9353 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9354 snapshot
9355 .buffer_snapshot
9356 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9357 })?;
9358
9359 let enclosing_excerpt = breakpoint_position.excerpt_id;
9360 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9361 let buffer_snapshot = buffer.read(cx).snapshot();
9362
9363 let row = buffer_snapshot
9364 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9365 .row;
9366
9367 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9368 let anchor_end = snapshot
9369 .buffer_snapshot
9370 .anchor_after(Point::new(row, line_len));
9371
9372 let bp = self
9373 .breakpoint_store
9374 .as_ref()?
9375 .read_with(cx, |breakpoint_store, cx| {
9376 breakpoint_store
9377 .breakpoints(
9378 &buffer,
9379 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9380 &buffer_snapshot,
9381 cx,
9382 )
9383 .next()
9384 .and_then(|(anchor, bp)| {
9385 let breakpoint_row = buffer_snapshot
9386 .summary_for_anchor::<text::PointUtf16>(anchor)
9387 .row;
9388
9389 if breakpoint_row == row {
9390 snapshot
9391 .buffer_snapshot
9392 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9393 .map(|anchor| (anchor, bp.clone()))
9394 } else {
9395 None
9396 }
9397 })
9398 });
9399 bp
9400 }
9401
9402 pub fn edit_log_breakpoint(
9403 &mut self,
9404 _: &EditLogBreakpoint,
9405 window: &mut Window,
9406 cx: &mut Context<Self>,
9407 ) {
9408 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9409 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9410 message: None,
9411 state: BreakpointState::Enabled,
9412 condition: None,
9413 hit_condition: None,
9414 });
9415
9416 self.add_edit_breakpoint_block(
9417 anchor,
9418 &breakpoint,
9419 BreakpointPromptEditAction::Log,
9420 window,
9421 cx,
9422 );
9423 }
9424 }
9425
9426 fn breakpoints_at_cursors(
9427 &self,
9428 window: &mut Window,
9429 cx: &mut Context<Self>,
9430 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9431 let snapshot = self.snapshot(window, cx);
9432 let cursors = self
9433 .selections
9434 .disjoint_anchors()
9435 .into_iter()
9436 .map(|selection| {
9437 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9438
9439 let breakpoint_position = self
9440 .breakpoint_at_row(cursor_position.row, window, cx)
9441 .map(|bp| bp.0)
9442 .unwrap_or_else(|| {
9443 snapshot
9444 .display_snapshot
9445 .buffer_snapshot
9446 .anchor_after(Point::new(cursor_position.row, 0))
9447 });
9448
9449 let breakpoint = self
9450 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9451 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9452
9453 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9454 })
9455 // 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.
9456 .collect::<HashMap<Anchor, _>>();
9457
9458 cursors.into_iter().collect()
9459 }
9460
9461 pub fn enable_breakpoint(
9462 &mut self,
9463 _: &crate::actions::EnableBreakpoint,
9464 window: &mut Window,
9465 cx: &mut Context<Self>,
9466 ) {
9467 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9468 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9469 continue;
9470 };
9471 self.edit_breakpoint_at_anchor(
9472 anchor,
9473 breakpoint,
9474 BreakpointEditAction::InvertState,
9475 cx,
9476 );
9477 }
9478 }
9479
9480 pub fn disable_breakpoint(
9481 &mut self,
9482 _: &crate::actions::DisableBreakpoint,
9483 window: &mut Window,
9484 cx: &mut Context<Self>,
9485 ) {
9486 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9487 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9488 continue;
9489 };
9490 self.edit_breakpoint_at_anchor(
9491 anchor,
9492 breakpoint,
9493 BreakpointEditAction::InvertState,
9494 cx,
9495 );
9496 }
9497 }
9498
9499 pub fn toggle_breakpoint(
9500 &mut self,
9501 _: &crate::actions::ToggleBreakpoint,
9502 window: &mut Window,
9503 cx: &mut Context<Self>,
9504 ) {
9505 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9506 if let Some(breakpoint) = breakpoint {
9507 self.edit_breakpoint_at_anchor(
9508 anchor,
9509 breakpoint,
9510 BreakpointEditAction::Toggle,
9511 cx,
9512 );
9513 } else {
9514 self.edit_breakpoint_at_anchor(
9515 anchor,
9516 Breakpoint::new_standard(),
9517 BreakpointEditAction::Toggle,
9518 cx,
9519 );
9520 }
9521 }
9522 }
9523
9524 pub fn edit_breakpoint_at_anchor(
9525 &mut self,
9526 breakpoint_position: Anchor,
9527 breakpoint: Breakpoint,
9528 edit_action: BreakpointEditAction,
9529 cx: &mut Context<Self>,
9530 ) {
9531 let Some(breakpoint_store) = &self.breakpoint_store else {
9532 return;
9533 };
9534
9535 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9536 if breakpoint_position == Anchor::min() {
9537 self.buffer()
9538 .read(cx)
9539 .excerpt_buffer_ids()
9540 .into_iter()
9541 .next()
9542 } else {
9543 None
9544 }
9545 }) else {
9546 return;
9547 };
9548
9549 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9550 return;
9551 };
9552
9553 breakpoint_store.update(cx, |breakpoint_store, cx| {
9554 breakpoint_store.toggle_breakpoint(
9555 buffer,
9556 (breakpoint_position.text_anchor, breakpoint),
9557 edit_action,
9558 cx,
9559 );
9560 });
9561
9562 cx.notify();
9563 }
9564
9565 #[cfg(any(test, feature = "test-support"))]
9566 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9567 self.breakpoint_store.clone()
9568 }
9569
9570 pub fn prepare_restore_change(
9571 &self,
9572 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9573 hunk: &MultiBufferDiffHunk,
9574 cx: &mut App,
9575 ) -> Option<()> {
9576 if hunk.is_created_file() {
9577 return None;
9578 }
9579 let buffer = self.buffer.read(cx);
9580 let diff = buffer.diff_for(hunk.buffer_id)?;
9581 let buffer = buffer.buffer(hunk.buffer_id)?;
9582 let buffer = buffer.read(cx);
9583 let original_text = diff
9584 .read(cx)
9585 .base_text()
9586 .as_rope()
9587 .slice(hunk.diff_base_byte_range.clone());
9588 let buffer_snapshot = buffer.snapshot();
9589 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9590 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9591 probe
9592 .0
9593 .start
9594 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9595 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9596 }) {
9597 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9598 Some(())
9599 } else {
9600 None
9601 }
9602 }
9603
9604 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9605 self.manipulate_lines(window, cx, |lines| lines.reverse())
9606 }
9607
9608 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9609 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9610 }
9611
9612 fn manipulate_lines<Fn>(
9613 &mut self,
9614 window: &mut Window,
9615 cx: &mut Context<Self>,
9616 mut callback: Fn,
9617 ) where
9618 Fn: FnMut(&mut Vec<&str>),
9619 {
9620 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9621
9622 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9623 let buffer = self.buffer.read(cx).snapshot(cx);
9624
9625 let mut edits = Vec::new();
9626
9627 let selections = self.selections.all::<Point>(cx);
9628 let mut selections = selections.iter().peekable();
9629 let mut contiguous_row_selections = Vec::new();
9630 let mut new_selections = Vec::new();
9631 let mut added_lines = 0;
9632 let mut removed_lines = 0;
9633
9634 while let Some(selection) = selections.next() {
9635 let (start_row, end_row) = consume_contiguous_rows(
9636 &mut contiguous_row_selections,
9637 selection,
9638 &display_map,
9639 &mut selections,
9640 );
9641
9642 let start_point = Point::new(start_row.0, 0);
9643 let end_point = Point::new(
9644 end_row.previous_row().0,
9645 buffer.line_len(end_row.previous_row()),
9646 );
9647 let text = buffer
9648 .text_for_range(start_point..end_point)
9649 .collect::<String>();
9650
9651 let mut lines = text.split('\n').collect_vec();
9652
9653 let lines_before = lines.len();
9654 callback(&mut lines);
9655 let lines_after = lines.len();
9656
9657 edits.push((start_point..end_point, lines.join("\n")));
9658
9659 // Selections must change based on added and removed line count
9660 let start_row =
9661 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9662 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9663 new_selections.push(Selection {
9664 id: selection.id,
9665 start: start_row,
9666 end: end_row,
9667 goal: SelectionGoal::None,
9668 reversed: selection.reversed,
9669 });
9670
9671 if lines_after > lines_before {
9672 added_lines += lines_after - lines_before;
9673 } else if lines_before > lines_after {
9674 removed_lines += lines_before - lines_after;
9675 }
9676 }
9677
9678 self.transact(window, cx, |this, window, cx| {
9679 let buffer = this.buffer.update(cx, |buffer, cx| {
9680 buffer.edit(edits, None, cx);
9681 buffer.snapshot(cx)
9682 });
9683
9684 // Recalculate offsets on newly edited buffer
9685 let new_selections = new_selections
9686 .iter()
9687 .map(|s| {
9688 let start_point = Point::new(s.start.0, 0);
9689 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9690 Selection {
9691 id: s.id,
9692 start: buffer.point_to_offset(start_point),
9693 end: buffer.point_to_offset(end_point),
9694 goal: s.goal,
9695 reversed: s.reversed,
9696 }
9697 })
9698 .collect();
9699
9700 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9701 s.select(new_selections);
9702 });
9703
9704 this.request_autoscroll(Autoscroll::fit(), cx);
9705 });
9706 }
9707
9708 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9709 self.manipulate_text(window, cx, |text| {
9710 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9711 if has_upper_case_characters {
9712 text.to_lowercase()
9713 } else {
9714 text.to_uppercase()
9715 }
9716 })
9717 }
9718
9719 pub fn convert_to_upper_case(
9720 &mut self,
9721 _: &ConvertToUpperCase,
9722 window: &mut Window,
9723 cx: &mut Context<Self>,
9724 ) {
9725 self.manipulate_text(window, cx, |text| text.to_uppercase())
9726 }
9727
9728 pub fn convert_to_lower_case(
9729 &mut self,
9730 _: &ConvertToLowerCase,
9731 window: &mut Window,
9732 cx: &mut Context<Self>,
9733 ) {
9734 self.manipulate_text(window, cx, |text| text.to_lowercase())
9735 }
9736
9737 pub fn convert_to_title_case(
9738 &mut self,
9739 _: &ConvertToTitleCase,
9740 window: &mut Window,
9741 cx: &mut Context<Self>,
9742 ) {
9743 self.manipulate_text(window, cx, |text| {
9744 text.split('\n')
9745 .map(|line| line.to_case(Case::Title))
9746 .join("\n")
9747 })
9748 }
9749
9750 pub fn convert_to_snake_case(
9751 &mut self,
9752 _: &ConvertToSnakeCase,
9753 window: &mut Window,
9754 cx: &mut Context<Self>,
9755 ) {
9756 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9757 }
9758
9759 pub fn convert_to_kebab_case(
9760 &mut self,
9761 _: &ConvertToKebabCase,
9762 window: &mut Window,
9763 cx: &mut Context<Self>,
9764 ) {
9765 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9766 }
9767
9768 pub fn convert_to_upper_camel_case(
9769 &mut self,
9770 _: &ConvertToUpperCamelCase,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) {
9774 self.manipulate_text(window, cx, |text| {
9775 text.split('\n')
9776 .map(|line| line.to_case(Case::UpperCamel))
9777 .join("\n")
9778 })
9779 }
9780
9781 pub fn convert_to_lower_camel_case(
9782 &mut self,
9783 _: &ConvertToLowerCamelCase,
9784 window: &mut Window,
9785 cx: &mut Context<Self>,
9786 ) {
9787 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9788 }
9789
9790 pub fn convert_to_opposite_case(
9791 &mut self,
9792 _: &ConvertToOppositeCase,
9793 window: &mut Window,
9794 cx: &mut Context<Self>,
9795 ) {
9796 self.manipulate_text(window, cx, |text| {
9797 text.chars()
9798 .fold(String::with_capacity(text.len()), |mut t, c| {
9799 if c.is_uppercase() {
9800 t.extend(c.to_lowercase());
9801 } else {
9802 t.extend(c.to_uppercase());
9803 }
9804 t
9805 })
9806 })
9807 }
9808
9809 pub fn convert_to_rot13(
9810 &mut self,
9811 _: &ConvertToRot13,
9812 window: &mut Window,
9813 cx: &mut Context<Self>,
9814 ) {
9815 self.manipulate_text(window, cx, |text| {
9816 text.chars()
9817 .map(|c| match c {
9818 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9819 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9820 _ => c,
9821 })
9822 .collect()
9823 })
9824 }
9825
9826 pub fn convert_to_rot47(
9827 &mut self,
9828 _: &ConvertToRot47,
9829 window: &mut Window,
9830 cx: &mut Context<Self>,
9831 ) {
9832 self.manipulate_text(window, cx, |text| {
9833 text.chars()
9834 .map(|c| {
9835 let code_point = c as u32;
9836 if code_point >= 33 && code_point <= 126 {
9837 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9838 }
9839 c
9840 })
9841 .collect()
9842 })
9843 }
9844
9845 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9846 where
9847 Fn: FnMut(&str) -> String,
9848 {
9849 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9850 let buffer = self.buffer.read(cx).snapshot(cx);
9851
9852 let mut new_selections = Vec::new();
9853 let mut edits = Vec::new();
9854 let mut selection_adjustment = 0i32;
9855
9856 for selection in self.selections.all::<usize>(cx) {
9857 let selection_is_empty = selection.is_empty();
9858
9859 let (start, end) = if selection_is_empty {
9860 let word_range = movement::surrounding_word(
9861 &display_map,
9862 selection.start.to_display_point(&display_map),
9863 );
9864 let start = word_range.start.to_offset(&display_map, Bias::Left);
9865 let end = word_range.end.to_offset(&display_map, Bias::Left);
9866 (start, end)
9867 } else {
9868 (selection.start, selection.end)
9869 };
9870
9871 let text = buffer.text_for_range(start..end).collect::<String>();
9872 let old_length = text.len() as i32;
9873 let text = callback(&text);
9874
9875 new_selections.push(Selection {
9876 start: (start as i32 - selection_adjustment) as usize,
9877 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9878 goal: SelectionGoal::None,
9879 ..selection
9880 });
9881
9882 selection_adjustment += old_length - text.len() as i32;
9883
9884 edits.push((start..end, text));
9885 }
9886
9887 self.transact(window, cx, |this, window, cx| {
9888 this.buffer.update(cx, |buffer, cx| {
9889 buffer.edit(edits, None, cx);
9890 });
9891
9892 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9893 s.select(new_selections);
9894 });
9895
9896 this.request_autoscroll(Autoscroll::fit(), cx);
9897 });
9898 }
9899
9900 pub fn duplicate(
9901 &mut self,
9902 upwards: bool,
9903 whole_lines: bool,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) {
9907 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9908
9909 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9910 let buffer = &display_map.buffer_snapshot;
9911 let selections = self.selections.all::<Point>(cx);
9912
9913 let mut edits = Vec::new();
9914 let mut selections_iter = selections.iter().peekable();
9915 while let Some(selection) = selections_iter.next() {
9916 let mut rows = selection.spanned_rows(false, &display_map);
9917 // duplicate line-wise
9918 if whole_lines || selection.start == selection.end {
9919 // Avoid duplicating the same lines twice.
9920 while let Some(next_selection) = selections_iter.peek() {
9921 let next_rows = next_selection.spanned_rows(false, &display_map);
9922 if next_rows.start < rows.end {
9923 rows.end = next_rows.end;
9924 selections_iter.next().unwrap();
9925 } else {
9926 break;
9927 }
9928 }
9929
9930 // Copy the text from the selected row region and splice it either at the start
9931 // or end of the region.
9932 let start = Point::new(rows.start.0, 0);
9933 let end = Point::new(
9934 rows.end.previous_row().0,
9935 buffer.line_len(rows.end.previous_row()),
9936 );
9937 let text = buffer
9938 .text_for_range(start..end)
9939 .chain(Some("\n"))
9940 .collect::<String>();
9941 let insert_location = if upwards {
9942 Point::new(rows.end.0, 0)
9943 } else {
9944 start
9945 };
9946 edits.push((insert_location..insert_location, text));
9947 } else {
9948 // duplicate character-wise
9949 let start = selection.start;
9950 let end = selection.end;
9951 let text = buffer.text_for_range(start..end).collect::<String>();
9952 edits.push((selection.end..selection.end, text));
9953 }
9954 }
9955
9956 self.transact(window, cx, |this, _, cx| {
9957 this.buffer.update(cx, |buffer, cx| {
9958 buffer.edit(edits, None, cx);
9959 });
9960
9961 this.request_autoscroll(Autoscroll::fit(), cx);
9962 });
9963 }
9964
9965 pub fn duplicate_line_up(
9966 &mut self,
9967 _: &DuplicateLineUp,
9968 window: &mut Window,
9969 cx: &mut Context<Self>,
9970 ) {
9971 self.duplicate(true, true, window, cx);
9972 }
9973
9974 pub fn duplicate_line_down(
9975 &mut self,
9976 _: &DuplicateLineDown,
9977 window: &mut Window,
9978 cx: &mut Context<Self>,
9979 ) {
9980 self.duplicate(false, true, window, cx);
9981 }
9982
9983 pub fn duplicate_selection(
9984 &mut self,
9985 _: &DuplicateSelection,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) {
9989 self.duplicate(false, false, window, cx);
9990 }
9991
9992 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9993 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9994
9995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9996 let buffer = self.buffer.read(cx).snapshot(cx);
9997
9998 let mut edits = Vec::new();
9999 let mut unfold_ranges = Vec::new();
10000 let mut refold_creases = Vec::new();
10001
10002 let selections = self.selections.all::<Point>(cx);
10003 let mut selections = selections.iter().peekable();
10004 let mut contiguous_row_selections = Vec::new();
10005 let mut new_selections = Vec::new();
10006
10007 while let Some(selection) = selections.next() {
10008 // Find all the selections that span a contiguous row range
10009 let (start_row, end_row) = consume_contiguous_rows(
10010 &mut contiguous_row_selections,
10011 selection,
10012 &display_map,
10013 &mut selections,
10014 );
10015
10016 // Move the text spanned by the row range to be before the line preceding the row range
10017 if start_row.0 > 0 {
10018 let range_to_move = Point::new(
10019 start_row.previous_row().0,
10020 buffer.line_len(start_row.previous_row()),
10021 )
10022 ..Point::new(
10023 end_row.previous_row().0,
10024 buffer.line_len(end_row.previous_row()),
10025 );
10026 let insertion_point = display_map
10027 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10028 .0;
10029
10030 // Don't move lines across excerpts
10031 if buffer
10032 .excerpt_containing(insertion_point..range_to_move.end)
10033 .is_some()
10034 {
10035 let text = buffer
10036 .text_for_range(range_to_move.clone())
10037 .flat_map(|s| s.chars())
10038 .skip(1)
10039 .chain(['\n'])
10040 .collect::<String>();
10041
10042 edits.push((
10043 buffer.anchor_after(range_to_move.start)
10044 ..buffer.anchor_before(range_to_move.end),
10045 String::new(),
10046 ));
10047 let insertion_anchor = buffer.anchor_after(insertion_point);
10048 edits.push((insertion_anchor..insertion_anchor, text));
10049
10050 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10051
10052 // Move selections up
10053 new_selections.extend(contiguous_row_selections.drain(..).map(
10054 |mut selection| {
10055 selection.start.row -= row_delta;
10056 selection.end.row -= row_delta;
10057 selection
10058 },
10059 ));
10060
10061 // Move folds up
10062 unfold_ranges.push(range_to_move.clone());
10063 for fold in display_map.folds_in_range(
10064 buffer.anchor_before(range_to_move.start)
10065 ..buffer.anchor_after(range_to_move.end),
10066 ) {
10067 let mut start = fold.range.start.to_point(&buffer);
10068 let mut end = fold.range.end.to_point(&buffer);
10069 start.row -= row_delta;
10070 end.row -= row_delta;
10071 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10072 }
10073 }
10074 }
10075
10076 // If we didn't move line(s), preserve the existing selections
10077 new_selections.append(&mut contiguous_row_selections);
10078 }
10079
10080 self.transact(window, cx, |this, window, cx| {
10081 this.unfold_ranges(&unfold_ranges, true, true, cx);
10082 this.buffer.update(cx, |buffer, cx| {
10083 for (range, text) in edits {
10084 buffer.edit([(range, text)], None, cx);
10085 }
10086 });
10087 this.fold_creases(refold_creases, true, window, cx);
10088 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10089 s.select(new_selections);
10090 })
10091 });
10092 }
10093
10094 pub fn move_line_down(
10095 &mut self,
10096 _: &MoveLineDown,
10097 window: &mut Window,
10098 cx: &mut Context<Self>,
10099 ) {
10100 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10101
10102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10103 let buffer = self.buffer.read(cx).snapshot(cx);
10104
10105 let mut edits = Vec::new();
10106 let mut unfold_ranges = Vec::new();
10107 let mut refold_creases = Vec::new();
10108
10109 let selections = self.selections.all::<Point>(cx);
10110 let mut selections = selections.iter().peekable();
10111 let mut contiguous_row_selections = Vec::new();
10112 let mut new_selections = Vec::new();
10113
10114 while let Some(selection) = selections.next() {
10115 // Find all the selections that span a contiguous row range
10116 let (start_row, end_row) = consume_contiguous_rows(
10117 &mut contiguous_row_selections,
10118 selection,
10119 &display_map,
10120 &mut selections,
10121 );
10122
10123 // Move the text spanned by the row range to be after the last line of the row range
10124 if end_row.0 <= buffer.max_point().row {
10125 let range_to_move =
10126 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10127 let insertion_point = display_map
10128 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10129 .0;
10130
10131 // Don't move lines across excerpt boundaries
10132 if buffer
10133 .excerpt_containing(range_to_move.start..insertion_point)
10134 .is_some()
10135 {
10136 let mut text = String::from("\n");
10137 text.extend(buffer.text_for_range(range_to_move.clone()));
10138 text.pop(); // Drop trailing newline
10139 edits.push((
10140 buffer.anchor_after(range_to_move.start)
10141 ..buffer.anchor_before(range_to_move.end),
10142 String::new(),
10143 ));
10144 let insertion_anchor = buffer.anchor_after(insertion_point);
10145 edits.push((insertion_anchor..insertion_anchor, text));
10146
10147 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10148
10149 // Move selections down
10150 new_selections.extend(contiguous_row_selections.drain(..).map(
10151 |mut selection| {
10152 selection.start.row += row_delta;
10153 selection.end.row += row_delta;
10154 selection
10155 },
10156 ));
10157
10158 // Move folds down
10159 unfold_ranges.push(range_to_move.clone());
10160 for fold in display_map.folds_in_range(
10161 buffer.anchor_before(range_to_move.start)
10162 ..buffer.anchor_after(range_to_move.end),
10163 ) {
10164 let mut start = fold.range.start.to_point(&buffer);
10165 let mut end = fold.range.end.to_point(&buffer);
10166 start.row += row_delta;
10167 end.row += row_delta;
10168 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10169 }
10170 }
10171 }
10172
10173 // If we didn't move line(s), preserve the existing selections
10174 new_selections.append(&mut contiguous_row_selections);
10175 }
10176
10177 self.transact(window, cx, |this, window, cx| {
10178 this.unfold_ranges(&unfold_ranges, true, true, cx);
10179 this.buffer.update(cx, |buffer, cx| {
10180 for (range, text) in edits {
10181 buffer.edit([(range, text)], None, cx);
10182 }
10183 });
10184 this.fold_creases(refold_creases, true, window, cx);
10185 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10186 s.select(new_selections)
10187 });
10188 });
10189 }
10190
10191 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10192 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10193 let text_layout_details = &self.text_layout_details(window);
10194 self.transact(window, cx, |this, window, cx| {
10195 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10196 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10197 s.move_with(|display_map, selection| {
10198 if !selection.is_empty() {
10199 return;
10200 }
10201
10202 let mut head = selection.head();
10203 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10204 if head.column() == display_map.line_len(head.row()) {
10205 transpose_offset = display_map
10206 .buffer_snapshot
10207 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10208 }
10209
10210 if transpose_offset == 0 {
10211 return;
10212 }
10213
10214 *head.column_mut() += 1;
10215 head = display_map.clip_point(head, Bias::Right);
10216 let goal = SelectionGoal::HorizontalPosition(
10217 display_map
10218 .x_for_display_point(head, text_layout_details)
10219 .into(),
10220 );
10221 selection.collapse_to(head, goal);
10222
10223 let transpose_start = display_map
10224 .buffer_snapshot
10225 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10226 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10227 let transpose_end = display_map
10228 .buffer_snapshot
10229 .clip_offset(transpose_offset + 1, Bias::Right);
10230 if let Some(ch) =
10231 display_map.buffer_snapshot.chars_at(transpose_start).next()
10232 {
10233 edits.push((transpose_start..transpose_offset, String::new()));
10234 edits.push((transpose_end..transpose_end, ch.to_string()));
10235 }
10236 }
10237 });
10238 edits
10239 });
10240 this.buffer
10241 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10242 let selections = this.selections.all::<usize>(cx);
10243 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10244 s.select(selections);
10245 });
10246 });
10247 }
10248
10249 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10250 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10251 self.rewrap_impl(RewrapOptions::default(), cx)
10252 }
10253
10254 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10255 let buffer = self.buffer.read(cx).snapshot(cx);
10256 let selections = self.selections.all::<Point>(cx);
10257 let mut selections = selections.iter().peekable();
10258
10259 let mut edits = Vec::new();
10260 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10261
10262 while let Some(selection) = selections.next() {
10263 let mut start_row = selection.start.row;
10264 let mut end_row = selection.end.row;
10265
10266 // Skip selections that overlap with a range that has already been rewrapped.
10267 let selection_range = start_row..end_row;
10268 if rewrapped_row_ranges
10269 .iter()
10270 .any(|range| range.overlaps(&selection_range))
10271 {
10272 continue;
10273 }
10274
10275 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10276
10277 // Since not all lines in the selection may be at the same indent
10278 // level, choose the indent size that is the most common between all
10279 // of the lines.
10280 //
10281 // If there is a tie, we use the deepest indent.
10282 let (indent_size, indent_end) = {
10283 let mut indent_size_occurrences = HashMap::default();
10284 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10285
10286 for row in start_row..=end_row {
10287 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10288 rows_by_indent_size.entry(indent).or_default().push(row);
10289 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10290 }
10291
10292 let indent_size = indent_size_occurrences
10293 .into_iter()
10294 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10295 .map(|(indent, _)| indent)
10296 .unwrap_or_default();
10297 let row = rows_by_indent_size[&indent_size][0];
10298 let indent_end = Point::new(row, indent_size.len);
10299
10300 (indent_size, indent_end)
10301 };
10302
10303 let mut line_prefix = indent_size.chars().collect::<String>();
10304
10305 let mut inside_comment = false;
10306 if let Some(comment_prefix) =
10307 buffer
10308 .language_scope_at(selection.head())
10309 .and_then(|language| {
10310 language
10311 .line_comment_prefixes()
10312 .iter()
10313 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10314 .cloned()
10315 })
10316 {
10317 line_prefix.push_str(&comment_prefix);
10318 inside_comment = true;
10319 }
10320
10321 let language_settings = buffer.language_settings_at(selection.head(), cx);
10322 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10323 RewrapBehavior::InComments => inside_comment,
10324 RewrapBehavior::InSelections => !selection.is_empty(),
10325 RewrapBehavior::Anywhere => true,
10326 };
10327
10328 let should_rewrap = options.override_language_settings
10329 || allow_rewrap_based_on_language
10330 || self.hard_wrap.is_some();
10331 if !should_rewrap {
10332 continue;
10333 }
10334
10335 if selection.is_empty() {
10336 'expand_upwards: while start_row > 0 {
10337 let prev_row = start_row - 1;
10338 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10339 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10340 {
10341 start_row = prev_row;
10342 } else {
10343 break 'expand_upwards;
10344 }
10345 }
10346
10347 'expand_downwards: while end_row < buffer.max_point().row {
10348 let next_row = end_row + 1;
10349 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10350 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10351 {
10352 end_row = next_row;
10353 } else {
10354 break 'expand_downwards;
10355 }
10356 }
10357 }
10358
10359 let start = Point::new(start_row, 0);
10360 let start_offset = start.to_offset(&buffer);
10361 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10362 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10363 let Some(lines_without_prefixes) = selection_text
10364 .lines()
10365 .map(|line| {
10366 line.strip_prefix(&line_prefix)
10367 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10368 .ok_or_else(|| {
10369 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10370 })
10371 })
10372 .collect::<Result<Vec<_>, _>>()
10373 .log_err()
10374 else {
10375 continue;
10376 };
10377
10378 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10379 buffer
10380 .language_settings_at(Point::new(start_row, 0), cx)
10381 .preferred_line_length as usize
10382 });
10383 let wrapped_text = wrap_with_prefix(
10384 line_prefix,
10385 lines_without_prefixes.join("\n"),
10386 wrap_column,
10387 tab_size,
10388 options.preserve_existing_whitespace,
10389 );
10390
10391 // TODO: should always use char-based diff while still supporting cursor behavior that
10392 // matches vim.
10393 let mut diff_options = DiffOptions::default();
10394 if options.override_language_settings {
10395 diff_options.max_word_diff_len = 0;
10396 diff_options.max_word_diff_line_count = 0;
10397 } else {
10398 diff_options.max_word_diff_len = usize::MAX;
10399 diff_options.max_word_diff_line_count = usize::MAX;
10400 }
10401
10402 for (old_range, new_text) in
10403 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10404 {
10405 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10406 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10407 edits.push((edit_start..edit_end, new_text));
10408 }
10409
10410 rewrapped_row_ranges.push(start_row..=end_row);
10411 }
10412
10413 self.buffer
10414 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10415 }
10416
10417 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10418 let mut text = String::new();
10419 let buffer = self.buffer.read(cx).snapshot(cx);
10420 let mut selections = self.selections.all::<Point>(cx);
10421 let mut clipboard_selections = Vec::with_capacity(selections.len());
10422 {
10423 let max_point = buffer.max_point();
10424 let mut is_first = true;
10425 for selection in &mut selections {
10426 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10427 if is_entire_line {
10428 selection.start = Point::new(selection.start.row, 0);
10429 if !selection.is_empty() && selection.end.column == 0 {
10430 selection.end = cmp::min(max_point, selection.end);
10431 } else {
10432 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10433 }
10434 selection.goal = SelectionGoal::None;
10435 }
10436 if is_first {
10437 is_first = false;
10438 } else {
10439 text += "\n";
10440 }
10441 let mut len = 0;
10442 for chunk in buffer.text_for_range(selection.start..selection.end) {
10443 text.push_str(chunk);
10444 len += chunk.len();
10445 }
10446 clipboard_selections.push(ClipboardSelection {
10447 len,
10448 is_entire_line,
10449 first_line_indent: buffer
10450 .indent_size_for_line(MultiBufferRow(selection.start.row))
10451 .len,
10452 });
10453 }
10454 }
10455
10456 self.transact(window, cx, |this, window, cx| {
10457 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10458 s.select(selections);
10459 });
10460 this.insert("", window, cx);
10461 });
10462 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10463 }
10464
10465 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10466 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10467 let item = self.cut_common(window, cx);
10468 cx.write_to_clipboard(item);
10469 }
10470
10471 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10472 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10473 self.change_selections(None, window, cx, |s| {
10474 s.move_with(|snapshot, sel| {
10475 if sel.is_empty() {
10476 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10477 }
10478 });
10479 });
10480 let item = self.cut_common(window, cx);
10481 cx.set_global(KillRing(item))
10482 }
10483
10484 pub fn kill_ring_yank(
10485 &mut self,
10486 _: &KillRingYank,
10487 window: &mut Window,
10488 cx: &mut Context<Self>,
10489 ) {
10490 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10491 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10492 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10493 (kill_ring.text().to_string(), kill_ring.metadata_json())
10494 } else {
10495 return;
10496 }
10497 } else {
10498 return;
10499 };
10500 self.do_paste(&text, metadata, false, window, cx);
10501 }
10502
10503 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10504 self.do_copy(true, cx);
10505 }
10506
10507 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10508 self.do_copy(false, cx);
10509 }
10510
10511 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10512 let selections = self.selections.all::<Point>(cx);
10513 let buffer = self.buffer.read(cx).read(cx);
10514 let mut text = String::new();
10515
10516 let mut clipboard_selections = Vec::with_capacity(selections.len());
10517 {
10518 let max_point = buffer.max_point();
10519 let mut is_first = true;
10520 for selection in &selections {
10521 let mut start = selection.start;
10522 let mut end = selection.end;
10523 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10524 if is_entire_line {
10525 start = Point::new(start.row, 0);
10526 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10527 }
10528
10529 let mut trimmed_selections = Vec::new();
10530 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10531 let row = MultiBufferRow(start.row);
10532 let first_indent = buffer.indent_size_for_line(row);
10533 if first_indent.len == 0 || start.column > first_indent.len {
10534 trimmed_selections.push(start..end);
10535 } else {
10536 trimmed_selections.push(
10537 Point::new(row.0, first_indent.len)
10538 ..Point::new(row.0, buffer.line_len(row)),
10539 );
10540 for row in start.row + 1..=end.row {
10541 let mut line_len = buffer.line_len(MultiBufferRow(row));
10542 if row == end.row {
10543 line_len = end.column;
10544 }
10545 if line_len == 0 {
10546 trimmed_selections
10547 .push(Point::new(row, 0)..Point::new(row, line_len));
10548 continue;
10549 }
10550 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10551 if row_indent_size.len >= first_indent.len {
10552 trimmed_selections.push(
10553 Point::new(row, first_indent.len)..Point::new(row, line_len),
10554 );
10555 } else {
10556 trimmed_selections.clear();
10557 trimmed_selections.push(start..end);
10558 break;
10559 }
10560 }
10561 }
10562 } else {
10563 trimmed_selections.push(start..end);
10564 }
10565
10566 for trimmed_range in trimmed_selections {
10567 if is_first {
10568 is_first = false;
10569 } else {
10570 text += "\n";
10571 }
10572 let mut len = 0;
10573 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10574 text.push_str(chunk);
10575 len += chunk.len();
10576 }
10577 clipboard_selections.push(ClipboardSelection {
10578 len,
10579 is_entire_line,
10580 first_line_indent: buffer
10581 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10582 .len,
10583 });
10584 }
10585 }
10586 }
10587
10588 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10589 text,
10590 clipboard_selections,
10591 ));
10592 }
10593
10594 pub fn do_paste(
10595 &mut self,
10596 text: &String,
10597 clipboard_selections: Option<Vec<ClipboardSelection>>,
10598 handle_entire_lines: bool,
10599 window: &mut Window,
10600 cx: &mut Context<Self>,
10601 ) {
10602 if self.read_only(cx) {
10603 return;
10604 }
10605
10606 let clipboard_text = Cow::Borrowed(text);
10607
10608 self.transact(window, cx, |this, window, cx| {
10609 if let Some(mut clipboard_selections) = clipboard_selections {
10610 let old_selections = this.selections.all::<usize>(cx);
10611 let all_selections_were_entire_line =
10612 clipboard_selections.iter().all(|s| s.is_entire_line);
10613 let first_selection_indent_column =
10614 clipboard_selections.first().map(|s| s.first_line_indent);
10615 if clipboard_selections.len() != old_selections.len() {
10616 clipboard_selections.drain(..);
10617 }
10618 let cursor_offset = this.selections.last::<usize>(cx).head();
10619 let mut auto_indent_on_paste = true;
10620
10621 this.buffer.update(cx, |buffer, cx| {
10622 let snapshot = buffer.read(cx);
10623 auto_indent_on_paste = snapshot
10624 .language_settings_at(cursor_offset, cx)
10625 .auto_indent_on_paste;
10626
10627 let mut start_offset = 0;
10628 let mut edits = Vec::new();
10629 let mut original_indent_columns = Vec::new();
10630 for (ix, selection) in old_selections.iter().enumerate() {
10631 let to_insert;
10632 let entire_line;
10633 let original_indent_column;
10634 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10635 let end_offset = start_offset + clipboard_selection.len;
10636 to_insert = &clipboard_text[start_offset..end_offset];
10637 entire_line = clipboard_selection.is_entire_line;
10638 start_offset = end_offset + 1;
10639 original_indent_column = Some(clipboard_selection.first_line_indent);
10640 } else {
10641 to_insert = clipboard_text.as_str();
10642 entire_line = all_selections_were_entire_line;
10643 original_indent_column = first_selection_indent_column
10644 }
10645
10646 // If the corresponding selection was empty when this slice of the
10647 // clipboard text was written, then the entire line containing the
10648 // selection was copied. If this selection is also currently empty,
10649 // then paste the line before the current line of the buffer.
10650 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10651 let column = selection.start.to_point(&snapshot).column as usize;
10652 let line_start = selection.start - column;
10653 line_start..line_start
10654 } else {
10655 selection.range()
10656 };
10657
10658 edits.push((range, to_insert));
10659 original_indent_columns.push(original_indent_column);
10660 }
10661 drop(snapshot);
10662
10663 buffer.edit(
10664 edits,
10665 if auto_indent_on_paste {
10666 Some(AutoindentMode::Block {
10667 original_indent_columns,
10668 })
10669 } else {
10670 None
10671 },
10672 cx,
10673 );
10674 });
10675
10676 let selections = this.selections.all::<usize>(cx);
10677 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10678 s.select(selections)
10679 });
10680 } else {
10681 this.insert(&clipboard_text, window, cx);
10682 }
10683 });
10684 }
10685
10686 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10687 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10688 if let Some(item) = cx.read_from_clipboard() {
10689 let entries = item.entries();
10690
10691 match entries.first() {
10692 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10693 // of all the pasted entries.
10694 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10695 .do_paste(
10696 clipboard_string.text(),
10697 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10698 true,
10699 window,
10700 cx,
10701 ),
10702 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10703 }
10704 }
10705 }
10706
10707 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10708 if self.read_only(cx) {
10709 return;
10710 }
10711
10712 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10713
10714 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10715 if let Some((selections, _)) =
10716 self.selection_history.transaction(transaction_id).cloned()
10717 {
10718 self.change_selections(None, window, cx, |s| {
10719 s.select_anchors(selections.to_vec());
10720 });
10721 } else {
10722 log::error!(
10723 "No entry in selection_history found for undo. \
10724 This may correspond to a bug where undo does not update the selection. \
10725 If this is occurring, please add details to \
10726 https://github.com/zed-industries/zed/issues/22692"
10727 );
10728 }
10729 self.request_autoscroll(Autoscroll::fit(), cx);
10730 self.unmark_text(window, cx);
10731 self.refresh_inline_completion(true, false, window, cx);
10732 cx.emit(EditorEvent::Edited { transaction_id });
10733 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10734 }
10735 }
10736
10737 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10738 if self.read_only(cx) {
10739 return;
10740 }
10741
10742 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10743
10744 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10745 if let Some((_, Some(selections))) =
10746 self.selection_history.transaction(transaction_id).cloned()
10747 {
10748 self.change_selections(None, window, cx, |s| {
10749 s.select_anchors(selections.to_vec());
10750 });
10751 } else {
10752 log::error!(
10753 "No entry in selection_history found for redo. \
10754 This may correspond to a bug where undo does not update the selection. \
10755 If this is occurring, please add details to \
10756 https://github.com/zed-industries/zed/issues/22692"
10757 );
10758 }
10759 self.request_autoscroll(Autoscroll::fit(), cx);
10760 self.unmark_text(window, cx);
10761 self.refresh_inline_completion(true, false, window, cx);
10762 cx.emit(EditorEvent::Edited { transaction_id });
10763 }
10764 }
10765
10766 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10767 self.buffer
10768 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10769 }
10770
10771 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10772 self.buffer
10773 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10774 }
10775
10776 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10777 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10779 s.move_with(|map, selection| {
10780 let cursor = if selection.is_empty() {
10781 movement::left(map, selection.start)
10782 } else {
10783 selection.start
10784 };
10785 selection.collapse_to(cursor, SelectionGoal::None);
10786 });
10787 })
10788 }
10789
10790 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10791 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10792 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10793 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10794 })
10795 }
10796
10797 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10798 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10799 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10800 s.move_with(|map, selection| {
10801 let cursor = if selection.is_empty() {
10802 movement::right(map, selection.end)
10803 } else {
10804 selection.end
10805 };
10806 selection.collapse_to(cursor, SelectionGoal::None)
10807 });
10808 })
10809 }
10810
10811 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10812 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10813 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10814 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10815 })
10816 }
10817
10818 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10819 if self.take_rename(true, window, cx).is_some() {
10820 return;
10821 }
10822
10823 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10824 cx.propagate();
10825 return;
10826 }
10827
10828 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10829
10830 let text_layout_details = &self.text_layout_details(window);
10831 let selection_count = self.selections.count();
10832 let first_selection = self.selections.first_anchor();
10833
10834 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10835 s.move_with(|map, selection| {
10836 if !selection.is_empty() {
10837 selection.goal = SelectionGoal::None;
10838 }
10839 let (cursor, goal) = movement::up(
10840 map,
10841 selection.start,
10842 selection.goal,
10843 false,
10844 text_layout_details,
10845 );
10846 selection.collapse_to(cursor, goal);
10847 });
10848 });
10849
10850 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10851 {
10852 cx.propagate();
10853 }
10854 }
10855
10856 pub fn move_up_by_lines(
10857 &mut self,
10858 action: &MoveUpByLines,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) {
10862 if self.take_rename(true, window, cx).is_some() {
10863 return;
10864 }
10865
10866 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10867 cx.propagate();
10868 return;
10869 }
10870
10871 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10872
10873 let text_layout_details = &self.text_layout_details(window);
10874
10875 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10876 s.move_with(|map, selection| {
10877 if !selection.is_empty() {
10878 selection.goal = SelectionGoal::None;
10879 }
10880 let (cursor, goal) = movement::up_by_rows(
10881 map,
10882 selection.start,
10883 action.lines,
10884 selection.goal,
10885 false,
10886 text_layout_details,
10887 );
10888 selection.collapse_to(cursor, goal);
10889 });
10890 })
10891 }
10892
10893 pub fn move_down_by_lines(
10894 &mut self,
10895 action: &MoveDownByLines,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 if self.take_rename(true, window, cx).is_some() {
10900 return;
10901 }
10902
10903 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10904 cx.propagate();
10905 return;
10906 }
10907
10908 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10909
10910 let text_layout_details = &self.text_layout_details(window);
10911
10912 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10913 s.move_with(|map, selection| {
10914 if !selection.is_empty() {
10915 selection.goal = SelectionGoal::None;
10916 }
10917 let (cursor, goal) = movement::down_by_rows(
10918 map,
10919 selection.start,
10920 action.lines,
10921 selection.goal,
10922 false,
10923 text_layout_details,
10924 );
10925 selection.collapse_to(cursor, goal);
10926 });
10927 })
10928 }
10929
10930 pub fn select_down_by_lines(
10931 &mut self,
10932 action: &SelectDownByLines,
10933 window: &mut Window,
10934 cx: &mut Context<Self>,
10935 ) {
10936 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10937 let text_layout_details = &self.text_layout_details(window);
10938 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10939 s.move_heads_with(|map, head, goal| {
10940 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10941 })
10942 })
10943 }
10944
10945 pub fn select_up_by_lines(
10946 &mut self,
10947 action: &SelectUpByLines,
10948 window: &mut Window,
10949 cx: &mut Context<Self>,
10950 ) {
10951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10952 let text_layout_details = &self.text_layout_details(window);
10953 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10954 s.move_heads_with(|map, head, goal| {
10955 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10956 })
10957 })
10958 }
10959
10960 pub fn select_page_up(
10961 &mut self,
10962 _: &SelectPageUp,
10963 window: &mut Window,
10964 cx: &mut Context<Self>,
10965 ) {
10966 let Some(row_count) = self.visible_row_count() else {
10967 return;
10968 };
10969
10970 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10971
10972 let text_layout_details = &self.text_layout_details(window);
10973
10974 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10975 s.move_heads_with(|map, head, goal| {
10976 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10977 })
10978 })
10979 }
10980
10981 pub fn move_page_up(
10982 &mut self,
10983 action: &MovePageUp,
10984 window: &mut Window,
10985 cx: &mut Context<Self>,
10986 ) {
10987 if self.take_rename(true, window, cx).is_some() {
10988 return;
10989 }
10990
10991 if self
10992 .context_menu
10993 .borrow_mut()
10994 .as_mut()
10995 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10996 .unwrap_or(false)
10997 {
10998 return;
10999 }
11000
11001 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11002 cx.propagate();
11003 return;
11004 }
11005
11006 let Some(row_count) = self.visible_row_count() else {
11007 return;
11008 };
11009
11010 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11011
11012 let autoscroll = if action.center_cursor {
11013 Autoscroll::center()
11014 } else {
11015 Autoscroll::fit()
11016 };
11017
11018 let text_layout_details = &self.text_layout_details(window);
11019
11020 self.change_selections(Some(autoscroll), window, cx, |s| {
11021 s.move_with(|map, selection| {
11022 if !selection.is_empty() {
11023 selection.goal = SelectionGoal::None;
11024 }
11025 let (cursor, goal) = movement::up_by_rows(
11026 map,
11027 selection.end,
11028 row_count,
11029 selection.goal,
11030 false,
11031 text_layout_details,
11032 );
11033 selection.collapse_to(cursor, goal);
11034 });
11035 });
11036 }
11037
11038 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11039 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11040 let text_layout_details = &self.text_layout_details(window);
11041 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11042 s.move_heads_with(|map, head, goal| {
11043 movement::up(map, head, goal, false, text_layout_details)
11044 })
11045 })
11046 }
11047
11048 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11049 self.take_rename(true, window, cx);
11050
11051 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11052 cx.propagate();
11053 return;
11054 }
11055
11056 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11057
11058 let text_layout_details = &self.text_layout_details(window);
11059 let selection_count = self.selections.count();
11060 let first_selection = self.selections.first_anchor();
11061
11062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11063 s.move_with(|map, selection| {
11064 if !selection.is_empty() {
11065 selection.goal = SelectionGoal::None;
11066 }
11067 let (cursor, goal) = movement::down(
11068 map,
11069 selection.end,
11070 selection.goal,
11071 false,
11072 text_layout_details,
11073 );
11074 selection.collapse_to(cursor, goal);
11075 });
11076 });
11077
11078 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11079 {
11080 cx.propagate();
11081 }
11082 }
11083
11084 pub fn select_page_down(
11085 &mut self,
11086 _: &SelectPageDown,
11087 window: &mut Window,
11088 cx: &mut Context<Self>,
11089 ) {
11090 let Some(row_count) = self.visible_row_count() else {
11091 return;
11092 };
11093
11094 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11095
11096 let text_layout_details = &self.text_layout_details(window);
11097
11098 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11099 s.move_heads_with(|map, head, goal| {
11100 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11101 })
11102 })
11103 }
11104
11105 pub fn move_page_down(
11106 &mut self,
11107 action: &MovePageDown,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 if self.take_rename(true, window, cx).is_some() {
11112 return;
11113 }
11114
11115 if self
11116 .context_menu
11117 .borrow_mut()
11118 .as_mut()
11119 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11120 .unwrap_or(false)
11121 {
11122 return;
11123 }
11124
11125 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11126 cx.propagate();
11127 return;
11128 }
11129
11130 let Some(row_count) = self.visible_row_count() else {
11131 return;
11132 };
11133
11134 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11135
11136 let autoscroll = if action.center_cursor {
11137 Autoscroll::center()
11138 } else {
11139 Autoscroll::fit()
11140 };
11141
11142 let text_layout_details = &self.text_layout_details(window);
11143 self.change_selections(Some(autoscroll), window, cx, |s| {
11144 s.move_with(|map, selection| {
11145 if !selection.is_empty() {
11146 selection.goal = SelectionGoal::None;
11147 }
11148 let (cursor, goal) = movement::down_by_rows(
11149 map,
11150 selection.end,
11151 row_count,
11152 selection.goal,
11153 false,
11154 text_layout_details,
11155 );
11156 selection.collapse_to(cursor, goal);
11157 });
11158 });
11159 }
11160
11161 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11162 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11163 let text_layout_details = &self.text_layout_details(window);
11164 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11165 s.move_heads_with(|map, head, goal| {
11166 movement::down(map, head, goal, false, text_layout_details)
11167 })
11168 });
11169 }
11170
11171 pub fn context_menu_first(
11172 &mut self,
11173 _: &ContextMenuFirst,
11174 _window: &mut Window,
11175 cx: &mut Context<Self>,
11176 ) {
11177 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11178 context_menu.select_first(self.completion_provider.as_deref(), cx);
11179 }
11180 }
11181
11182 pub fn context_menu_prev(
11183 &mut self,
11184 _: &ContextMenuPrevious,
11185 _window: &mut Window,
11186 cx: &mut Context<Self>,
11187 ) {
11188 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11189 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11190 }
11191 }
11192
11193 pub fn context_menu_next(
11194 &mut self,
11195 _: &ContextMenuNext,
11196 _window: &mut Window,
11197 cx: &mut Context<Self>,
11198 ) {
11199 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11200 context_menu.select_next(self.completion_provider.as_deref(), cx);
11201 }
11202 }
11203
11204 pub fn context_menu_last(
11205 &mut self,
11206 _: &ContextMenuLast,
11207 _window: &mut Window,
11208 cx: &mut Context<Self>,
11209 ) {
11210 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11211 context_menu.select_last(self.completion_provider.as_deref(), cx);
11212 }
11213 }
11214
11215 pub fn move_to_previous_word_start(
11216 &mut self,
11217 _: &MoveToPreviousWordStart,
11218 window: &mut Window,
11219 cx: &mut Context<Self>,
11220 ) {
11221 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11222 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11223 s.move_cursors_with(|map, head, _| {
11224 (
11225 movement::previous_word_start(map, head),
11226 SelectionGoal::None,
11227 )
11228 });
11229 })
11230 }
11231
11232 pub fn move_to_previous_subword_start(
11233 &mut self,
11234 _: &MoveToPreviousSubwordStart,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11239 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11240 s.move_cursors_with(|map, head, _| {
11241 (
11242 movement::previous_subword_start(map, head),
11243 SelectionGoal::None,
11244 )
11245 });
11246 })
11247 }
11248
11249 pub fn select_to_previous_word_start(
11250 &mut self,
11251 _: &SelectToPreviousWordStart,
11252 window: &mut Window,
11253 cx: &mut Context<Self>,
11254 ) {
11255 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11256 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11257 s.move_heads_with(|map, head, _| {
11258 (
11259 movement::previous_word_start(map, head),
11260 SelectionGoal::None,
11261 )
11262 });
11263 })
11264 }
11265
11266 pub fn select_to_previous_subword_start(
11267 &mut self,
11268 _: &SelectToPreviousSubwordStart,
11269 window: &mut Window,
11270 cx: &mut Context<Self>,
11271 ) {
11272 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11273 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11274 s.move_heads_with(|map, head, _| {
11275 (
11276 movement::previous_subword_start(map, head),
11277 SelectionGoal::None,
11278 )
11279 });
11280 })
11281 }
11282
11283 pub fn delete_to_previous_word_start(
11284 &mut self,
11285 action: &DeleteToPreviousWordStart,
11286 window: &mut Window,
11287 cx: &mut Context<Self>,
11288 ) {
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11290 self.transact(window, cx, |this, window, cx| {
11291 this.select_autoclose_pair(window, cx);
11292 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11293 s.move_with(|map, selection| {
11294 if selection.is_empty() {
11295 let cursor = if action.ignore_newlines {
11296 movement::previous_word_start(map, selection.head())
11297 } else {
11298 movement::previous_word_start_or_newline(map, selection.head())
11299 };
11300 selection.set_head(cursor, SelectionGoal::None);
11301 }
11302 });
11303 });
11304 this.insert("", window, cx);
11305 });
11306 }
11307
11308 pub fn delete_to_previous_subword_start(
11309 &mut self,
11310 _: &DeleteToPreviousSubwordStart,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11315 self.transact(window, cx, |this, window, cx| {
11316 this.select_autoclose_pair(window, cx);
11317 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11318 s.move_with(|map, selection| {
11319 if selection.is_empty() {
11320 let cursor = movement::previous_subword_start(map, selection.head());
11321 selection.set_head(cursor, SelectionGoal::None);
11322 }
11323 });
11324 });
11325 this.insert("", window, cx);
11326 });
11327 }
11328
11329 pub fn move_to_next_word_end(
11330 &mut self,
11331 _: &MoveToNextWordEnd,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11336 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11337 s.move_cursors_with(|map, head, _| {
11338 (movement::next_word_end(map, head), SelectionGoal::None)
11339 });
11340 })
11341 }
11342
11343 pub fn move_to_next_subword_end(
11344 &mut self,
11345 _: &MoveToNextSubwordEnd,
11346 window: &mut Window,
11347 cx: &mut Context<Self>,
11348 ) {
11349 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11350 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11351 s.move_cursors_with(|map, head, _| {
11352 (movement::next_subword_end(map, head), SelectionGoal::None)
11353 });
11354 })
11355 }
11356
11357 pub fn select_to_next_word_end(
11358 &mut self,
11359 _: &SelectToNextWordEnd,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11364 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11365 s.move_heads_with(|map, head, _| {
11366 (movement::next_word_end(map, head), SelectionGoal::None)
11367 });
11368 })
11369 }
11370
11371 pub fn select_to_next_subword_end(
11372 &mut self,
11373 _: &SelectToNextSubwordEnd,
11374 window: &mut Window,
11375 cx: &mut Context<Self>,
11376 ) {
11377 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11378 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11379 s.move_heads_with(|map, head, _| {
11380 (movement::next_subword_end(map, head), SelectionGoal::None)
11381 });
11382 })
11383 }
11384
11385 pub fn delete_to_next_word_end(
11386 &mut self,
11387 action: &DeleteToNextWordEnd,
11388 window: &mut Window,
11389 cx: &mut Context<Self>,
11390 ) {
11391 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11392 self.transact(window, cx, |this, window, cx| {
11393 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11394 s.move_with(|map, selection| {
11395 if selection.is_empty() {
11396 let cursor = if action.ignore_newlines {
11397 movement::next_word_end(map, selection.head())
11398 } else {
11399 movement::next_word_end_or_newline(map, selection.head())
11400 };
11401 selection.set_head(cursor, SelectionGoal::None);
11402 }
11403 });
11404 });
11405 this.insert("", window, cx);
11406 });
11407 }
11408
11409 pub fn delete_to_next_subword_end(
11410 &mut self,
11411 _: &DeleteToNextSubwordEnd,
11412 window: &mut Window,
11413 cx: &mut Context<Self>,
11414 ) {
11415 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11416 self.transact(window, cx, |this, window, cx| {
11417 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11418 s.move_with(|map, selection| {
11419 if selection.is_empty() {
11420 let cursor = movement::next_subword_end(map, selection.head());
11421 selection.set_head(cursor, SelectionGoal::None);
11422 }
11423 });
11424 });
11425 this.insert("", window, cx);
11426 });
11427 }
11428
11429 pub fn move_to_beginning_of_line(
11430 &mut self,
11431 action: &MoveToBeginningOfLine,
11432 window: &mut Window,
11433 cx: &mut Context<Self>,
11434 ) {
11435 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11436 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11437 s.move_cursors_with(|map, head, _| {
11438 (
11439 movement::indented_line_beginning(
11440 map,
11441 head,
11442 action.stop_at_soft_wraps,
11443 action.stop_at_indent,
11444 ),
11445 SelectionGoal::None,
11446 )
11447 });
11448 })
11449 }
11450
11451 pub fn select_to_beginning_of_line(
11452 &mut self,
11453 action: &SelectToBeginningOfLine,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11458 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11459 s.move_heads_with(|map, head, _| {
11460 (
11461 movement::indented_line_beginning(
11462 map,
11463 head,
11464 action.stop_at_soft_wraps,
11465 action.stop_at_indent,
11466 ),
11467 SelectionGoal::None,
11468 )
11469 });
11470 });
11471 }
11472
11473 pub fn delete_to_beginning_of_line(
11474 &mut self,
11475 action: &DeleteToBeginningOfLine,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11480 self.transact(window, cx, |this, window, cx| {
11481 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11482 s.move_with(|_, selection| {
11483 selection.reversed = true;
11484 });
11485 });
11486
11487 this.select_to_beginning_of_line(
11488 &SelectToBeginningOfLine {
11489 stop_at_soft_wraps: false,
11490 stop_at_indent: action.stop_at_indent,
11491 },
11492 window,
11493 cx,
11494 );
11495 this.backspace(&Backspace, window, cx);
11496 });
11497 }
11498
11499 pub fn move_to_end_of_line(
11500 &mut self,
11501 action: &MoveToEndOfLine,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11506 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11507 s.move_cursors_with(|map, head, _| {
11508 (
11509 movement::line_end(map, head, action.stop_at_soft_wraps),
11510 SelectionGoal::None,
11511 )
11512 });
11513 })
11514 }
11515
11516 pub fn select_to_end_of_line(
11517 &mut self,
11518 action: &SelectToEndOfLine,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11523 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11524 s.move_heads_with(|map, head, _| {
11525 (
11526 movement::line_end(map, head, action.stop_at_soft_wraps),
11527 SelectionGoal::None,
11528 )
11529 });
11530 })
11531 }
11532
11533 pub fn delete_to_end_of_line(
11534 &mut self,
11535 _: &DeleteToEndOfLine,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11540 self.transact(window, cx, |this, window, cx| {
11541 this.select_to_end_of_line(
11542 &SelectToEndOfLine {
11543 stop_at_soft_wraps: false,
11544 },
11545 window,
11546 cx,
11547 );
11548 this.delete(&Delete, window, cx);
11549 });
11550 }
11551
11552 pub fn cut_to_end_of_line(
11553 &mut self,
11554 _: &CutToEndOfLine,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11559 self.transact(window, cx, |this, window, cx| {
11560 this.select_to_end_of_line(
11561 &SelectToEndOfLine {
11562 stop_at_soft_wraps: false,
11563 },
11564 window,
11565 cx,
11566 );
11567 this.cut(&Cut, window, cx);
11568 });
11569 }
11570
11571 pub fn move_to_start_of_paragraph(
11572 &mut self,
11573 _: &MoveToStartOfParagraph,
11574 window: &mut Window,
11575 cx: &mut Context<Self>,
11576 ) {
11577 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11578 cx.propagate();
11579 return;
11580 }
11581 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11582 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11583 s.move_with(|map, selection| {
11584 selection.collapse_to(
11585 movement::start_of_paragraph(map, selection.head(), 1),
11586 SelectionGoal::None,
11587 )
11588 });
11589 })
11590 }
11591
11592 pub fn move_to_end_of_paragraph(
11593 &mut self,
11594 _: &MoveToEndOfParagraph,
11595 window: &mut Window,
11596 cx: &mut Context<Self>,
11597 ) {
11598 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11599 cx.propagate();
11600 return;
11601 }
11602 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11603 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11604 s.move_with(|map, selection| {
11605 selection.collapse_to(
11606 movement::end_of_paragraph(map, selection.head(), 1),
11607 SelectionGoal::None,
11608 )
11609 });
11610 })
11611 }
11612
11613 pub fn select_to_start_of_paragraph(
11614 &mut self,
11615 _: &SelectToStartOfParagraph,
11616 window: &mut Window,
11617 cx: &mut Context<Self>,
11618 ) {
11619 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11620 cx.propagate();
11621 return;
11622 }
11623 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11624 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11625 s.move_heads_with(|map, head, _| {
11626 (
11627 movement::start_of_paragraph(map, head, 1),
11628 SelectionGoal::None,
11629 )
11630 });
11631 })
11632 }
11633
11634 pub fn select_to_end_of_paragraph(
11635 &mut self,
11636 _: &SelectToEndOfParagraph,
11637 window: &mut Window,
11638 cx: &mut Context<Self>,
11639 ) {
11640 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11641 cx.propagate();
11642 return;
11643 }
11644 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11645 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11646 s.move_heads_with(|map, head, _| {
11647 (
11648 movement::end_of_paragraph(map, head, 1),
11649 SelectionGoal::None,
11650 )
11651 });
11652 })
11653 }
11654
11655 pub fn move_to_start_of_excerpt(
11656 &mut self,
11657 _: &MoveToStartOfExcerpt,
11658 window: &mut Window,
11659 cx: &mut Context<Self>,
11660 ) {
11661 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11662 cx.propagate();
11663 return;
11664 }
11665 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11666 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11667 s.move_with(|map, selection| {
11668 selection.collapse_to(
11669 movement::start_of_excerpt(
11670 map,
11671 selection.head(),
11672 workspace::searchable::Direction::Prev,
11673 ),
11674 SelectionGoal::None,
11675 )
11676 });
11677 })
11678 }
11679
11680 pub fn move_to_start_of_next_excerpt(
11681 &mut self,
11682 _: &MoveToStartOfNextExcerpt,
11683 window: &mut Window,
11684 cx: &mut Context<Self>,
11685 ) {
11686 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11687 cx.propagate();
11688 return;
11689 }
11690
11691 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11692 s.move_with(|map, selection| {
11693 selection.collapse_to(
11694 movement::start_of_excerpt(
11695 map,
11696 selection.head(),
11697 workspace::searchable::Direction::Next,
11698 ),
11699 SelectionGoal::None,
11700 )
11701 });
11702 })
11703 }
11704
11705 pub fn move_to_end_of_excerpt(
11706 &mut self,
11707 _: &MoveToEndOfExcerpt,
11708 window: &mut Window,
11709 cx: &mut Context<Self>,
11710 ) {
11711 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11712 cx.propagate();
11713 return;
11714 }
11715 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11716 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11717 s.move_with(|map, selection| {
11718 selection.collapse_to(
11719 movement::end_of_excerpt(
11720 map,
11721 selection.head(),
11722 workspace::searchable::Direction::Next,
11723 ),
11724 SelectionGoal::None,
11725 )
11726 });
11727 })
11728 }
11729
11730 pub fn move_to_end_of_previous_excerpt(
11731 &mut self,
11732 _: &MoveToEndOfPreviousExcerpt,
11733 window: &mut Window,
11734 cx: &mut Context<Self>,
11735 ) {
11736 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11737 cx.propagate();
11738 return;
11739 }
11740 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11741 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11742 s.move_with(|map, selection| {
11743 selection.collapse_to(
11744 movement::end_of_excerpt(
11745 map,
11746 selection.head(),
11747 workspace::searchable::Direction::Prev,
11748 ),
11749 SelectionGoal::None,
11750 )
11751 });
11752 })
11753 }
11754
11755 pub fn select_to_start_of_excerpt(
11756 &mut self,
11757 _: &SelectToStartOfExcerpt,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11762 cx.propagate();
11763 return;
11764 }
11765 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11766 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11767 s.move_heads_with(|map, head, _| {
11768 (
11769 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11770 SelectionGoal::None,
11771 )
11772 });
11773 })
11774 }
11775
11776 pub fn select_to_start_of_next_excerpt(
11777 &mut self,
11778 _: &SelectToStartOfNextExcerpt,
11779 window: &mut Window,
11780 cx: &mut Context<Self>,
11781 ) {
11782 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11783 cx.propagate();
11784 return;
11785 }
11786 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11787 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11788 s.move_heads_with(|map, head, _| {
11789 (
11790 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11791 SelectionGoal::None,
11792 )
11793 });
11794 })
11795 }
11796
11797 pub fn select_to_end_of_excerpt(
11798 &mut self,
11799 _: &SelectToEndOfExcerpt,
11800 window: &mut Window,
11801 cx: &mut Context<Self>,
11802 ) {
11803 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11804 cx.propagate();
11805 return;
11806 }
11807 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11808 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11809 s.move_heads_with(|map, head, _| {
11810 (
11811 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11812 SelectionGoal::None,
11813 )
11814 });
11815 })
11816 }
11817
11818 pub fn select_to_end_of_previous_excerpt(
11819 &mut self,
11820 _: &SelectToEndOfPreviousExcerpt,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11825 cx.propagate();
11826 return;
11827 }
11828 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11829 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11830 s.move_heads_with(|map, head, _| {
11831 (
11832 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11833 SelectionGoal::None,
11834 )
11835 });
11836 })
11837 }
11838
11839 pub fn move_to_beginning(
11840 &mut self,
11841 _: &MoveToBeginning,
11842 window: &mut Window,
11843 cx: &mut Context<Self>,
11844 ) {
11845 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11846 cx.propagate();
11847 return;
11848 }
11849 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11850 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11851 s.select_ranges(vec![0..0]);
11852 });
11853 }
11854
11855 pub fn select_to_beginning(
11856 &mut self,
11857 _: &SelectToBeginning,
11858 window: &mut Window,
11859 cx: &mut Context<Self>,
11860 ) {
11861 let mut selection = self.selections.last::<Point>(cx);
11862 selection.set_head(Point::zero(), SelectionGoal::None);
11863 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.select(vec![selection]);
11866 });
11867 }
11868
11869 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11870 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11871 cx.propagate();
11872 return;
11873 }
11874 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11875 let cursor = self.buffer.read(cx).read(cx).len();
11876 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11877 s.select_ranges(vec![cursor..cursor])
11878 });
11879 }
11880
11881 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11882 self.nav_history = nav_history;
11883 }
11884
11885 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11886 self.nav_history.as_ref()
11887 }
11888
11889 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11890 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11891 }
11892
11893 fn push_to_nav_history(
11894 &mut self,
11895 cursor_anchor: Anchor,
11896 new_position: Option<Point>,
11897 is_deactivate: bool,
11898 cx: &mut Context<Self>,
11899 ) {
11900 if let Some(nav_history) = self.nav_history.as_mut() {
11901 let buffer = self.buffer.read(cx).read(cx);
11902 let cursor_position = cursor_anchor.to_point(&buffer);
11903 let scroll_state = self.scroll_manager.anchor();
11904 let scroll_top_row = scroll_state.top_row(&buffer);
11905 drop(buffer);
11906
11907 if let Some(new_position) = new_position {
11908 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11909 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11910 return;
11911 }
11912 }
11913
11914 nav_history.push(
11915 Some(NavigationData {
11916 cursor_anchor,
11917 cursor_position,
11918 scroll_anchor: scroll_state,
11919 scroll_top_row,
11920 }),
11921 cx,
11922 );
11923 cx.emit(EditorEvent::PushedToNavHistory {
11924 anchor: cursor_anchor,
11925 is_deactivate,
11926 })
11927 }
11928 }
11929
11930 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11931 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11932 let buffer = self.buffer.read(cx).snapshot(cx);
11933 let mut selection = self.selections.first::<usize>(cx);
11934 selection.set_head(buffer.len(), SelectionGoal::None);
11935 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11936 s.select(vec![selection]);
11937 });
11938 }
11939
11940 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11941 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11942 let end = self.buffer.read(cx).read(cx).len();
11943 self.change_selections(None, window, cx, |s| {
11944 s.select_ranges(vec![0..end]);
11945 });
11946 }
11947
11948 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11949 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11950 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11951 let mut selections = self.selections.all::<Point>(cx);
11952 let max_point = display_map.buffer_snapshot.max_point();
11953 for selection in &mut selections {
11954 let rows = selection.spanned_rows(true, &display_map);
11955 selection.start = Point::new(rows.start.0, 0);
11956 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11957 selection.reversed = false;
11958 }
11959 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11960 s.select(selections);
11961 });
11962 }
11963
11964 pub fn split_selection_into_lines(
11965 &mut self,
11966 _: &SplitSelectionIntoLines,
11967 window: &mut Window,
11968 cx: &mut Context<Self>,
11969 ) {
11970 let selections = self
11971 .selections
11972 .all::<Point>(cx)
11973 .into_iter()
11974 .map(|selection| selection.start..selection.end)
11975 .collect::<Vec<_>>();
11976 self.unfold_ranges(&selections, true, true, cx);
11977
11978 let mut new_selection_ranges = Vec::new();
11979 {
11980 let buffer = self.buffer.read(cx).read(cx);
11981 for selection in selections {
11982 for row in selection.start.row..selection.end.row {
11983 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11984 new_selection_ranges.push(cursor..cursor);
11985 }
11986
11987 let is_multiline_selection = selection.start.row != selection.end.row;
11988 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11989 // so this action feels more ergonomic when paired with other selection operations
11990 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11991 if !should_skip_last {
11992 new_selection_ranges.push(selection.end..selection.end);
11993 }
11994 }
11995 }
11996 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11997 s.select_ranges(new_selection_ranges);
11998 });
11999 }
12000
12001 pub fn add_selection_above(
12002 &mut self,
12003 _: &AddSelectionAbove,
12004 window: &mut Window,
12005 cx: &mut Context<Self>,
12006 ) {
12007 self.add_selection(true, window, cx);
12008 }
12009
12010 pub fn add_selection_below(
12011 &mut self,
12012 _: &AddSelectionBelow,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 ) {
12016 self.add_selection(false, window, cx);
12017 }
12018
12019 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12020 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12021
12022 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12023 let mut selections = self.selections.all::<Point>(cx);
12024 let text_layout_details = self.text_layout_details(window);
12025 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12026 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12027 let range = oldest_selection.display_range(&display_map).sorted();
12028
12029 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12030 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12031 let positions = start_x.min(end_x)..start_x.max(end_x);
12032
12033 selections.clear();
12034 let mut stack = Vec::new();
12035 for row in range.start.row().0..=range.end.row().0 {
12036 if let Some(selection) = self.selections.build_columnar_selection(
12037 &display_map,
12038 DisplayRow(row),
12039 &positions,
12040 oldest_selection.reversed,
12041 &text_layout_details,
12042 ) {
12043 stack.push(selection.id);
12044 selections.push(selection);
12045 }
12046 }
12047
12048 if above {
12049 stack.reverse();
12050 }
12051
12052 AddSelectionsState { above, stack }
12053 });
12054
12055 let last_added_selection = *state.stack.last().unwrap();
12056 let mut new_selections = Vec::new();
12057 if above == state.above {
12058 let end_row = if above {
12059 DisplayRow(0)
12060 } else {
12061 display_map.max_point().row()
12062 };
12063
12064 'outer: for selection in selections {
12065 if selection.id == last_added_selection {
12066 let range = selection.display_range(&display_map).sorted();
12067 debug_assert_eq!(range.start.row(), range.end.row());
12068 let mut row = range.start.row();
12069 let positions =
12070 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12071 px(start)..px(end)
12072 } else {
12073 let start_x =
12074 display_map.x_for_display_point(range.start, &text_layout_details);
12075 let end_x =
12076 display_map.x_for_display_point(range.end, &text_layout_details);
12077 start_x.min(end_x)..start_x.max(end_x)
12078 };
12079
12080 while row != end_row {
12081 if above {
12082 row.0 -= 1;
12083 } else {
12084 row.0 += 1;
12085 }
12086
12087 if let Some(new_selection) = self.selections.build_columnar_selection(
12088 &display_map,
12089 row,
12090 &positions,
12091 selection.reversed,
12092 &text_layout_details,
12093 ) {
12094 state.stack.push(new_selection.id);
12095 if above {
12096 new_selections.push(new_selection);
12097 new_selections.push(selection);
12098 } else {
12099 new_selections.push(selection);
12100 new_selections.push(new_selection);
12101 }
12102
12103 continue 'outer;
12104 }
12105 }
12106 }
12107
12108 new_selections.push(selection);
12109 }
12110 } else {
12111 new_selections = selections;
12112 new_selections.retain(|s| s.id != last_added_selection);
12113 state.stack.pop();
12114 }
12115
12116 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12117 s.select(new_selections);
12118 });
12119 if state.stack.len() > 1 {
12120 self.add_selections_state = Some(state);
12121 }
12122 }
12123
12124 pub fn select_next_match_internal(
12125 &mut self,
12126 display_map: &DisplaySnapshot,
12127 replace_newest: bool,
12128 autoscroll: Option<Autoscroll>,
12129 window: &mut Window,
12130 cx: &mut Context<Self>,
12131 ) -> Result<()> {
12132 fn select_next_match_ranges(
12133 this: &mut Editor,
12134 range: Range<usize>,
12135 reversed: bool,
12136 replace_newest: bool,
12137 auto_scroll: Option<Autoscroll>,
12138 window: &mut Window,
12139 cx: &mut Context<Editor>,
12140 ) {
12141 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12142 this.change_selections(auto_scroll, window, cx, |s| {
12143 if replace_newest {
12144 s.delete(s.newest_anchor().id);
12145 }
12146 if reversed {
12147 s.insert_range(range.end..range.start);
12148 } else {
12149 s.insert_range(range);
12150 }
12151 });
12152 }
12153
12154 let buffer = &display_map.buffer_snapshot;
12155 let mut selections = self.selections.all::<usize>(cx);
12156 if let Some(mut select_next_state) = self.select_next_state.take() {
12157 let query = &select_next_state.query;
12158 if !select_next_state.done {
12159 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12160 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12161 let mut next_selected_range = None;
12162
12163 let bytes_after_last_selection =
12164 buffer.bytes_in_range(last_selection.end..buffer.len());
12165 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12166 let query_matches = query
12167 .stream_find_iter(bytes_after_last_selection)
12168 .map(|result| (last_selection.end, result))
12169 .chain(
12170 query
12171 .stream_find_iter(bytes_before_first_selection)
12172 .map(|result| (0, result)),
12173 );
12174
12175 for (start_offset, query_match) in query_matches {
12176 let query_match = query_match.unwrap(); // can only fail due to I/O
12177 let offset_range =
12178 start_offset + query_match.start()..start_offset + query_match.end();
12179 let display_range = offset_range.start.to_display_point(display_map)
12180 ..offset_range.end.to_display_point(display_map);
12181
12182 if !select_next_state.wordwise
12183 || (!movement::is_inside_word(display_map, display_range.start)
12184 && !movement::is_inside_word(display_map, display_range.end))
12185 {
12186 // TODO: This is n^2, because we might check all the selections
12187 if !selections
12188 .iter()
12189 .any(|selection| selection.range().overlaps(&offset_range))
12190 {
12191 next_selected_range = Some(offset_range);
12192 break;
12193 }
12194 }
12195 }
12196
12197 if let Some(next_selected_range) = next_selected_range {
12198 select_next_match_ranges(
12199 self,
12200 next_selected_range,
12201 last_selection.reversed,
12202 replace_newest,
12203 autoscroll,
12204 window,
12205 cx,
12206 );
12207 } else {
12208 select_next_state.done = true;
12209 }
12210 }
12211
12212 self.select_next_state = Some(select_next_state);
12213 } else {
12214 let mut only_carets = true;
12215 let mut same_text_selected = true;
12216 let mut selected_text = None;
12217
12218 let mut selections_iter = selections.iter().peekable();
12219 while let Some(selection) = selections_iter.next() {
12220 if selection.start != selection.end {
12221 only_carets = false;
12222 }
12223
12224 if same_text_selected {
12225 if selected_text.is_none() {
12226 selected_text =
12227 Some(buffer.text_for_range(selection.range()).collect::<String>());
12228 }
12229
12230 if let Some(next_selection) = selections_iter.peek() {
12231 if next_selection.range().len() == selection.range().len() {
12232 let next_selected_text = buffer
12233 .text_for_range(next_selection.range())
12234 .collect::<String>();
12235 if Some(next_selected_text) != selected_text {
12236 same_text_selected = false;
12237 selected_text = None;
12238 }
12239 } else {
12240 same_text_selected = false;
12241 selected_text = None;
12242 }
12243 }
12244 }
12245 }
12246
12247 if only_carets {
12248 for selection in &mut selections {
12249 let word_range = movement::surrounding_word(
12250 display_map,
12251 selection.start.to_display_point(display_map),
12252 );
12253 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12254 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12255 selection.goal = SelectionGoal::None;
12256 selection.reversed = false;
12257 select_next_match_ranges(
12258 self,
12259 selection.start..selection.end,
12260 selection.reversed,
12261 replace_newest,
12262 autoscroll,
12263 window,
12264 cx,
12265 );
12266 }
12267
12268 if selections.len() == 1 {
12269 let selection = selections
12270 .last()
12271 .expect("ensured that there's only one selection");
12272 let query = buffer
12273 .text_for_range(selection.start..selection.end)
12274 .collect::<String>();
12275 let is_empty = query.is_empty();
12276 let select_state = SelectNextState {
12277 query: AhoCorasick::new(&[query])?,
12278 wordwise: true,
12279 done: is_empty,
12280 };
12281 self.select_next_state = Some(select_state);
12282 } else {
12283 self.select_next_state = None;
12284 }
12285 } else if let Some(selected_text) = selected_text {
12286 self.select_next_state = Some(SelectNextState {
12287 query: AhoCorasick::new(&[selected_text])?,
12288 wordwise: false,
12289 done: false,
12290 });
12291 self.select_next_match_internal(
12292 display_map,
12293 replace_newest,
12294 autoscroll,
12295 window,
12296 cx,
12297 )?;
12298 }
12299 }
12300 Ok(())
12301 }
12302
12303 pub fn select_all_matches(
12304 &mut self,
12305 _action: &SelectAllMatches,
12306 window: &mut Window,
12307 cx: &mut Context<Self>,
12308 ) -> Result<()> {
12309 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12310
12311 self.push_to_selection_history();
12312 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12313
12314 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12315 let Some(select_next_state) = self.select_next_state.as_mut() else {
12316 return Ok(());
12317 };
12318 if select_next_state.done {
12319 return Ok(());
12320 }
12321
12322 let mut new_selections = Vec::new();
12323
12324 let reversed = self.selections.oldest::<usize>(cx).reversed;
12325 let buffer = &display_map.buffer_snapshot;
12326 let query_matches = select_next_state
12327 .query
12328 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12329
12330 for query_match in query_matches.into_iter() {
12331 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12332 let offset_range = if reversed {
12333 query_match.end()..query_match.start()
12334 } else {
12335 query_match.start()..query_match.end()
12336 };
12337 let display_range = offset_range.start.to_display_point(&display_map)
12338 ..offset_range.end.to_display_point(&display_map);
12339
12340 if !select_next_state.wordwise
12341 || (!movement::is_inside_word(&display_map, display_range.start)
12342 && !movement::is_inside_word(&display_map, display_range.end))
12343 {
12344 new_selections.push(offset_range.start..offset_range.end);
12345 }
12346 }
12347
12348 select_next_state.done = true;
12349 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12350 self.change_selections(None, window, cx, |selections| {
12351 selections.select_ranges(new_selections)
12352 });
12353
12354 Ok(())
12355 }
12356
12357 pub fn select_next(
12358 &mut self,
12359 action: &SelectNext,
12360 window: &mut Window,
12361 cx: &mut Context<Self>,
12362 ) -> Result<()> {
12363 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12364 self.push_to_selection_history();
12365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12366 self.select_next_match_internal(
12367 &display_map,
12368 action.replace_newest,
12369 Some(Autoscroll::newest()),
12370 window,
12371 cx,
12372 )?;
12373 Ok(())
12374 }
12375
12376 pub fn select_previous(
12377 &mut self,
12378 action: &SelectPrevious,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 ) -> Result<()> {
12382 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12383 self.push_to_selection_history();
12384 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12385 let buffer = &display_map.buffer_snapshot;
12386 let mut selections = self.selections.all::<usize>(cx);
12387 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12388 let query = &select_prev_state.query;
12389 if !select_prev_state.done {
12390 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12391 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12392 let mut next_selected_range = None;
12393 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12394 let bytes_before_last_selection =
12395 buffer.reversed_bytes_in_range(0..last_selection.start);
12396 let bytes_after_first_selection =
12397 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12398 let query_matches = query
12399 .stream_find_iter(bytes_before_last_selection)
12400 .map(|result| (last_selection.start, result))
12401 .chain(
12402 query
12403 .stream_find_iter(bytes_after_first_selection)
12404 .map(|result| (buffer.len(), result)),
12405 );
12406 for (end_offset, query_match) in query_matches {
12407 let query_match = query_match.unwrap(); // can only fail due to I/O
12408 let offset_range =
12409 end_offset - query_match.end()..end_offset - query_match.start();
12410 let display_range = offset_range.start.to_display_point(&display_map)
12411 ..offset_range.end.to_display_point(&display_map);
12412
12413 if !select_prev_state.wordwise
12414 || (!movement::is_inside_word(&display_map, display_range.start)
12415 && !movement::is_inside_word(&display_map, display_range.end))
12416 {
12417 next_selected_range = Some(offset_range);
12418 break;
12419 }
12420 }
12421
12422 if let Some(next_selected_range) = next_selected_range {
12423 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12424 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12425 if action.replace_newest {
12426 s.delete(s.newest_anchor().id);
12427 }
12428 if last_selection.reversed {
12429 s.insert_range(next_selected_range.end..next_selected_range.start);
12430 } else {
12431 s.insert_range(next_selected_range);
12432 }
12433 });
12434 } else {
12435 select_prev_state.done = true;
12436 }
12437 }
12438
12439 self.select_prev_state = Some(select_prev_state);
12440 } else {
12441 let mut only_carets = true;
12442 let mut same_text_selected = true;
12443 let mut selected_text = None;
12444
12445 let mut selections_iter = selections.iter().peekable();
12446 while let Some(selection) = selections_iter.next() {
12447 if selection.start != selection.end {
12448 only_carets = false;
12449 }
12450
12451 if same_text_selected {
12452 if selected_text.is_none() {
12453 selected_text =
12454 Some(buffer.text_for_range(selection.range()).collect::<String>());
12455 }
12456
12457 if let Some(next_selection) = selections_iter.peek() {
12458 if next_selection.range().len() == selection.range().len() {
12459 let next_selected_text = buffer
12460 .text_for_range(next_selection.range())
12461 .collect::<String>();
12462 if Some(next_selected_text) != selected_text {
12463 same_text_selected = false;
12464 selected_text = None;
12465 }
12466 } else {
12467 same_text_selected = false;
12468 selected_text = None;
12469 }
12470 }
12471 }
12472 }
12473
12474 if only_carets {
12475 for selection in &mut selections {
12476 let word_range = movement::surrounding_word(
12477 &display_map,
12478 selection.start.to_display_point(&display_map),
12479 );
12480 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12481 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12482 selection.goal = SelectionGoal::None;
12483 selection.reversed = false;
12484 }
12485 if selections.len() == 1 {
12486 let selection = selections
12487 .last()
12488 .expect("ensured that there's only one selection");
12489 let query = buffer
12490 .text_for_range(selection.start..selection.end)
12491 .collect::<String>();
12492 let is_empty = query.is_empty();
12493 let select_state = SelectNextState {
12494 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12495 wordwise: true,
12496 done: is_empty,
12497 };
12498 self.select_prev_state = Some(select_state);
12499 } else {
12500 self.select_prev_state = None;
12501 }
12502
12503 self.unfold_ranges(
12504 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12505 false,
12506 true,
12507 cx,
12508 );
12509 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12510 s.select(selections);
12511 });
12512 } else if let Some(selected_text) = selected_text {
12513 self.select_prev_state = Some(SelectNextState {
12514 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12515 wordwise: false,
12516 done: false,
12517 });
12518 self.select_previous(action, window, cx)?;
12519 }
12520 }
12521 Ok(())
12522 }
12523
12524 pub fn find_next_match(
12525 &mut self,
12526 _: &FindNextMatch,
12527 window: &mut Window,
12528 cx: &mut Context<Self>,
12529 ) -> Result<()> {
12530 let selections = self.selections.disjoint_anchors();
12531 match selections.first() {
12532 Some(first) if selections.len() >= 2 => {
12533 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12534 s.select_ranges([first.range()]);
12535 });
12536 }
12537 _ => self.select_next(
12538 &SelectNext {
12539 replace_newest: true,
12540 },
12541 window,
12542 cx,
12543 )?,
12544 }
12545 Ok(())
12546 }
12547
12548 pub fn find_previous_match(
12549 &mut self,
12550 _: &FindPreviousMatch,
12551 window: &mut Window,
12552 cx: &mut Context<Self>,
12553 ) -> Result<()> {
12554 let selections = self.selections.disjoint_anchors();
12555 match selections.last() {
12556 Some(last) if selections.len() >= 2 => {
12557 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12558 s.select_ranges([last.range()]);
12559 });
12560 }
12561 _ => self.select_previous(
12562 &SelectPrevious {
12563 replace_newest: true,
12564 },
12565 window,
12566 cx,
12567 )?,
12568 }
12569 Ok(())
12570 }
12571
12572 pub fn toggle_comments(
12573 &mut self,
12574 action: &ToggleComments,
12575 window: &mut Window,
12576 cx: &mut Context<Self>,
12577 ) {
12578 if self.read_only(cx) {
12579 return;
12580 }
12581 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12582 let text_layout_details = &self.text_layout_details(window);
12583 self.transact(window, cx, |this, window, cx| {
12584 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12585 let mut edits = Vec::new();
12586 let mut selection_edit_ranges = Vec::new();
12587 let mut last_toggled_row = None;
12588 let snapshot = this.buffer.read(cx).read(cx);
12589 let empty_str: Arc<str> = Arc::default();
12590 let mut suffixes_inserted = Vec::new();
12591 let ignore_indent = action.ignore_indent;
12592
12593 fn comment_prefix_range(
12594 snapshot: &MultiBufferSnapshot,
12595 row: MultiBufferRow,
12596 comment_prefix: &str,
12597 comment_prefix_whitespace: &str,
12598 ignore_indent: bool,
12599 ) -> Range<Point> {
12600 let indent_size = if ignore_indent {
12601 0
12602 } else {
12603 snapshot.indent_size_for_line(row).len
12604 };
12605
12606 let start = Point::new(row.0, indent_size);
12607
12608 let mut line_bytes = snapshot
12609 .bytes_in_range(start..snapshot.max_point())
12610 .flatten()
12611 .copied();
12612
12613 // If this line currently begins with the line comment prefix, then record
12614 // the range containing the prefix.
12615 if line_bytes
12616 .by_ref()
12617 .take(comment_prefix.len())
12618 .eq(comment_prefix.bytes())
12619 {
12620 // Include any whitespace that matches the comment prefix.
12621 let matching_whitespace_len = line_bytes
12622 .zip(comment_prefix_whitespace.bytes())
12623 .take_while(|(a, b)| a == b)
12624 .count() as u32;
12625 let end = Point::new(
12626 start.row,
12627 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12628 );
12629 start..end
12630 } else {
12631 start..start
12632 }
12633 }
12634
12635 fn comment_suffix_range(
12636 snapshot: &MultiBufferSnapshot,
12637 row: MultiBufferRow,
12638 comment_suffix: &str,
12639 comment_suffix_has_leading_space: bool,
12640 ) -> Range<Point> {
12641 let end = Point::new(row.0, snapshot.line_len(row));
12642 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12643
12644 let mut line_end_bytes = snapshot
12645 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12646 .flatten()
12647 .copied();
12648
12649 let leading_space_len = if suffix_start_column > 0
12650 && line_end_bytes.next() == Some(b' ')
12651 && comment_suffix_has_leading_space
12652 {
12653 1
12654 } else {
12655 0
12656 };
12657
12658 // If this line currently begins with the line comment prefix, then record
12659 // the range containing the prefix.
12660 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12661 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12662 start..end
12663 } else {
12664 end..end
12665 }
12666 }
12667
12668 // TODO: Handle selections that cross excerpts
12669 for selection in &mut selections {
12670 let start_column = snapshot
12671 .indent_size_for_line(MultiBufferRow(selection.start.row))
12672 .len;
12673 let language = if let Some(language) =
12674 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12675 {
12676 language
12677 } else {
12678 continue;
12679 };
12680
12681 selection_edit_ranges.clear();
12682
12683 // If multiple selections contain a given row, avoid processing that
12684 // row more than once.
12685 let mut start_row = MultiBufferRow(selection.start.row);
12686 if last_toggled_row == Some(start_row) {
12687 start_row = start_row.next_row();
12688 }
12689 let end_row =
12690 if selection.end.row > selection.start.row && selection.end.column == 0 {
12691 MultiBufferRow(selection.end.row - 1)
12692 } else {
12693 MultiBufferRow(selection.end.row)
12694 };
12695 last_toggled_row = Some(end_row);
12696
12697 if start_row > end_row {
12698 continue;
12699 }
12700
12701 // If the language has line comments, toggle those.
12702 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12703
12704 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12705 if ignore_indent {
12706 full_comment_prefixes = full_comment_prefixes
12707 .into_iter()
12708 .map(|s| Arc::from(s.trim_end()))
12709 .collect();
12710 }
12711
12712 if !full_comment_prefixes.is_empty() {
12713 let first_prefix = full_comment_prefixes
12714 .first()
12715 .expect("prefixes is non-empty");
12716 let prefix_trimmed_lengths = full_comment_prefixes
12717 .iter()
12718 .map(|p| p.trim_end_matches(' ').len())
12719 .collect::<SmallVec<[usize; 4]>>();
12720
12721 let mut all_selection_lines_are_comments = true;
12722
12723 for row in start_row.0..=end_row.0 {
12724 let row = MultiBufferRow(row);
12725 if start_row < end_row && snapshot.is_line_blank(row) {
12726 continue;
12727 }
12728
12729 let prefix_range = full_comment_prefixes
12730 .iter()
12731 .zip(prefix_trimmed_lengths.iter().copied())
12732 .map(|(prefix, trimmed_prefix_len)| {
12733 comment_prefix_range(
12734 snapshot.deref(),
12735 row,
12736 &prefix[..trimmed_prefix_len],
12737 &prefix[trimmed_prefix_len..],
12738 ignore_indent,
12739 )
12740 })
12741 .max_by_key(|range| range.end.column - range.start.column)
12742 .expect("prefixes is non-empty");
12743
12744 if prefix_range.is_empty() {
12745 all_selection_lines_are_comments = false;
12746 }
12747
12748 selection_edit_ranges.push(prefix_range);
12749 }
12750
12751 if all_selection_lines_are_comments {
12752 edits.extend(
12753 selection_edit_ranges
12754 .iter()
12755 .cloned()
12756 .map(|range| (range, empty_str.clone())),
12757 );
12758 } else {
12759 let min_column = selection_edit_ranges
12760 .iter()
12761 .map(|range| range.start.column)
12762 .min()
12763 .unwrap_or(0);
12764 edits.extend(selection_edit_ranges.iter().map(|range| {
12765 let position = Point::new(range.start.row, min_column);
12766 (position..position, first_prefix.clone())
12767 }));
12768 }
12769 } else if let Some((full_comment_prefix, comment_suffix)) =
12770 language.block_comment_delimiters()
12771 {
12772 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12773 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12774 let prefix_range = comment_prefix_range(
12775 snapshot.deref(),
12776 start_row,
12777 comment_prefix,
12778 comment_prefix_whitespace,
12779 ignore_indent,
12780 );
12781 let suffix_range = comment_suffix_range(
12782 snapshot.deref(),
12783 end_row,
12784 comment_suffix.trim_start_matches(' '),
12785 comment_suffix.starts_with(' '),
12786 );
12787
12788 if prefix_range.is_empty() || suffix_range.is_empty() {
12789 edits.push((
12790 prefix_range.start..prefix_range.start,
12791 full_comment_prefix.clone(),
12792 ));
12793 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12794 suffixes_inserted.push((end_row, comment_suffix.len()));
12795 } else {
12796 edits.push((prefix_range, empty_str.clone()));
12797 edits.push((suffix_range, empty_str.clone()));
12798 }
12799 } else {
12800 continue;
12801 }
12802 }
12803
12804 drop(snapshot);
12805 this.buffer.update(cx, |buffer, cx| {
12806 buffer.edit(edits, None, cx);
12807 });
12808
12809 // Adjust selections so that they end before any comment suffixes that
12810 // were inserted.
12811 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12812 let mut selections = this.selections.all::<Point>(cx);
12813 let snapshot = this.buffer.read(cx).read(cx);
12814 for selection in &mut selections {
12815 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12816 match row.cmp(&MultiBufferRow(selection.end.row)) {
12817 Ordering::Less => {
12818 suffixes_inserted.next();
12819 continue;
12820 }
12821 Ordering::Greater => break,
12822 Ordering::Equal => {
12823 if selection.end.column == snapshot.line_len(row) {
12824 if selection.is_empty() {
12825 selection.start.column -= suffix_len as u32;
12826 }
12827 selection.end.column -= suffix_len as u32;
12828 }
12829 break;
12830 }
12831 }
12832 }
12833 }
12834
12835 drop(snapshot);
12836 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12837 s.select(selections)
12838 });
12839
12840 let selections = this.selections.all::<Point>(cx);
12841 let selections_on_single_row = selections.windows(2).all(|selections| {
12842 selections[0].start.row == selections[1].start.row
12843 && selections[0].end.row == selections[1].end.row
12844 && selections[0].start.row == selections[0].end.row
12845 });
12846 let selections_selecting = selections
12847 .iter()
12848 .any(|selection| selection.start != selection.end);
12849 let advance_downwards = action.advance_downwards
12850 && selections_on_single_row
12851 && !selections_selecting
12852 && !matches!(this.mode, EditorMode::SingleLine { .. });
12853
12854 if advance_downwards {
12855 let snapshot = this.buffer.read(cx).snapshot(cx);
12856
12857 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12858 s.move_cursors_with(|display_snapshot, display_point, _| {
12859 let mut point = display_point.to_point(display_snapshot);
12860 point.row += 1;
12861 point = snapshot.clip_point(point, Bias::Left);
12862 let display_point = point.to_display_point(display_snapshot);
12863 let goal = SelectionGoal::HorizontalPosition(
12864 display_snapshot
12865 .x_for_display_point(display_point, text_layout_details)
12866 .into(),
12867 );
12868 (display_point, goal)
12869 })
12870 });
12871 }
12872 });
12873 }
12874
12875 pub fn select_enclosing_symbol(
12876 &mut self,
12877 _: &SelectEnclosingSymbol,
12878 window: &mut Window,
12879 cx: &mut Context<Self>,
12880 ) {
12881 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12882
12883 let buffer = self.buffer.read(cx).snapshot(cx);
12884 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12885
12886 fn update_selection(
12887 selection: &Selection<usize>,
12888 buffer_snap: &MultiBufferSnapshot,
12889 ) -> Option<Selection<usize>> {
12890 let cursor = selection.head();
12891 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12892 for symbol in symbols.iter().rev() {
12893 let start = symbol.range.start.to_offset(buffer_snap);
12894 let end = symbol.range.end.to_offset(buffer_snap);
12895 let new_range = start..end;
12896 if start < selection.start || end > selection.end {
12897 return Some(Selection {
12898 id: selection.id,
12899 start: new_range.start,
12900 end: new_range.end,
12901 goal: SelectionGoal::None,
12902 reversed: selection.reversed,
12903 });
12904 }
12905 }
12906 None
12907 }
12908
12909 let mut selected_larger_symbol = false;
12910 let new_selections = old_selections
12911 .iter()
12912 .map(|selection| match update_selection(selection, &buffer) {
12913 Some(new_selection) => {
12914 if new_selection.range() != selection.range() {
12915 selected_larger_symbol = true;
12916 }
12917 new_selection
12918 }
12919 None => selection.clone(),
12920 })
12921 .collect::<Vec<_>>();
12922
12923 if selected_larger_symbol {
12924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12925 s.select(new_selections);
12926 });
12927 }
12928 }
12929
12930 pub fn select_larger_syntax_node(
12931 &mut self,
12932 _: &SelectLargerSyntaxNode,
12933 window: &mut Window,
12934 cx: &mut Context<Self>,
12935 ) {
12936 let Some(visible_row_count) = self.visible_row_count() else {
12937 return;
12938 };
12939 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12940 if old_selections.is_empty() {
12941 return;
12942 }
12943
12944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12945
12946 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12947 let buffer = self.buffer.read(cx).snapshot(cx);
12948
12949 let mut selected_larger_node = false;
12950 let mut new_selections = old_selections
12951 .iter()
12952 .map(|selection| {
12953 let old_range = selection.start..selection.end;
12954
12955 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12956 // manually select word at selection
12957 if ["string_content", "inline"].contains(&node.kind()) {
12958 let word_range = {
12959 let display_point = buffer
12960 .offset_to_point(old_range.start)
12961 .to_display_point(&display_map);
12962 let Range { start, end } =
12963 movement::surrounding_word(&display_map, display_point);
12964 start.to_point(&display_map).to_offset(&buffer)
12965 ..end.to_point(&display_map).to_offset(&buffer)
12966 };
12967 // ignore if word is already selected
12968 if !word_range.is_empty() && old_range != word_range {
12969 let last_word_range = {
12970 let display_point = buffer
12971 .offset_to_point(old_range.end)
12972 .to_display_point(&display_map);
12973 let Range { start, end } =
12974 movement::surrounding_word(&display_map, display_point);
12975 start.to_point(&display_map).to_offset(&buffer)
12976 ..end.to_point(&display_map).to_offset(&buffer)
12977 };
12978 // only select word if start and end point belongs to same word
12979 if word_range == last_word_range {
12980 selected_larger_node = true;
12981 return Selection {
12982 id: selection.id,
12983 start: word_range.start,
12984 end: word_range.end,
12985 goal: SelectionGoal::None,
12986 reversed: selection.reversed,
12987 };
12988 }
12989 }
12990 }
12991 }
12992
12993 let mut new_range = old_range.clone();
12994 let mut new_node = None;
12995 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12996 {
12997 new_node = Some(node);
12998 new_range = match containing_range {
12999 MultiOrSingleBufferOffsetRange::Single(_) => break,
13000 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13001 };
13002 if !display_map.intersects_fold(new_range.start)
13003 && !display_map.intersects_fold(new_range.end)
13004 {
13005 break;
13006 }
13007 }
13008
13009 if let Some(node) = new_node {
13010 // Log the ancestor, to support using this action as a way to explore TreeSitter
13011 // nodes. Parent and grandparent are also logged because this operation will not
13012 // visit nodes that have the same range as their parent.
13013 log::info!("Node: {node:?}");
13014 let parent = node.parent();
13015 log::info!("Parent: {parent:?}");
13016 let grandparent = parent.and_then(|x| x.parent());
13017 log::info!("Grandparent: {grandparent:?}");
13018 }
13019
13020 selected_larger_node |= new_range != old_range;
13021 Selection {
13022 id: selection.id,
13023 start: new_range.start,
13024 end: new_range.end,
13025 goal: SelectionGoal::None,
13026 reversed: selection.reversed,
13027 }
13028 })
13029 .collect::<Vec<_>>();
13030
13031 if !selected_larger_node {
13032 return; // don't put this call in the history
13033 }
13034
13035 // scroll based on transformation done to the last selection created by the user
13036 let (last_old, last_new) = old_selections
13037 .last()
13038 .zip(new_selections.last().cloned())
13039 .expect("old_selections isn't empty");
13040
13041 // revert selection
13042 let is_selection_reversed = {
13043 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13044 new_selections.last_mut().expect("checked above").reversed =
13045 should_newest_selection_be_reversed;
13046 should_newest_selection_be_reversed
13047 };
13048
13049 if selected_larger_node {
13050 self.select_syntax_node_history.disable_clearing = true;
13051 self.change_selections(None, window, cx, |s| {
13052 s.select(new_selections.clone());
13053 });
13054 self.select_syntax_node_history.disable_clearing = false;
13055 }
13056
13057 let start_row = last_new.start.to_display_point(&display_map).row().0;
13058 let end_row = last_new.end.to_display_point(&display_map).row().0;
13059 let selection_height = end_row - start_row + 1;
13060 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13061
13062 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13063 let scroll_behavior = if fits_on_the_screen {
13064 self.request_autoscroll(Autoscroll::fit(), cx);
13065 SelectSyntaxNodeScrollBehavior::FitSelection
13066 } else if is_selection_reversed {
13067 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13068 SelectSyntaxNodeScrollBehavior::CursorTop
13069 } else {
13070 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13071 SelectSyntaxNodeScrollBehavior::CursorBottom
13072 };
13073
13074 self.select_syntax_node_history.push((
13075 old_selections,
13076 scroll_behavior,
13077 is_selection_reversed,
13078 ));
13079 }
13080
13081 pub fn select_smaller_syntax_node(
13082 &mut self,
13083 _: &SelectSmallerSyntaxNode,
13084 window: &mut Window,
13085 cx: &mut Context<Self>,
13086 ) {
13087 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13088
13089 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13090 self.select_syntax_node_history.pop()
13091 {
13092 if let Some(selection) = selections.last_mut() {
13093 selection.reversed = is_selection_reversed;
13094 }
13095
13096 self.select_syntax_node_history.disable_clearing = true;
13097 self.change_selections(None, window, cx, |s| {
13098 s.select(selections.to_vec());
13099 });
13100 self.select_syntax_node_history.disable_clearing = false;
13101
13102 match scroll_behavior {
13103 SelectSyntaxNodeScrollBehavior::CursorTop => {
13104 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13105 }
13106 SelectSyntaxNodeScrollBehavior::FitSelection => {
13107 self.request_autoscroll(Autoscroll::fit(), cx);
13108 }
13109 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13110 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13111 }
13112 }
13113 }
13114 }
13115
13116 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13117 if !EditorSettings::get_global(cx).gutter.runnables {
13118 self.clear_tasks();
13119 return Task::ready(());
13120 }
13121 let project = self.project.as_ref().map(Entity::downgrade);
13122 let task_sources = self.lsp_task_sources(cx);
13123 cx.spawn_in(window, async move |editor, cx| {
13124 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13125 let Some(project) = project.and_then(|p| p.upgrade()) else {
13126 return;
13127 };
13128 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13129 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13130 }) else {
13131 return;
13132 };
13133
13134 let hide_runnables = project
13135 .update(cx, |project, cx| {
13136 // Do not display any test indicators in non-dev server remote projects.
13137 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13138 })
13139 .unwrap_or(true);
13140 if hide_runnables {
13141 return;
13142 }
13143 let new_rows =
13144 cx.background_spawn({
13145 let snapshot = display_snapshot.clone();
13146 async move {
13147 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13148 }
13149 })
13150 .await;
13151 let Ok(lsp_tasks) =
13152 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13153 else {
13154 return;
13155 };
13156 let lsp_tasks = lsp_tasks.await;
13157
13158 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13159 lsp_tasks
13160 .into_iter()
13161 .flat_map(|(kind, tasks)| {
13162 tasks.into_iter().filter_map(move |(location, task)| {
13163 Some((kind.clone(), location?, task))
13164 })
13165 })
13166 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13167 let buffer = location.target.buffer;
13168 let buffer_snapshot = buffer.read(cx).snapshot();
13169 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13170 |(excerpt_id, snapshot, _)| {
13171 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13172 display_snapshot
13173 .buffer_snapshot
13174 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13175 } else {
13176 None
13177 }
13178 },
13179 );
13180 if let Some(offset) = offset {
13181 let task_buffer_range =
13182 location.target.range.to_point(&buffer_snapshot);
13183 let context_buffer_range =
13184 task_buffer_range.to_offset(&buffer_snapshot);
13185 let context_range = BufferOffset(context_buffer_range.start)
13186 ..BufferOffset(context_buffer_range.end);
13187
13188 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13189 .or_insert_with(|| RunnableTasks {
13190 templates: Vec::new(),
13191 offset,
13192 column: task_buffer_range.start.column,
13193 extra_variables: HashMap::default(),
13194 context_range,
13195 })
13196 .templates
13197 .push((kind, task.original_task().clone()));
13198 }
13199
13200 acc
13201 })
13202 }) else {
13203 return;
13204 };
13205
13206 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13207 editor
13208 .update(cx, |editor, _| {
13209 editor.clear_tasks();
13210 for (key, mut value) in rows {
13211 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13212 value.templates.extend(lsp_tasks.templates);
13213 }
13214
13215 editor.insert_tasks(key, value);
13216 }
13217 for (key, value) in lsp_tasks_by_rows {
13218 editor.insert_tasks(key, value);
13219 }
13220 })
13221 .ok();
13222 })
13223 }
13224 fn fetch_runnable_ranges(
13225 snapshot: &DisplaySnapshot,
13226 range: Range<Anchor>,
13227 ) -> Vec<language::RunnableRange> {
13228 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13229 }
13230
13231 fn runnable_rows(
13232 project: Entity<Project>,
13233 snapshot: DisplaySnapshot,
13234 runnable_ranges: Vec<RunnableRange>,
13235 mut cx: AsyncWindowContext,
13236 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13237 runnable_ranges
13238 .into_iter()
13239 .filter_map(|mut runnable| {
13240 let tasks = cx
13241 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13242 .ok()?;
13243 if tasks.is_empty() {
13244 return None;
13245 }
13246
13247 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13248
13249 let row = snapshot
13250 .buffer_snapshot
13251 .buffer_line_for_row(MultiBufferRow(point.row))?
13252 .1
13253 .start
13254 .row;
13255
13256 let context_range =
13257 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13258 Some((
13259 (runnable.buffer_id, row),
13260 RunnableTasks {
13261 templates: tasks,
13262 offset: snapshot
13263 .buffer_snapshot
13264 .anchor_before(runnable.run_range.start),
13265 context_range,
13266 column: point.column,
13267 extra_variables: runnable.extra_captures,
13268 },
13269 ))
13270 })
13271 .collect()
13272 }
13273
13274 fn templates_with_tags(
13275 project: &Entity<Project>,
13276 runnable: &mut Runnable,
13277 cx: &mut App,
13278 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13279 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13280 let (worktree_id, file) = project
13281 .buffer_for_id(runnable.buffer, cx)
13282 .and_then(|buffer| buffer.read(cx).file())
13283 .map(|file| (file.worktree_id(cx), file.clone()))
13284 .unzip();
13285
13286 (
13287 project.task_store().read(cx).task_inventory().cloned(),
13288 worktree_id,
13289 file,
13290 )
13291 });
13292
13293 let mut templates_with_tags = mem::take(&mut runnable.tags)
13294 .into_iter()
13295 .flat_map(|RunnableTag(tag)| {
13296 inventory
13297 .as_ref()
13298 .into_iter()
13299 .flat_map(|inventory| {
13300 inventory.read(cx).list_tasks(
13301 file.clone(),
13302 Some(runnable.language.clone()),
13303 worktree_id,
13304 cx,
13305 )
13306 })
13307 .filter(move |(_, template)| {
13308 template.tags.iter().any(|source_tag| source_tag == &tag)
13309 })
13310 })
13311 .sorted_by_key(|(kind, _)| kind.to_owned())
13312 .collect::<Vec<_>>();
13313 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13314 // Strongest source wins; if we have worktree tag binding, prefer that to
13315 // global and language bindings;
13316 // if we have a global binding, prefer that to language binding.
13317 let first_mismatch = templates_with_tags
13318 .iter()
13319 .position(|(tag_source, _)| tag_source != leading_tag_source);
13320 if let Some(index) = first_mismatch {
13321 templates_with_tags.truncate(index);
13322 }
13323 }
13324
13325 templates_with_tags
13326 }
13327
13328 pub fn move_to_enclosing_bracket(
13329 &mut self,
13330 _: &MoveToEnclosingBracket,
13331 window: &mut Window,
13332 cx: &mut Context<Self>,
13333 ) {
13334 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13335 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13336 s.move_offsets_with(|snapshot, selection| {
13337 let Some(enclosing_bracket_ranges) =
13338 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13339 else {
13340 return;
13341 };
13342
13343 let mut best_length = usize::MAX;
13344 let mut best_inside = false;
13345 let mut best_in_bracket_range = false;
13346 let mut best_destination = None;
13347 for (open, close) in enclosing_bracket_ranges {
13348 let close = close.to_inclusive();
13349 let length = close.end() - open.start;
13350 let inside = selection.start >= open.end && selection.end <= *close.start();
13351 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13352 || close.contains(&selection.head());
13353
13354 // If best is next to a bracket and current isn't, skip
13355 if !in_bracket_range && best_in_bracket_range {
13356 continue;
13357 }
13358
13359 // Prefer smaller lengths unless best is inside and current isn't
13360 if length > best_length && (best_inside || !inside) {
13361 continue;
13362 }
13363
13364 best_length = length;
13365 best_inside = inside;
13366 best_in_bracket_range = in_bracket_range;
13367 best_destination = Some(
13368 if close.contains(&selection.start) && close.contains(&selection.end) {
13369 if inside { open.end } else { open.start }
13370 } else if inside {
13371 *close.start()
13372 } else {
13373 *close.end()
13374 },
13375 );
13376 }
13377
13378 if let Some(destination) = best_destination {
13379 selection.collapse_to(destination, SelectionGoal::None);
13380 }
13381 })
13382 });
13383 }
13384
13385 pub fn undo_selection(
13386 &mut self,
13387 _: &UndoSelection,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13392 self.end_selection(window, cx);
13393 self.selection_history.mode = SelectionHistoryMode::Undoing;
13394 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13395 self.change_selections(None, window, cx, |s| {
13396 s.select_anchors(entry.selections.to_vec())
13397 });
13398 self.select_next_state = entry.select_next_state;
13399 self.select_prev_state = entry.select_prev_state;
13400 self.add_selections_state = entry.add_selections_state;
13401 self.request_autoscroll(Autoscroll::newest(), cx);
13402 }
13403 self.selection_history.mode = SelectionHistoryMode::Normal;
13404 }
13405
13406 pub fn redo_selection(
13407 &mut self,
13408 _: &RedoSelection,
13409 window: &mut Window,
13410 cx: &mut Context<Self>,
13411 ) {
13412 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13413 self.end_selection(window, cx);
13414 self.selection_history.mode = SelectionHistoryMode::Redoing;
13415 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13416 self.change_selections(None, window, cx, |s| {
13417 s.select_anchors(entry.selections.to_vec())
13418 });
13419 self.select_next_state = entry.select_next_state;
13420 self.select_prev_state = entry.select_prev_state;
13421 self.add_selections_state = entry.add_selections_state;
13422 self.request_autoscroll(Autoscroll::newest(), cx);
13423 }
13424 self.selection_history.mode = SelectionHistoryMode::Normal;
13425 }
13426
13427 pub fn expand_excerpts(
13428 &mut self,
13429 action: &ExpandExcerpts,
13430 _: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13434 }
13435
13436 pub fn expand_excerpts_down(
13437 &mut self,
13438 action: &ExpandExcerptsDown,
13439 _: &mut Window,
13440 cx: &mut Context<Self>,
13441 ) {
13442 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13443 }
13444
13445 pub fn expand_excerpts_up(
13446 &mut self,
13447 action: &ExpandExcerptsUp,
13448 _: &mut Window,
13449 cx: &mut Context<Self>,
13450 ) {
13451 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13452 }
13453
13454 pub fn expand_excerpts_for_direction(
13455 &mut self,
13456 lines: u32,
13457 direction: ExpandExcerptDirection,
13458
13459 cx: &mut Context<Self>,
13460 ) {
13461 let selections = self.selections.disjoint_anchors();
13462
13463 let lines = if lines == 0 {
13464 EditorSettings::get_global(cx).expand_excerpt_lines
13465 } else {
13466 lines
13467 };
13468
13469 self.buffer.update(cx, |buffer, cx| {
13470 let snapshot = buffer.snapshot(cx);
13471 let mut excerpt_ids = selections
13472 .iter()
13473 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13474 .collect::<Vec<_>>();
13475 excerpt_ids.sort();
13476 excerpt_ids.dedup();
13477 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13478 })
13479 }
13480
13481 pub fn expand_excerpt(
13482 &mut self,
13483 excerpt: ExcerptId,
13484 direction: ExpandExcerptDirection,
13485 window: &mut Window,
13486 cx: &mut Context<Self>,
13487 ) {
13488 let current_scroll_position = self.scroll_position(cx);
13489 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13490 let mut should_scroll_up = false;
13491
13492 if direction == ExpandExcerptDirection::Down {
13493 let multi_buffer = self.buffer.read(cx);
13494 let snapshot = multi_buffer.snapshot(cx);
13495 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13496 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13497 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13498 let buffer_snapshot = buffer.read(cx).snapshot();
13499 let excerpt_end_row =
13500 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13501 let last_row = buffer_snapshot.max_point().row;
13502 let lines_below = last_row.saturating_sub(excerpt_end_row);
13503 should_scroll_up = lines_below >= lines_to_expand;
13504 }
13505 }
13506 }
13507 }
13508
13509 self.buffer.update(cx, |buffer, cx| {
13510 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13511 });
13512
13513 if should_scroll_up {
13514 let new_scroll_position =
13515 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13516 self.set_scroll_position(new_scroll_position, window, cx);
13517 }
13518 }
13519
13520 pub fn go_to_singleton_buffer_point(
13521 &mut self,
13522 point: Point,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 self.go_to_singleton_buffer_range(point..point, window, cx);
13527 }
13528
13529 pub fn go_to_singleton_buffer_range(
13530 &mut self,
13531 range: Range<Point>,
13532 window: &mut Window,
13533 cx: &mut Context<Self>,
13534 ) {
13535 let multibuffer = self.buffer().read(cx);
13536 let Some(buffer) = multibuffer.as_singleton() else {
13537 return;
13538 };
13539 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13540 return;
13541 };
13542 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13543 return;
13544 };
13545 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13546 s.select_anchor_ranges([start..end])
13547 });
13548 }
13549
13550 pub fn go_to_diagnostic(
13551 &mut self,
13552 _: &GoToDiagnostic,
13553 window: &mut Window,
13554 cx: &mut Context<Self>,
13555 ) {
13556 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13557 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13558 }
13559
13560 pub fn go_to_prev_diagnostic(
13561 &mut self,
13562 _: &GoToPreviousDiagnostic,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13567 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13568 }
13569
13570 pub fn go_to_diagnostic_impl(
13571 &mut self,
13572 direction: Direction,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 let buffer = self.buffer.read(cx).snapshot(cx);
13577 let selection = self.selections.newest::<usize>(cx);
13578
13579 let mut active_group_id = None;
13580 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13581 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13582 active_group_id = Some(active_group.group_id);
13583 }
13584 }
13585
13586 fn filtered(
13587 snapshot: EditorSnapshot,
13588 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13589 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13590 diagnostics
13591 .filter(|entry| entry.range.start != entry.range.end)
13592 .filter(|entry| !entry.diagnostic.is_unnecessary)
13593 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13594 }
13595
13596 let snapshot = self.snapshot(window, cx);
13597 let before = filtered(
13598 snapshot.clone(),
13599 buffer
13600 .diagnostics_in_range(0..selection.start)
13601 .filter(|entry| entry.range.start <= selection.start),
13602 );
13603 let after = filtered(
13604 snapshot,
13605 buffer
13606 .diagnostics_in_range(selection.start..buffer.len())
13607 .filter(|entry| entry.range.start >= selection.start),
13608 );
13609
13610 let mut found: Option<DiagnosticEntry<usize>> = None;
13611 if direction == Direction::Prev {
13612 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13613 {
13614 for diagnostic in prev_diagnostics.into_iter().rev() {
13615 if diagnostic.range.start != selection.start
13616 || active_group_id
13617 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13618 {
13619 found = Some(diagnostic);
13620 break 'outer;
13621 }
13622 }
13623 }
13624 } else {
13625 for diagnostic in after.chain(before) {
13626 if diagnostic.range.start != selection.start
13627 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13628 {
13629 found = Some(diagnostic);
13630 break;
13631 }
13632 }
13633 }
13634 let Some(next_diagnostic) = found else {
13635 return;
13636 };
13637
13638 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13639 return;
13640 };
13641 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13642 s.select_ranges(vec![
13643 next_diagnostic.range.start..next_diagnostic.range.start,
13644 ])
13645 });
13646 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13647 self.refresh_inline_completion(false, true, window, cx);
13648 }
13649
13650 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13652 let snapshot = self.snapshot(window, cx);
13653 let selection = self.selections.newest::<Point>(cx);
13654 self.go_to_hunk_before_or_after_position(
13655 &snapshot,
13656 selection.head(),
13657 Direction::Next,
13658 window,
13659 cx,
13660 );
13661 }
13662
13663 pub fn go_to_hunk_before_or_after_position(
13664 &mut self,
13665 snapshot: &EditorSnapshot,
13666 position: Point,
13667 direction: Direction,
13668 window: &mut Window,
13669 cx: &mut Context<Editor>,
13670 ) {
13671 let row = if direction == Direction::Next {
13672 self.hunk_after_position(snapshot, position)
13673 .map(|hunk| hunk.row_range.start)
13674 } else {
13675 self.hunk_before_position(snapshot, position)
13676 };
13677
13678 if let Some(row) = row {
13679 let destination = Point::new(row.0, 0);
13680 let autoscroll = Autoscroll::center();
13681
13682 self.unfold_ranges(&[destination..destination], false, false, cx);
13683 self.change_selections(Some(autoscroll), window, cx, |s| {
13684 s.select_ranges([destination..destination]);
13685 });
13686 }
13687 }
13688
13689 fn hunk_after_position(
13690 &mut self,
13691 snapshot: &EditorSnapshot,
13692 position: Point,
13693 ) -> Option<MultiBufferDiffHunk> {
13694 snapshot
13695 .buffer_snapshot
13696 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13697 .find(|hunk| hunk.row_range.start.0 > position.row)
13698 .or_else(|| {
13699 snapshot
13700 .buffer_snapshot
13701 .diff_hunks_in_range(Point::zero()..position)
13702 .find(|hunk| hunk.row_range.end.0 < position.row)
13703 })
13704 }
13705
13706 fn go_to_prev_hunk(
13707 &mut self,
13708 _: &GoToPreviousHunk,
13709 window: &mut Window,
13710 cx: &mut Context<Self>,
13711 ) {
13712 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13713 let snapshot = self.snapshot(window, cx);
13714 let selection = self.selections.newest::<Point>(cx);
13715 self.go_to_hunk_before_or_after_position(
13716 &snapshot,
13717 selection.head(),
13718 Direction::Prev,
13719 window,
13720 cx,
13721 );
13722 }
13723
13724 fn hunk_before_position(
13725 &mut self,
13726 snapshot: &EditorSnapshot,
13727 position: Point,
13728 ) -> Option<MultiBufferRow> {
13729 snapshot
13730 .buffer_snapshot
13731 .diff_hunk_before(position)
13732 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13733 }
13734
13735 fn go_to_next_change(
13736 &mut self,
13737 _: &GoToNextChange,
13738 window: &mut Window,
13739 cx: &mut Context<Self>,
13740 ) {
13741 if let Some(selections) = self
13742 .change_list
13743 .next_change(1, Direction::Next)
13744 .map(|s| s.to_vec())
13745 {
13746 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13747 let map = s.display_map();
13748 s.select_display_ranges(selections.iter().map(|a| {
13749 let point = a.to_display_point(&map);
13750 point..point
13751 }))
13752 })
13753 }
13754 }
13755
13756 fn go_to_previous_change(
13757 &mut self,
13758 _: &GoToPreviousChange,
13759 window: &mut Window,
13760 cx: &mut Context<Self>,
13761 ) {
13762 if let Some(selections) = self
13763 .change_list
13764 .next_change(1, Direction::Prev)
13765 .map(|s| s.to_vec())
13766 {
13767 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13768 let map = s.display_map();
13769 s.select_display_ranges(selections.iter().map(|a| {
13770 let point = a.to_display_point(&map);
13771 point..point
13772 }))
13773 })
13774 }
13775 }
13776
13777 fn go_to_line<T: 'static>(
13778 &mut self,
13779 position: Anchor,
13780 highlight_color: Option<Hsla>,
13781 window: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) {
13784 let snapshot = self.snapshot(window, cx).display_snapshot;
13785 let position = position.to_point(&snapshot.buffer_snapshot);
13786 let start = snapshot
13787 .buffer_snapshot
13788 .clip_point(Point::new(position.row, 0), Bias::Left);
13789 let end = start + Point::new(1, 0);
13790 let start = snapshot.buffer_snapshot.anchor_before(start);
13791 let end = snapshot.buffer_snapshot.anchor_before(end);
13792
13793 self.highlight_rows::<T>(
13794 start..end,
13795 highlight_color
13796 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13797 Default::default(),
13798 cx,
13799 );
13800 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13801 }
13802
13803 pub fn go_to_definition(
13804 &mut self,
13805 _: &GoToDefinition,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) -> Task<Result<Navigated>> {
13809 let definition =
13810 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13811 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13812 cx.spawn_in(window, async move |editor, cx| {
13813 if definition.await? == Navigated::Yes {
13814 return Ok(Navigated::Yes);
13815 }
13816 match fallback_strategy {
13817 GoToDefinitionFallback::None => Ok(Navigated::No),
13818 GoToDefinitionFallback::FindAllReferences => {
13819 match editor.update_in(cx, |editor, window, cx| {
13820 editor.find_all_references(&FindAllReferences, window, cx)
13821 })? {
13822 Some(references) => references.await,
13823 None => Ok(Navigated::No),
13824 }
13825 }
13826 }
13827 })
13828 }
13829
13830 pub fn go_to_declaration(
13831 &mut self,
13832 _: &GoToDeclaration,
13833 window: &mut Window,
13834 cx: &mut Context<Self>,
13835 ) -> Task<Result<Navigated>> {
13836 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13837 }
13838
13839 pub fn go_to_declaration_split(
13840 &mut self,
13841 _: &GoToDeclaration,
13842 window: &mut Window,
13843 cx: &mut Context<Self>,
13844 ) -> Task<Result<Navigated>> {
13845 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13846 }
13847
13848 pub fn go_to_implementation(
13849 &mut self,
13850 _: &GoToImplementation,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) -> Task<Result<Navigated>> {
13854 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13855 }
13856
13857 pub fn go_to_implementation_split(
13858 &mut self,
13859 _: &GoToImplementationSplit,
13860 window: &mut Window,
13861 cx: &mut Context<Self>,
13862 ) -> Task<Result<Navigated>> {
13863 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13864 }
13865
13866 pub fn go_to_type_definition(
13867 &mut self,
13868 _: &GoToTypeDefinition,
13869 window: &mut Window,
13870 cx: &mut Context<Self>,
13871 ) -> Task<Result<Navigated>> {
13872 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13873 }
13874
13875 pub fn go_to_definition_split(
13876 &mut self,
13877 _: &GoToDefinitionSplit,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) -> Task<Result<Navigated>> {
13881 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13882 }
13883
13884 pub fn go_to_type_definition_split(
13885 &mut self,
13886 _: &GoToTypeDefinitionSplit,
13887 window: &mut Window,
13888 cx: &mut Context<Self>,
13889 ) -> Task<Result<Navigated>> {
13890 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13891 }
13892
13893 fn go_to_definition_of_kind(
13894 &mut self,
13895 kind: GotoDefinitionKind,
13896 split: bool,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) -> Task<Result<Navigated>> {
13900 let Some(provider) = self.semantics_provider.clone() else {
13901 return Task::ready(Ok(Navigated::No));
13902 };
13903 let head = self.selections.newest::<usize>(cx).head();
13904 let buffer = self.buffer.read(cx);
13905 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13906 text_anchor
13907 } else {
13908 return Task::ready(Ok(Navigated::No));
13909 };
13910
13911 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13912 return Task::ready(Ok(Navigated::No));
13913 };
13914
13915 cx.spawn_in(window, async move |editor, cx| {
13916 let definitions = definitions.await?;
13917 let navigated = editor
13918 .update_in(cx, |editor, window, cx| {
13919 editor.navigate_to_hover_links(
13920 Some(kind),
13921 definitions
13922 .into_iter()
13923 .filter(|location| {
13924 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13925 })
13926 .map(HoverLink::Text)
13927 .collect::<Vec<_>>(),
13928 split,
13929 window,
13930 cx,
13931 )
13932 })?
13933 .await?;
13934 anyhow::Ok(navigated)
13935 })
13936 }
13937
13938 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13939 let selection = self.selections.newest_anchor();
13940 let head = selection.head();
13941 let tail = selection.tail();
13942
13943 let Some((buffer, start_position)) =
13944 self.buffer.read(cx).text_anchor_for_position(head, cx)
13945 else {
13946 return;
13947 };
13948
13949 let end_position = if head != tail {
13950 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13951 return;
13952 };
13953 Some(pos)
13954 } else {
13955 None
13956 };
13957
13958 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13959 let url = if let Some(end_pos) = end_position {
13960 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13961 } else {
13962 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13963 };
13964
13965 if let Some(url) = url {
13966 editor.update(cx, |_, cx| {
13967 cx.open_url(&url);
13968 })
13969 } else {
13970 Ok(())
13971 }
13972 });
13973
13974 url_finder.detach();
13975 }
13976
13977 pub fn open_selected_filename(
13978 &mut self,
13979 _: &OpenSelectedFilename,
13980 window: &mut Window,
13981 cx: &mut Context<Self>,
13982 ) {
13983 let Some(workspace) = self.workspace() else {
13984 return;
13985 };
13986
13987 let position = self.selections.newest_anchor().head();
13988
13989 let Some((buffer, buffer_position)) =
13990 self.buffer.read(cx).text_anchor_for_position(position, cx)
13991 else {
13992 return;
13993 };
13994
13995 let project = self.project.clone();
13996
13997 cx.spawn_in(window, async move |_, cx| {
13998 let result = find_file(&buffer, project, buffer_position, cx).await;
13999
14000 if let Some((_, path)) = result {
14001 workspace
14002 .update_in(cx, |workspace, window, cx| {
14003 workspace.open_resolved_path(path, window, cx)
14004 })?
14005 .await?;
14006 }
14007 anyhow::Ok(())
14008 })
14009 .detach();
14010 }
14011
14012 pub(crate) fn navigate_to_hover_links(
14013 &mut self,
14014 kind: Option<GotoDefinitionKind>,
14015 mut definitions: Vec<HoverLink>,
14016 split: bool,
14017 window: &mut Window,
14018 cx: &mut Context<Editor>,
14019 ) -> Task<Result<Navigated>> {
14020 // If there is one definition, just open it directly
14021 if definitions.len() == 1 {
14022 let definition = definitions.pop().unwrap();
14023
14024 enum TargetTaskResult {
14025 Location(Option<Location>),
14026 AlreadyNavigated,
14027 }
14028
14029 let target_task = match definition {
14030 HoverLink::Text(link) => {
14031 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14032 }
14033 HoverLink::InlayHint(lsp_location, server_id) => {
14034 let computation =
14035 self.compute_target_location(lsp_location, server_id, window, cx);
14036 cx.background_spawn(async move {
14037 let location = computation.await?;
14038 Ok(TargetTaskResult::Location(location))
14039 })
14040 }
14041 HoverLink::Url(url) => {
14042 cx.open_url(&url);
14043 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14044 }
14045 HoverLink::File(path) => {
14046 if let Some(workspace) = self.workspace() {
14047 cx.spawn_in(window, async move |_, cx| {
14048 workspace
14049 .update_in(cx, |workspace, window, cx| {
14050 workspace.open_resolved_path(path, window, cx)
14051 })?
14052 .await
14053 .map(|_| TargetTaskResult::AlreadyNavigated)
14054 })
14055 } else {
14056 Task::ready(Ok(TargetTaskResult::Location(None)))
14057 }
14058 }
14059 };
14060 cx.spawn_in(window, async move |editor, cx| {
14061 let target = match target_task.await.context("target resolution task")? {
14062 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14063 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14064 TargetTaskResult::Location(Some(target)) => target,
14065 };
14066
14067 editor.update_in(cx, |editor, window, cx| {
14068 let Some(workspace) = editor.workspace() else {
14069 return Navigated::No;
14070 };
14071 let pane = workspace.read(cx).active_pane().clone();
14072
14073 let range = target.range.to_point(target.buffer.read(cx));
14074 let range = editor.range_for_match(&range);
14075 let range = collapse_multiline_range(range);
14076
14077 if !split
14078 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14079 {
14080 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14081 } else {
14082 window.defer(cx, move |window, cx| {
14083 let target_editor: Entity<Self> =
14084 workspace.update(cx, |workspace, cx| {
14085 let pane = if split {
14086 workspace.adjacent_pane(window, cx)
14087 } else {
14088 workspace.active_pane().clone()
14089 };
14090
14091 workspace.open_project_item(
14092 pane,
14093 target.buffer.clone(),
14094 true,
14095 true,
14096 window,
14097 cx,
14098 )
14099 });
14100 target_editor.update(cx, |target_editor, cx| {
14101 // When selecting a definition in a different buffer, disable the nav history
14102 // to avoid creating a history entry at the previous cursor location.
14103 pane.update(cx, |pane, _| pane.disable_history());
14104 target_editor.go_to_singleton_buffer_range(range, window, cx);
14105 pane.update(cx, |pane, _| pane.enable_history());
14106 });
14107 });
14108 }
14109 Navigated::Yes
14110 })
14111 })
14112 } else if !definitions.is_empty() {
14113 cx.spawn_in(window, async move |editor, cx| {
14114 let (title, location_tasks, workspace) = editor
14115 .update_in(cx, |editor, window, cx| {
14116 let tab_kind = match kind {
14117 Some(GotoDefinitionKind::Implementation) => "Implementations",
14118 _ => "Definitions",
14119 };
14120 let title = definitions
14121 .iter()
14122 .find_map(|definition| match definition {
14123 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14124 let buffer = origin.buffer.read(cx);
14125 format!(
14126 "{} for {}",
14127 tab_kind,
14128 buffer
14129 .text_for_range(origin.range.clone())
14130 .collect::<String>()
14131 )
14132 }),
14133 HoverLink::InlayHint(_, _) => None,
14134 HoverLink::Url(_) => None,
14135 HoverLink::File(_) => None,
14136 })
14137 .unwrap_or(tab_kind.to_string());
14138 let location_tasks = definitions
14139 .into_iter()
14140 .map(|definition| match definition {
14141 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14142 HoverLink::InlayHint(lsp_location, server_id) => editor
14143 .compute_target_location(lsp_location, server_id, window, cx),
14144 HoverLink::Url(_) => Task::ready(Ok(None)),
14145 HoverLink::File(_) => Task::ready(Ok(None)),
14146 })
14147 .collect::<Vec<_>>();
14148 (title, location_tasks, editor.workspace().clone())
14149 })
14150 .context("location tasks preparation")?;
14151
14152 let locations = future::join_all(location_tasks)
14153 .await
14154 .into_iter()
14155 .filter_map(|location| location.transpose())
14156 .collect::<Result<_>>()
14157 .context("location tasks")?;
14158
14159 let Some(workspace) = workspace else {
14160 return Ok(Navigated::No);
14161 };
14162 let opened = workspace
14163 .update_in(cx, |workspace, window, cx| {
14164 Self::open_locations_in_multibuffer(
14165 workspace,
14166 locations,
14167 title,
14168 split,
14169 MultibufferSelectionMode::First,
14170 window,
14171 cx,
14172 )
14173 })
14174 .ok();
14175
14176 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14177 })
14178 } else {
14179 Task::ready(Ok(Navigated::No))
14180 }
14181 }
14182
14183 fn compute_target_location(
14184 &self,
14185 lsp_location: lsp::Location,
14186 server_id: LanguageServerId,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) -> Task<anyhow::Result<Option<Location>>> {
14190 let Some(project) = self.project.clone() else {
14191 return Task::ready(Ok(None));
14192 };
14193
14194 cx.spawn_in(window, async move |editor, cx| {
14195 let location_task = editor.update(cx, |_, cx| {
14196 project.update(cx, |project, cx| {
14197 let language_server_name = project
14198 .language_server_statuses(cx)
14199 .find(|(id, _)| server_id == *id)
14200 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14201 language_server_name.map(|language_server_name| {
14202 project.open_local_buffer_via_lsp(
14203 lsp_location.uri.clone(),
14204 server_id,
14205 language_server_name,
14206 cx,
14207 )
14208 })
14209 })
14210 })?;
14211 let location = match location_task {
14212 Some(task) => Some({
14213 let target_buffer_handle = task.await.context("open local buffer")?;
14214 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14215 let target_start = target_buffer
14216 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14217 let target_end = target_buffer
14218 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14219 target_buffer.anchor_after(target_start)
14220 ..target_buffer.anchor_before(target_end)
14221 })?;
14222 Location {
14223 buffer: target_buffer_handle,
14224 range,
14225 }
14226 }),
14227 None => None,
14228 };
14229 Ok(location)
14230 })
14231 }
14232
14233 pub fn find_all_references(
14234 &mut self,
14235 _: &FindAllReferences,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) -> Option<Task<Result<Navigated>>> {
14239 let selection = self.selections.newest::<usize>(cx);
14240 let multi_buffer = self.buffer.read(cx);
14241 let head = selection.head();
14242
14243 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14244 let head_anchor = multi_buffer_snapshot.anchor_at(
14245 head,
14246 if head < selection.tail() {
14247 Bias::Right
14248 } else {
14249 Bias::Left
14250 },
14251 );
14252
14253 match self
14254 .find_all_references_task_sources
14255 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14256 {
14257 Ok(_) => {
14258 log::info!(
14259 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14260 );
14261 return None;
14262 }
14263 Err(i) => {
14264 self.find_all_references_task_sources.insert(i, head_anchor);
14265 }
14266 }
14267
14268 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14269 let workspace = self.workspace()?;
14270 let project = workspace.read(cx).project().clone();
14271 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14272 Some(cx.spawn_in(window, async move |editor, cx| {
14273 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14274 if let Ok(i) = editor
14275 .find_all_references_task_sources
14276 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14277 {
14278 editor.find_all_references_task_sources.remove(i);
14279 }
14280 });
14281
14282 let locations = references.await?;
14283 if locations.is_empty() {
14284 return anyhow::Ok(Navigated::No);
14285 }
14286
14287 workspace.update_in(cx, |workspace, window, cx| {
14288 let title = locations
14289 .first()
14290 .as_ref()
14291 .map(|location| {
14292 let buffer = location.buffer.read(cx);
14293 format!(
14294 "References to `{}`",
14295 buffer
14296 .text_for_range(location.range.clone())
14297 .collect::<String>()
14298 )
14299 })
14300 .unwrap();
14301 Self::open_locations_in_multibuffer(
14302 workspace,
14303 locations,
14304 title,
14305 false,
14306 MultibufferSelectionMode::First,
14307 window,
14308 cx,
14309 );
14310 Navigated::Yes
14311 })
14312 }))
14313 }
14314
14315 /// Opens a multibuffer with the given project locations in it
14316 pub fn open_locations_in_multibuffer(
14317 workspace: &mut Workspace,
14318 mut locations: Vec<Location>,
14319 title: String,
14320 split: bool,
14321 multibuffer_selection_mode: MultibufferSelectionMode,
14322 window: &mut Window,
14323 cx: &mut Context<Workspace>,
14324 ) {
14325 // If there are multiple definitions, open them in a multibuffer
14326 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14327 let mut locations = locations.into_iter().peekable();
14328 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14329 let capability = workspace.project().read(cx).capability();
14330
14331 let excerpt_buffer = cx.new(|cx| {
14332 let mut multibuffer = MultiBuffer::new(capability);
14333 while let Some(location) = locations.next() {
14334 let buffer = location.buffer.read(cx);
14335 let mut ranges_for_buffer = Vec::new();
14336 let range = location.range.to_point(buffer);
14337 ranges_for_buffer.push(range.clone());
14338
14339 while let Some(next_location) = locations.peek() {
14340 if next_location.buffer == location.buffer {
14341 ranges_for_buffer.push(next_location.range.to_point(buffer));
14342 locations.next();
14343 } else {
14344 break;
14345 }
14346 }
14347
14348 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14349 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14350 PathKey::for_buffer(&location.buffer, cx),
14351 location.buffer.clone(),
14352 ranges_for_buffer,
14353 DEFAULT_MULTIBUFFER_CONTEXT,
14354 cx,
14355 );
14356 ranges.extend(new_ranges)
14357 }
14358
14359 multibuffer.with_title(title)
14360 });
14361
14362 let editor = cx.new(|cx| {
14363 Editor::for_multibuffer(
14364 excerpt_buffer,
14365 Some(workspace.project().clone()),
14366 window,
14367 cx,
14368 )
14369 });
14370 editor.update(cx, |editor, cx| {
14371 match multibuffer_selection_mode {
14372 MultibufferSelectionMode::First => {
14373 if let Some(first_range) = ranges.first() {
14374 editor.change_selections(None, window, cx, |selections| {
14375 selections.clear_disjoint();
14376 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14377 });
14378 }
14379 editor.highlight_background::<Self>(
14380 &ranges,
14381 |theme| theme.editor_highlighted_line_background,
14382 cx,
14383 );
14384 }
14385 MultibufferSelectionMode::All => {
14386 editor.change_selections(None, window, cx, |selections| {
14387 selections.clear_disjoint();
14388 selections.select_anchor_ranges(ranges);
14389 });
14390 }
14391 }
14392 editor.register_buffers_with_language_servers(cx);
14393 });
14394
14395 let item = Box::new(editor);
14396 let item_id = item.item_id();
14397
14398 if split {
14399 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14400 } else {
14401 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14402 let (preview_item_id, preview_item_idx) =
14403 workspace.active_pane().update(cx, |pane, _| {
14404 (pane.preview_item_id(), pane.preview_item_idx())
14405 });
14406
14407 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14408
14409 if let Some(preview_item_id) = preview_item_id {
14410 workspace.active_pane().update(cx, |pane, cx| {
14411 pane.remove_item(preview_item_id, false, false, window, cx);
14412 });
14413 }
14414 } else {
14415 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14416 }
14417 }
14418 workspace.active_pane().update(cx, |pane, cx| {
14419 pane.set_preview_item_id(Some(item_id), cx);
14420 });
14421 }
14422
14423 pub fn rename(
14424 &mut self,
14425 _: &Rename,
14426 window: &mut Window,
14427 cx: &mut Context<Self>,
14428 ) -> Option<Task<Result<()>>> {
14429 use language::ToOffset as _;
14430
14431 let provider = self.semantics_provider.clone()?;
14432 let selection = self.selections.newest_anchor().clone();
14433 let (cursor_buffer, cursor_buffer_position) = self
14434 .buffer
14435 .read(cx)
14436 .text_anchor_for_position(selection.head(), cx)?;
14437 let (tail_buffer, cursor_buffer_position_end) = self
14438 .buffer
14439 .read(cx)
14440 .text_anchor_for_position(selection.tail(), cx)?;
14441 if tail_buffer != cursor_buffer {
14442 return None;
14443 }
14444
14445 let snapshot = cursor_buffer.read(cx).snapshot();
14446 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14447 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14448 let prepare_rename = provider
14449 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14450 .unwrap_or_else(|| Task::ready(Ok(None)));
14451 drop(snapshot);
14452
14453 Some(cx.spawn_in(window, async move |this, cx| {
14454 let rename_range = if let Some(range) = prepare_rename.await? {
14455 Some(range)
14456 } else {
14457 this.update(cx, |this, cx| {
14458 let buffer = this.buffer.read(cx).snapshot(cx);
14459 let mut buffer_highlights = this
14460 .document_highlights_for_position(selection.head(), &buffer)
14461 .filter(|highlight| {
14462 highlight.start.excerpt_id == selection.head().excerpt_id
14463 && highlight.end.excerpt_id == selection.head().excerpt_id
14464 });
14465 buffer_highlights
14466 .next()
14467 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14468 })?
14469 };
14470 if let Some(rename_range) = rename_range {
14471 this.update_in(cx, |this, window, cx| {
14472 let snapshot = cursor_buffer.read(cx).snapshot();
14473 let rename_buffer_range = rename_range.to_offset(&snapshot);
14474 let cursor_offset_in_rename_range =
14475 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14476 let cursor_offset_in_rename_range_end =
14477 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14478
14479 this.take_rename(false, window, cx);
14480 let buffer = this.buffer.read(cx).read(cx);
14481 let cursor_offset = selection.head().to_offset(&buffer);
14482 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14483 let rename_end = rename_start + rename_buffer_range.len();
14484 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14485 let mut old_highlight_id = None;
14486 let old_name: Arc<str> = buffer
14487 .chunks(rename_start..rename_end, true)
14488 .map(|chunk| {
14489 if old_highlight_id.is_none() {
14490 old_highlight_id = chunk.syntax_highlight_id;
14491 }
14492 chunk.text
14493 })
14494 .collect::<String>()
14495 .into();
14496
14497 drop(buffer);
14498
14499 // Position the selection in the rename editor so that it matches the current selection.
14500 this.show_local_selections = false;
14501 let rename_editor = cx.new(|cx| {
14502 let mut editor = Editor::single_line(window, cx);
14503 editor.buffer.update(cx, |buffer, cx| {
14504 buffer.edit([(0..0, old_name.clone())], None, cx)
14505 });
14506 let rename_selection_range = match cursor_offset_in_rename_range
14507 .cmp(&cursor_offset_in_rename_range_end)
14508 {
14509 Ordering::Equal => {
14510 editor.select_all(&SelectAll, window, cx);
14511 return editor;
14512 }
14513 Ordering::Less => {
14514 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14515 }
14516 Ordering::Greater => {
14517 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14518 }
14519 };
14520 if rename_selection_range.end > old_name.len() {
14521 editor.select_all(&SelectAll, window, cx);
14522 } else {
14523 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14524 s.select_ranges([rename_selection_range]);
14525 });
14526 }
14527 editor
14528 });
14529 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14530 if e == &EditorEvent::Focused {
14531 cx.emit(EditorEvent::FocusedIn)
14532 }
14533 })
14534 .detach();
14535
14536 let write_highlights =
14537 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14538 let read_highlights =
14539 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14540 let ranges = write_highlights
14541 .iter()
14542 .flat_map(|(_, ranges)| ranges.iter())
14543 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14544 .cloned()
14545 .collect();
14546
14547 this.highlight_text::<Rename>(
14548 ranges,
14549 HighlightStyle {
14550 fade_out: Some(0.6),
14551 ..Default::default()
14552 },
14553 cx,
14554 );
14555 let rename_focus_handle = rename_editor.focus_handle(cx);
14556 window.focus(&rename_focus_handle);
14557 let block_id = this.insert_blocks(
14558 [BlockProperties {
14559 style: BlockStyle::Flex,
14560 placement: BlockPlacement::Below(range.start),
14561 height: Some(1),
14562 render: Arc::new({
14563 let rename_editor = rename_editor.clone();
14564 move |cx: &mut BlockContext| {
14565 let mut text_style = cx.editor_style.text.clone();
14566 if let Some(highlight_style) = old_highlight_id
14567 .and_then(|h| h.style(&cx.editor_style.syntax))
14568 {
14569 text_style = text_style.highlight(highlight_style);
14570 }
14571 div()
14572 .block_mouse_down()
14573 .pl(cx.anchor_x)
14574 .child(EditorElement::new(
14575 &rename_editor,
14576 EditorStyle {
14577 background: cx.theme().system().transparent,
14578 local_player: cx.editor_style.local_player,
14579 text: text_style,
14580 scrollbar_width: cx.editor_style.scrollbar_width,
14581 syntax: cx.editor_style.syntax.clone(),
14582 status: cx.editor_style.status.clone(),
14583 inlay_hints_style: HighlightStyle {
14584 font_weight: Some(FontWeight::BOLD),
14585 ..make_inlay_hints_style(cx.app)
14586 },
14587 inline_completion_styles: make_suggestion_styles(
14588 cx.app,
14589 ),
14590 ..EditorStyle::default()
14591 },
14592 ))
14593 .into_any_element()
14594 }
14595 }),
14596 priority: 0,
14597 }],
14598 Some(Autoscroll::fit()),
14599 cx,
14600 )[0];
14601 this.pending_rename = Some(RenameState {
14602 range,
14603 old_name,
14604 editor: rename_editor,
14605 block_id,
14606 });
14607 })?;
14608 }
14609
14610 Ok(())
14611 }))
14612 }
14613
14614 pub fn confirm_rename(
14615 &mut self,
14616 _: &ConfirmRename,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) -> Option<Task<Result<()>>> {
14620 let rename = self.take_rename(false, window, cx)?;
14621 let workspace = self.workspace()?.downgrade();
14622 let (buffer, start) = self
14623 .buffer
14624 .read(cx)
14625 .text_anchor_for_position(rename.range.start, cx)?;
14626 let (end_buffer, _) = self
14627 .buffer
14628 .read(cx)
14629 .text_anchor_for_position(rename.range.end, cx)?;
14630 if buffer != end_buffer {
14631 return None;
14632 }
14633
14634 let old_name = rename.old_name;
14635 let new_name = rename.editor.read(cx).text(cx);
14636
14637 let rename = self.semantics_provider.as_ref()?.perform_rename(
14638 &buffer,
14639 start,
14640 new_name.clone(),
14641 cx,
14642 )?;
14643
14644 Some(cx.spawn_in(window, async move |editor, cx| {
14645 let project_transaction = rename.await?;
14646 Self::open_project_transaction(
14647 &editor,
14648 workspace,
14649 project_transaction,
14650 format!("Rename: {} → {}", old_name, new_name),
14651 cx,
14652 )
14653 .await?;
14654
14655 editor.update(cx, |editor, cx| {
14656 editor.refresh_document_highlights(cx);
14657 })?;
14658 Ok(())
14659 }))
14660 }
14661
14662 fn take_rename(
14663 &mut self,
14664 moving_cursor: bool,
14665 window: &mut Window,
14666 cx: &mut Context<Self>,
14667 ) -> Option<RenameState> {
14668 let rename = self.pending_rename.take()?;
14669 if rename.editor.focus_handle(cx).is_focused(window) {
14670 window.focus(&self.focus_handle);
14671 }
14672
14673 self.remove_blocks(
14674 [rename.block_id].into_iter().collect(),
14675 Some(Autoscroll::fit()),
14676 cx,
14677 );
14678 self.clear_highlights::<Rename>(cx);
14679 self.show_local_selections = true;
14680
14681 if moving_cursor {
14682 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14683 editor.selections.newest::<usize>(cx).head()
14684 });
14685
14686 // Update the selection to match the position of the selection inside
14687 // the rename editor.
14688 let snapshot = self.buffer.read(cx).read(cx);
14689 let rename_range = rename.range.to_offset(&snapshot);
14690 let cursor_in_editor = snapshot
14691 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14692 .min(rename_range.end);
14693 drop(snapshot);
14694
14695 self.change_selections(None, window, cx, |s| {
14696 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14697 });
14698 } else {
14699 self.refresh_document_highlights(cx);
14700 }
14701
14702 Some(rename)
14703 }
14704
14705 pub fn pending_rename(&self) -> Option<&RenameState> {
14706 self.pending_rename.as_ref()
14707 }
14708
14709 fn format(
14710 &mut self,
14711 _: &Format,
14712 window: &mut Window,
14713 cx: &mut Context<Self>,
14714 ) -> Option<Task<Result<()>>> {
14715 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14716
14717 let project = match &self.project {
14718 Some(project) => project.clone(),
14719 None => return None,
14720 };
14721
14722 Some(self.perform_format(
14723 project,
14724 FormatTrigger::Manual,
14725 FormatTarget::Buffers,
14726 window,
14727 cx,
14728 ))
14729 }
14730
14731 fn format_selections(
14732 &mut self,
14733 _: &FormatSelections,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) -> Option<Task<Result<()>>> {
14737 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14738
14739 let project = match &self.project {
14740 Some(project) => project.clone(),
14741 None => return None,
14742 };
14743
14744 let ranges = self
14745 .selections
14746 .all_adjusted(cx)
14747 .into_iter()
14748 .map(|selection| selection.range())
14749 .collect_vec();
14750
14751 Some(self.perform_format(
14752 project,
14753 FormatTrigger::Manual,
14754 FormatTarget::Ranges(ranges),
14755 window,
14756 cx,
14757 ))
14758 }
14759
14760 fn perform_format(
14761 &mut self,
14762 project: Entity<Project>,
14763 trigger: FormatTrigger,
14764 target: FormatTarget,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) -> Task<Result<()>> {
14768 let buffer = self.buffer.clone();
14769 let (buffers, target) = match target {
14770 FormatTarget::Buffers => {
14771 let mut buffers = buffer.read(cx).all_buffers();
14772 if trigger == FormatTrigger::Save {
14773 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14774 }
14775 (buffers, LspFormatTarget::Buffers)
14776 }
14777 FormatTarget::Ranges(selection_ranges) => {
14778 let multi_buffer = buffer.read(cx);
14779 let snapshot = multi_buffer.read(cx);
14780 let mut buffers = HashSet::default();
14781 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14782 BTreeMap::new();
14783 for selection_range in selection_ranges {
14784 for (buffer, buffer_range, _) in
14785 snapshot.range_to_buffer_ranges(selection_range)
14786 {
14787 let buffer_id = buffer.remote_id();
14788 let start = buffer.anchor_before(buffer_range.start);
14789 let end = buffer.anchor_after(buffer_range.end);
14790 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14791 buffer_id_to_ranges
14792 .entry(buffer_id)
14793 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14794 .or_insert_with(|| vec![start..end]);
14795 }
14796 }
14797 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14798 }
14799 };
14800
14801 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14802 let selections_prev = transaction_id_prev
14803 .and_then(|transaction_id_prev| {
14804 // default to selections as they were after the last edit, if we have them,
14805 // instead of how they are now.
14806 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14807 // will take you back to where you made the last edit, instead of staying where you scrolled
14808 self.selection_history
14809 .transaction(transaction_id_prev)
14810 .map(|t| t.0.clone())
14811 })
14812 .unwrap_or_else(|| {
14813 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14814 self.selections.disjoint_anchors()
14815 });
14816
14817 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14818 let format = project.update(cx, |project, cx| {
14819 project.format(buffers, target, true, trigger, cx)
14820 });
14821
14822 cx.spawn_in(window, async move |editor, cx| {
14823 let transaction = futures::select_biased! {
14824 transaction = format.log_err().fuse() => transaction,
14825 () = timeout => {
14826 log::warn!("timed out waiting for formatting");
14827 None
14828 }
14829 };
14830
14831 buffer
14832 .update(cx, |buffer, cx| {
14833 if let Some(transaction) = transaction {
14834 if !buffer.is_singleton() {
14835 buffer.push_transaction(&transaction.0, cx);
14836 }
14837 }
14838 cx.notify();
14839 })
14840 .ok();
14841
14842 if let Some(transaction_id_now) =
14843 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14844 {
14845 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14846 if has_new_transaction {
14847 _ = editor.update(cx, |editor, _| {
14848 editor
14849 .selection_history
14850 .insert_transaction(transaction_id_now, selections_prev);
14851 });
14852 }
14853 }
14854
14855 Ok(())
14856 })
14857 }
14858
14859 fn organize_imports(
14860 &mut self,
14861 _: &OrganizeImports,
14862 window: &mut Window,
14863 cx: &mut Context<Self>,
14864 ) -> Option<Task<Result<()>>> {
14865 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14866 let project = match &self.project {
14867 Some(project) => project.clone(),
14868 None => return None,
14869 };
14870 Some(self.perform_code_action_kind(
14871 project,
14872 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14873 window,
14874 cx,
14875 ))
14876 }
14877
14878 fn perform_code_action_kind(
14879 &mut self,
14880 project: Entity<Project>,
14881 kind: CodeActionKind,
14882 window: &mut Window,
14883 cx: &mut Context<Self>,
14884 ) -> Task<Result<()>> {
14885 let buffer = self.buffer.clone();
14886 let buffers = buffer.read(cx).all_buffers();
14887 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14888 let apply_action = project.update(cx, |project, cx| {
14889 project.apply_code_action_kind(buffers, kind, true, cx)
14890 });
14891 cx.spawn_in(window, async move |_, cx| {
14892 let transaction = futures::select_biased! {
14893 () = timeout => {
14894 log::warn!("timed out waiting for executing code action");
14895 None
14896 }
14897 transaction = apply_action.log_err().fuse() => transaction,
14898 };
14899 buffer
14900 .update(cx, |buffer, cx| {
14901 // check if we need this
14902 if let Some(transaction) = transaction {
14903 if !buffer.is_singleton() {
14904 buffer.push_transaction(&transaction.0, cx);
14905 }
14906 }
14907 cx.notify();
14908 })
14909 .ok();
14910 Ok(())
14911 })
14912 }
14913
14914 fn restart_language_server(
14915 &mut self,
14916 _: &RestartLanguageServer,
14917 _: &mut Window,
14918 cx: &mut Context<Self>,
14919 ) {
14920 if let Some(project) = self.project.clone() {
14921 self.buffer.update(cx, |multi_buffer, cx| {
14922 project.update(cx, |project, cx| {
14923 project.restart_language_servers_for_buffers(
14924 multi_buffer.all_buffers().into_iter().collect(),
14925 cx,
14926 );
14927 });
14928 })
14929 }
14930 }
14931
14932 fn stop_language_server(
14933 &mut self,
14934 _: &StopLanguageServer,
14935 _: &mut Window,
14936 cx: &mut Context<Self>,
14937 ) {
14938 if let Some(project) = self.project.clone() {
14939 self.buffer.update(cx, |multi_buffer, cx| {
14940 project.update(cx, |project, cx| {
14941 project.stop_language_servers_for_buffers(
14942 multi_buffer.all_buffers().into_iter().collect(),
14943 cx,
14944 );
14945 cx.emit(project::Event::RefreshInlayHints);
14946 });
14947 });
14948 }
14949 }
14950
14951 fn cancel_language_server_work(
14952 workspace: &mut Workspace,
14953 _: &actions::CancelLanguageServerWork,
14954 _: &mut Window,
14955 cx: &mut Context<Workspace>,
14956 ) {
14957 let project = workspace.project();
14958 let buffers = workspace
14959 .active_item(cx)
14960 .and_then(|item| item.act_as::<Editor>(cx))
14961 .map_or(HashSet::default(), |editor| {
14962 editor.read(cx).buffer.read(cx).all_buffers()
14963 });
14964 project.update(cx, |project, cx| {
14965 project.cancel_language_server_work_for_buffers(buffers, cx);
14966 });
14967 }
14968
14969 fn show_character_palette(
14970 &mut self,
14971 _: &ShowCharacterPalette,
14972 window: &mut Window,
14973 _: &mut Context<Self>,
14974 ) {
14975 window.show_character_palette();
14976 }
14977
14978 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14979 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14980 let buffer = self.buffer.read(cx).snapshot(cx);
14981 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14982 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14983 let is_valid = buffer
14984 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14985 .any(|entry| {
14986 entry.diagnostic.is_primary
14987 && !entry.range.is_empty()
14988 && entry.range.start == primary_range_start
14989 && entry.diagnostic.message == active_diagnostics.active_message
14990 });
14991
14992 if !is_valid {
14993 self.dismiss_diagnostics(cx);
14994 }
14995 }
14996 }
14997
14998 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14999 match &self.active_diagnostics {
15000 ActiveDiagnostic::Group(group) => Some(group),
15001 _ => None,
15002 }
15003 }
15004
15005 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15006 self.dismiss_diagnostics(cx);
15007 self.active_diagnostics = ActiveDiagnostic::All;
15008 }
15009
15010 fn activate_diagnostics(
15011 &mut self,
15012 buffer_id: BufferId,
15013 diagnostic: DiagnosticEntry<usize>,
15014 window: &mut Window,
15015 cx: &mut Context<Self>,
15016 ) {
15017 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15018 return;
15019 }
15020 self.dismiss_diagnostics(cx);
15021 let snapshot = self.snapshot(window, cx);
15022 let buffer = self.buffer.read(cx).snapshot(cx);
15023 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15024 return;
15025 };
15026
15027 let diagnostic_group = buffer
15028 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15029 .collect::<Vec<_>>();
15030
15031 let blocks =
15032 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15033
15034 let blocks = self.display_map.update(cx, |display_map, cx| {
15035 display_map.insert_blocks(blocks, cx).into_iter().collect()
15036 });
15037 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15038 active_range: buffer.anchor_before(diagnostic.range.start)
15039 ..buffer.anchor_after(diagnostic.range.end),
15040 active_message: diagnostic.diagnostic.message.clone(),
15041 group_id: diagnostic.diagnostic.group_id,
15042 blocks,
15043 });
15044 cx.notify();
15045 }
15046
15047 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15048 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15049 return;
15050 };
15051
15052 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15053 if let ActiveDiagnostic::Group(group) = prev {
15054 self.display_map.update(cx, |display_map, cx| {
15055 display_map.remove_blocks(group.blocks, cx);
15056 });
15057 cx.notify();
15058 }
15059 }
15060
15061 /// Disable inline diagnostics rendering for this editor.
15062 pub fn disable_inline_diagnostics(&mut self) {
15063 self.inline_diagnostics_enabled = false;
15064 self.inline_diagnostics_update = Task::ready(());
15065 self.inline_diagnostics.clear();
15066 }
15067
15068 pub fn inline_diagnostics_enabled(&self) -> bool {
15069 self.inline_diagnostics_enabled
15070 }
15071
15072 pub fn show_inline_diagnostics(&self) -> bool {
15073 self.show_inline_diagnostics
15074 }
15075
15076 pub fn toggle_inline_diagnostics(
15077 &mut self,
15078 _: &ToggleInlineDiagnostics,
15079 window: &mut Window,
15080 cx: &mut Context<Editor>,
15081 ) {
15082 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15083 self.refresh_inline_diagnostics(false, window, cx);
15084 }
15085
15086 fn refresh_inline_diagnostics(
15087 &mut self,
15088 debounce: bool,
15089 window: &mut Window,
15090 cx: &mut Context<Self>,
15091 ) {
15092 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
15093 self.inline_diagnostics_update = Task::ready(());
15094 self.inline_diagnostics.clear();
15095 return;
15096 }
15097
15098 let debounce_ms = ProjectSettings::get_global(cx)
15099 .diagnostics
15100 .inline
15101 .update_debounce_ms;
15102 let debounce = if debounce && debounce_ms > 0 {
15103 Some(Duration::from_millis(debounce_ms))
15104 } else {
15105 None
15106 };
15107 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15108 let editor = editor.upgrade().unwrap();
15109
15110 if let Some(debounce) = debounce {
15111 cx.background_executor().timer(debounce).await;
15112 }
15113 let Some(snapshot) = editor
15114 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15115 .ok()
15116 else {
15117 return;
15118 };
15119
15120 let new_inline_diagnostics = cx
15121 .background_spawn(async move {
15122 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15123 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15124 let message = diagnostic_entry
15125 .diagnostic
15126 .message
15127 .split_once('\n')
15128 .map(|(line, _)| line)
15129 .map(SharedString::new)
15130 .unwrap_or_else(|| {
15131 SharedString::from(diagnostic_entry.diagnostic.message)
15132 });
15133 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15134 let (Ok(i) | Err(i)) = inline_diagnostics
15135 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15136 inline_diagnostics.insert(
15137 i,
15138 (
15139 start_anchor,
15140 InlineDiagnostic {
15141 message,
15142 group_id: diagnostic_entry.diagnostic.group_id,
15143 start: diagnostic_entry.range.start.to_point(&snapshot),
15144 is_primary: diagnostic_entry.diagnostic.is_primary,
15145 severity: diagnostic_entry.diagnostic.severity,
15146 },
15147 ),
15148 );
15149 }
15150 inline_diagnostics
15151 })
15152 .await;
15153
15154 editor
15155 .update(cx, |editor, cx| {
15156 editor.inline_diagnostics = new_inline_diagnostics;
15157 cx.notify();
15158 })
15159 .ok();
15160 });
15161 }
15162
15163 pub fn set_selections_from_remote(
15164 &mut self,
15165 selections: Vec<Selection<Anchor>>,
15166 pending_selection: Option<Selection<Anchor>>,
15167 window: &mut Window,
15168 cx: &mut Context<Self>,
15169 ) {
15170 let old_cursor_position = self.selections.newest_anchor().head();
15171 self.selections.change_with(cx, |s| {
15172 s.select_anchors(selections);
15173 if let Some(pending_selection) = pending_selection {
15174 s.set_pending(pending_selection, SelectMode::Character);
15175 } else {
15176 s.clear_pending();
15177 }
15178 });
15179 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15180 }
15181
15182 fn push_to_selection_history(&mut self) {
15183 self.selection_history.push(SelectionHistoryEntry {
15184 selections: self.selections.disjoint_anchors(),
15185 select_next_state: self.select_next_state.clone(),
15186 select_prev_state: self.select_prev_state.clone(),
15187 add_selections_state: self.add_selections_state.clone(),
15188 });
15189 }
15190
15191 pub fn transact(
15192 &mut self,
15193 window: &mut Window,
15194 cx: &mut Context<Self>,
15195 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15196 ) -> Option<TransactionId> {
15197 self.start_transaction_at(Instant::now(), window, cx);
15198 update(self, window, cx);
15199 self.end_transaction_at(Instant::now(), cx)
15200 }
15201
15202 pub fn start_transaction_at(
15203 &mut self,
15204 now: Instant,
15205 window: &mut Window,
15206 cx: &mut Context<Self>,
15207 ) {
15208 self.end_selection(window, cx);
15209 if let Some(tx_id) = self
15210 .buffer
15211 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15212 {
15213 self.selection_history
15214 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15215 cx.emit(EditorEvent::TransactionBegun {
15216 transaction_id: tx_id,
15217 })
15218 }
15219 }
15220
15221 pub fn end_transaction_at(
15222 &mut self,
15223 now: Instant,
15224 cx: &mut Context<Self>,
15225 ) -> Option<TransactionId> {
15226 if let Some(transaction_id) = self
15227 .buffer
15228 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15229 {
15230 if let Some((_, end_selections)) =
15231 self.selection_history.transaction_mut(transaction_id)
15232 {
15233 *end_selections = Some(self.selections.disjoint_anchors());
15234 } else {
15235 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15236 }
15237
15238 cx.emit(EditorEvent::Edited { transaction_id });
15239 Some(transaction_id)
15240 } else {
15241 None
15242 }
15243 }
15244
15245 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15246 if self.selection_mark_mode {
15247 self.change_selections(None, window, cx, |s| {
15248 s.move_with(|_, sel| {
15249 sel.collapse_to(sel.head(), SelectionGoal::None);
15250 });
15251 })
15252 }
15253 self.selection_mark_mode = true;
15254 cx.notify();
15255 }
15256
15257 pub fn swap_selection_ends(
15258 &mut self,
15259 _: &actions::SwapSelectionEnds,
15260 window: &mut Window,
15261 cx: &mut Context<Self>,
15262 ) {
15263 self.change_selections(None, window, cx, |s| {
15264 s.move_with(|_, sel| {
15265 if sel.start != sel.end {
15266 sel.reversed = !sel.reversed
15267 }
15268 });
15269 });
15270 self.request_autoscroll(Autoscroll::newest(), cx);
15271 cx.notify();
15272 }
15273
15274 pub fn toggle_fold(
15275 &mut self,
15276 _: &actions::ToggleFold,
15277 window: &mut Window,
15278 cx: &mut Context<Self>,
15279 ) {
15280 if self.is_singleton(cx) {
15281 let selection = self.selections.newest::<Point>(cx);
15282
15283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15284 let range = if selection.is_empty() {
15285 let point = selection.head().to_display_point(&display_map);
15286 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15287 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15288 .to_point(&display_map);
15289 start..end
15290 } else {
15291 selection.range()
15292 };
15293 if display_map.folds_in_range(range).next().is_some() {
15294 self.unfold_lines(&Default::default(), window, cx)
15295 } else {
15296 self.fold(&Default::default(), window, cx)
15297 }
15298 } else {
15299 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15300 let buffer_ids: HashSet<_> = self
15301 .selections
15302 .disjoint_anchor_ranges()
15303 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15304 .collect();
15305
15306 let should_unfold = buffer_ids
15307 .iter()
15308 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15309
15310 for buffer_id in buffer_ids {
15311 if should_unfold {
15312 self.unfold_buffer(buffer_id, cx);
15313 } else {
15314 self.fold_buffer(buffer_id, cx);
15315 }
15316 }
15317 }
15318 }
15319
15320 pub fn toggle_fold_recursive(
15321 &mut self,
15322 _: &actions::ToggleFoldRecursive,
15323 window: &mut Window,
15324 cx: &mut Context<Self>,
15325 ) {
15326 let selection = self.selections.newest::<Point>(cx);
15327
15328 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15329 let range = if selection.is_empty() {
15330 let point = selection.head().to_display_point(&display_map);
15331 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15332 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15333 .to_point(&display_map);
15334 start..end
15335 } else {
15336 selection.range()
15337 };
15338 if display_map.folds_in_range(range).next().is_some() {
15339 self.unfold_recursive(&Default::default(), window, cx)
15340 } else {
15341 self.fold_recursive(&Default::default(), window, cx)
15342 }
15343 }
15344
15345 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15346 if self.is_singleton(cx) {
15347 let mut to_fold = Vec::new();
15348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15349 let selections = self.selections.all_adjusted(cx);
15350
15351 for selection in selections {
15352 let range = selection.range().sorted();
15353 let buffer_start_row = range.start.row;
15354
15355 if range.start.row != range.end.row {
15356 let mut found = false;
15357 let mut row = range.start.row;
15358 while row <= range.end.row {
15359 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15360 {
15361 found = true;
15362 row = crease.range().end.row + 1;
15363 to_fold.push(crease);
15364 } else {
15365 row += 1
15366 }
15367 }
15368 if found {
15369 continue;
15370 }
15371 }
15372
15373 for row in (0..=range.start.row).rev() {
15374 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15375 if crease.range().end.row >= buffer_start_row {
15376 to_fold.push(crease);
15377 if row <= range.start.row {
15378 break;
15379 }
15380 }
15381 }
15382 }
15383 }
15384
15385 self.fold_creases(to_fold, true, window, cx);
15386 } else {
15387 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15388 let buffer_ids = self
15389 .selections
15390 .disjoint_anchor_ranges()
15391 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15392 .collect::<HashSet<_>>();
15393 for buffer_id in buffer_ids {
15394 self.fold_buffer(buffer_id, cx);
15395 }
15396 }
15397 }
15398
15399 fn fold_at_level(
15400 &mut self,
15401 fold_at: &FoldAtLevel,
15402 window: &mut Window,
15403 cx: &mut Context<Self>,
15404 ) {
15405 if !self.buffer.read(cx).is_singleton() {
15406 return;
15407 }
15408
15409 let fold_at_level = fold_at.0;
15410 let snapshot = self.buffer.read(cx).snapshot(cx);
15411 let mut to_fold = Vec::new();
15412 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15413
15414 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15415 while start_row < end_row {
15416 match self
15417 .snapshot(window, cx)
15418 .crease_for_buffer_row(MultiBufferRow(start_row))
15419 {
15420 Some(crease) => {
15421 let nested_start_row = crease.range().start.row + 1;
15422 let nested_end_row = crease.range().end.row;
15423
15424 if current_level < fold_at_level {
15425 stack.push((nested_start_row, nested_end_row, current_level + 1));
15426 } else if current_level == fold_at_level {
15427 to_fold.push(crease);
15428 }
15429
15430 start_row = nested_end_row + 1;
15431 }
15432 None => start_row += 1,
15433 }
15434 }
15435 }
15436
15437 self.fold_creases(to_fold, true, window, cx);
15438 }
15439
15440 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15441 if self.buffer.read(cx).is_singleton() {
15442 let mut fold_ranges = Vec::new();
15443 let snapshot = self.buffer.read(cx).snapshot(cx);
15444
15445 for row in 0..snapshot.max_row().0 {
15446 if let Some(foldable_range) = self
15447 .snapshot(window, cx)
15448 .crease_for_buffer_row(MultiBufferRow(row))
15449 {
15450 fold_ranges.push(foldable_range);
15451 }
15452 }
15453
15454 self.fold_creases(fold_ranges, true, window, cx);
15455 } else {
15456 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15457 editor
15458 .update_in(cx, |editor, _, cx| {
15459 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15460 editor.fold_buffer(buffer_id, cx);
15461 }
15462 })
15463 .ok();
15464 });
15465 }
15466 }
15467
15468 pub fn fold_function_bodies(
15469 &mut self,
15470 _: &actions::FoldFunctionBodies,
15471 window: &mut Window,
15472 cx: &mut Context<Self>,
15473 ) {
15474 let snapshot = self.buffer.read(cx).snapshot(cx);
15475
15476 let ranges = snapshot
15477 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15478 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15479 .collect::<Vec<_>>();
15480
15481 let creases = ranges
15482 .into_iter()
15483 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15484 .collect();
15485
15486 self.fold_creases(creases, true, window, cx);
15487 }
15488
15489 pub fn fold_recursive(
15490 &mut self,
15491 _: &actions::FoldRecursive,
15492 window: &mut Window,
15493 cx: &mut Context<Self>,
15494 ) {
15495 let mut to_fold = Vec::new();
15496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15497 let selections = self.selections.all_adjusted(cx);
15498
15499 for selection in selections {
15500 let range = selection.range().sorted();
15501 let buffer_start_row = range.start.row;
15502
15503 if range.start.row != range.end.row {
15504 let mut found = false;
15505 for row in range.start.row..=range.end.row {
15506 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15507 found = true;
15508 to_fold.push(crease);
15509 }
15510 }
15511 if found {
15512 continue;
15513 }
15514 }
15515
15516 for row in (0..=range.start.row).rev() {
15517 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15518 if crease.range().end.row >= buffer_start_row {
15519 to_fold.push(crease);
15520 } else {
15521 break;
15522 }
15523 }
15524 }
15525 }
15526
15527 self.fold_creases(to_fold, true, window, cx);
15528 }
15529
15530 pub fn fold_at(
15531 &mut self,
15532 buffer_row: MultiBufferRow,
15533 window: &mut Window,
15534 cx: &mut Context<Self>,
15535 ) {
15536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15537
15538 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15539 let autoscroll = self
15540 .selections
15541 .all::<Point>(cx)
15542 .iter()
15543 .any(|selection| crease.range().overlaps(&selection.range()));
15544
15545 self.fold_creases(vec![crease], autoscroll, window, cx);
15546 }
15547 }
15548
15549 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15550 if self.is_singleton(cx) {
15551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15552 let buffer = &display_map.buffer_snapshot;
15553 let selections = self.selections.all::<Point>(cx);
15554 let ranges = selections
15555 .iter()
15556 .map(|s| {
15557 let range = s.display_range(&display_map).sorted();
15558 let mut start = range.start.to_point(&display_map);
15559 let mut end = range.end.to_point(&display_map);
15560 start.column = 0;
15561 end.column = buffer.line_len(MultiBufferRow(end.row));
15562 start..end
15563 })
15564 .collect::<Vec<_>>();
15565
15566 self.unfold_ranges(&ranges, true, true, cx);
15567 } else {
15568 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15569 let buffer_ids = self
15570 .selections
15571 .disjoint_anchor_ranges()
15572 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15573 .collect::<HashSet<_>>();
15574 for buffer_id in buffer_ids {
15575 self.unfold_buffer(buffer_id, cx);
15576 }
15577 }
15578 }
15579
15580 pub fn unfold_recursive(
15581 &mut self,
15582 _: &UnfoldRecursive,
15583 _window: &mut Window,
15584 cx: &mut Context<Self>,
15585 ) {
15586 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15587 let selections = self.selections.all::<Point>(cx);
15588 let ranges = selections
15589 .iter()
15590 .map(|s| {
15591 let mut range = s.display_range(&display_map).sorted();
15592 *range.start.column_mut() = 0;
15593 *range.end.column_mut() = display_map.line_len(range.end.row());
15594 let start = range.start.to_point(&display_map);
15595 let end = range.end.to_point(&display_map);
15596 start..end
15597 })
15598 .collect::<Vec<_>>();
15599
15600 self.unfold_ranges(&ranges, true, true, cx);
15601 }
15602
15603 pub fn unfold_at(
15604 &mut self,
15605 buffer_row: MultiBufferRow,
15606 _window: &mut Window,
15607 cx: &mut Context<Self>,
15608 ) {
15609 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15610
15611 let intersection_range = Point::new(buffer_row.0, 0)
15612 ..Point::new(
15613 buffer_row.0,
15614 display_map.buffer_snapshot.line_len(buffer_row),
15615 );
15616
15617 let autoscroll = self
15618 .selections
15619 .all::<Point>(cx)
15620 .iter()
15621 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15622
15623 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15624 }
15625
15626 pub fn unfold_all(
15627 &mut self,
15628 _: &actions::UnfoldAll,
15629 _window: &mut Window,
15630 cx: &mut Context<Self>,
15631 ) {
15632 if self.buffer.read(cx).is_singleton() {
15633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15634 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15635 } else {
15636 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15637 editor
15638 .update(cx, |editor, cx| {
15639 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15640 editor.unfold_buffer(buffer_id, cx);
15641 }
15642 })
15643 .ok();
15644 });
15645 }
15646 }
15647
15648 pub fn fold_selected_ranges(
15649 &mut self,
15650 _: &FoldSelectedRanges,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) {
15654 let selections = self.selections.all_adjusted(cx);
15655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15656 let ranges = selections
15657 .into_iter()
15658 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15659 .collect::<Vec<_>>();
15660 self.fold_creases(ranges, true, window, cx);
15661 }
15662
15663 pub fn fold_ranges<T: ToOffset + Clone>(
15664 &mut self,
15665 ranges: Vec<Range<T>>,
15666 auto_scroll: bool,
15667 window: &mut Window,
15668 cx: &mut Context<Self>,
15669 ) {
15670 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15671 let ranges = ranges
15672 .into_iter()
15673 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15674 .collect::<Vec<_>>();
15675 self.fold_creases(ranges, auto_scroll, window, cx);
15676 }
15677
15678 pub fn fold_creases<T: ToOffset + Clone>(
15679 &mut self,
15680 creases: Vec<Crease<T>>,
15681 auto_scroll: bool,
15682 _window: &mut Window,
15683 cx: &mut Context<Self>,
15684 ) {
15685 if creases.is_empty() {
15686 return;
15687 }
15688
15689 let mut buffers_affected = HashSet::default();
15690 let multi_buffer = self.buffer().read(cx);
15691 for crease in &creases {
15692 if let Some((_, buffer, _)) =
15693 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15694 {
15695 buffers_affected.insert(buffer.read(cx).remote_id());
15696 };
15697 }
15698
15699 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15700
15701 if auto_scroll {
15702 self.request_autoscroll(Autoscroll::fit(), cx);
15703 }
15704
15705 cx.notify();
15706
15707 self.scrollbar_marker_state.dirty = true;
15708 self.folds_did_change(cx);
15709 }
15710
15711 /// Removes any folds whose ranges intersect any of the given ranges.
15712 pub fn unfold_ranges<T: ToOffset + Clone>(
15713 &mut self,
15714 ranges: &[Range<T>],
15715 inclusive: bool,
15716 auto_scroll: bool,
15717 cx: &mut Context<Self>,
15718 ) {
15719 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15720 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15721 });
15722 self.folds_did_change(cx);
15723 }
15724
15725 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15726 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15727 return;
15728 }
15729 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15730 self.display_map.update(cx, |display_map, cx| {
15731 display_map.fold_buffers([buffer_id], cx)
15732 });
15733 cx.emit(EditorEvent::BufferFoldToggled {
15734 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15735 folded: true,
15736 });
15737 cx.notify();
15738 }
15739
15740 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15741 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15742 return;
15743 }
15744 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15745 self.display_map.update(cx, |display_map, cx| {
15746 display_map.unfold_buffers([buffer_id], cx);
15747 });
15748 cx.emit(EditorEvent::BufferFoldToggled {
15749 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15750 folded: false,
15751 });
15752 cx.notify();
15753 }
15754
15755 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15756 self.display_map.read(cx).is_buffer_folded(buffer)
15757 }
15758
15759 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15760 self.display_map.read(cx).folded_buffers()
15761 }
15762
15763 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15764 self.display_map.update(cx, |display_map, cx| {
15765 display_map.disable_header_for_buffer(buffer_id, cx);
15766 });
15767 cx.notify();
15768 }
15769
15770 /// Removes any folds with the given ranges.
15771 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15772 &mut self,
15773 ranges: &[Range<T>],
15774 type_id: TypeId,
15775 auto_scroll: bool,
15776 cx: &mut Context<Self>,
15777 ) {
15778 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15779 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15780 });
15781 self.folds_did_change(cx);
15782 }
15783
15784 fn remove_folds_with<T: ToOffset + Clone>(
15785 &mut self,
15786 ranges: &[Range<T>],
15787 auto_scroll: bool,
15788 cx: &mut Context<Self>,
15789 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15790 ) {
15791 if ranges.is_empty() {
15792 return;
15793 }
15794
15795 let mut buffers_affected = HashSet::default();
15796 let multi_buffer = self.buffer().read(cx);
15797 for range in ranges {
15798 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15799 buffers_affected.insert(buffer.read(cx).remote_id());
15800 };
15801 }
15802
15803 self.display_map.update(cx, update);
15804
15805 if auto_scroll {
15806 self.request_autoscroll(Autoscroll::fit(), cx);
15807 }
15808
15809 cx.notify();
15810 self.scrollbar_marker_state.dirty = true;
15811 self.active_indent_guides_state.dirty = true;
15812 }
15813
15814 pub fn update_fold_widths(
15815 &mut self,
15816 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15817 cx: &mut Context<Self>,
15818 ) -> bool {
15819 self.display_map
15820 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15821 }
15822
15823 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15824 self.display_map.read(cx).fold_placeholder.clone()
15825 }
15826
15827 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15828 self.buffer.update(cx, |buffer, cx| {
15829 buffer.set_all_diff_hunks_expanded(cx);
15830 });
15831 }
15832
15833 pub fn expand_all_diff_hunks(
15834 &mut self,
15835 _: &ExpandAllDiffHunks,
15836 _window: &mut Window,
15837 cx: &mut Context<Self>,
15838 ) {
15839 self.buffer.update(cx, |buffer, cx| {
15840 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15841 });
15842 }
15843
15844 pub fn toggle_selected_diff_hunks(
15845 &mut self,
15846 _: &ToggleSelectedDiffHunks,
15847 _window: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15851 self.toggle_diff_hunks_in_ranges(ranges, cx);
15852 }
15853
15854 pub fn diff_hunks_in_ranges<'a>(
15855 &'a self,
15856 ranges: &'a [Range<Anchor>],
15857 buffer: &'a MultiBufferSnapshot,
15858 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15859 ranges.iter().flat_map(move |range| {
15860 let end_excerpt_id = range.end.excerpt_id;
15861 let range = range.to_point(buffer);
15862 let mut peek_end = range.end;
15863 if range.end.row < buffer.max_row().0 {
15864 peek_end = Point::new(range.end.row + 1, 0);
15865 }
15866 buffer
15867 .diff_hunks_in_range(range.start..peek_end)
15868 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15869 })
15870 }
15871
15872 pub fn has_stageable_diff_hunks_in_ranges(
15873 &self,
15874 ranges: &[Range<Anchor>],
15875 snapshot: &MultiBufferSnapshot,
15876 ) -> bool {
15877 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15878 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15879 }
15880
15881 pub fn toggle_staged_selected_diff_hunks(
15882 &mut self,
15883 _: &::git::ToggleStaged,
15884 _: &mut Window,
15885 cx: &mut Context<Self>,
15886 ) {
15887 let snapshot = self.buffer.read(cx).snapshot(cx);
15888 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15889 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15890 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15891 }
15892
15893 pub fn set_render_diff_hunk_controls(
15894 &mut self,
15895 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15896 cx: &mut Context<Self>,
15897 ) {
15898 self.render_diff_hunk_controls = render_diff_hunk_controls;
15899 cx.notify();
15900 }
15901
15902 pub fn stage_and_next(
15903 &mut self,
15904 _: &::git::StageAndNext,
15905 window: &mut Window,
15906 cx: &mut Context<Self>,
15907 ) {
15908 self.do_stage_or_unstage_and_next(true, window, cx);
15909 }
15910
15911 pub fn unstage_and_next(
15912 &mut self,
15913 _: &::git::UnstageAndNext,
15914 window: &mut Window,
15915 cx: &mut Context<Self>,
15916 ) {
15917 self.do_stage_or_unstage_and_next(false, window, cx);
15918 }
15919
15920 pub fn stage_or_unstage_diff_hunks(
15921 &mut self,
15922 stage: bool,
15923 ranges: Vec<Range<Anchor>>,
15924 cx: &mut Context<Self>,
15925 ) {
15926 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15927 cx.spawn(async move |this, cx| {
15928 task.await?;
15929 this.update(cx, |this, cx| {
15930 let snapshot = this.buffer.read(cx).snapshot(cx);
15931 let chunk_by = this
15932 .diff_hunks_in_ranges(&ranges, &snapshot)
15933 .chunk_by(|hunk| hunk.buffer_id);
15934 for (buffer_id, hunks) in &chunk_by {
15935 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15936 }
15937 })
15938 })
15939 .detach_and_log_err(cx);
15940 }
15941
15942 fn save_buffers_for_ranges_if_needed(
15943 &mut self,
15944 ranges: &[Range<Anchor>],
15945 cx: &mut Context<Editor>,
15946 ) -> Task<Result<()>> {
15947 let multibuffer = self.buffer.read(cx);
15948 let snapshot = multibuffer.read(cx);
15949 let buffer_ids: HashSet<_> = ranges
15950 .iter()
15951 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15952 .collect();
15953 drop(snapshot);
15954
15955 let mut buffers = HashSet::default();
15956 for buffer_id in buffer_ids {
15957 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15958 let buffer = buffer_entity.read(cx);
15959 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15960 {
15961 buffers.insert(buffer_entity);
15962 }
15963 }
15964 }
15965
15966 if let Some(project) = &self.project {
15967 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15968 } else {
15969 Task::ready(Ok(()))
15970 }
15971 }
15972
15973 fn do_stage_or_unstage_and_next(
15974 &mut self,
15975 stage: bool,
15976 window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) {
15979 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15980
15981 if ranges.iter().any(|range| range.start != range.end) {
15982 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15983 return;
15984 }
15985
15986 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15987 let snapshot = self.snapshot(window, cx);
15988 let position = self.selections.newest::<Point>(cx).head();
15989 let mut row = snapshot
15990 .buffer_snapshot
15991 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15992 .find(|hunk| hunk.row_range.start.0 > position.row)
15993 .map(|hunk| hunk.row_range.start);
15994
15995 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15996 // Outside of the project diff editor, wrap around to the beginning.
15997 if !all_diff_hunks_expanded {
15998 row = row.or_else(|| {
15999 snapshot
16000 .buffer_snapshot
16001 .diff_hunks_in_range(Point::zero()..position)
16002 .find(|hunk| hunk.row_range.end.0 < position.row)
16003 .map(|hunk| hunk.row_range.start)
16004 });
16005 }
16006
16007 if let Some(row) = row {
16008 let destination = Point::new(row.0, 0);
16009 let autoscroll = Autoscroll::center();
16010
16011 self.unfold_ranges(&[destination..destination], false, false, cx);
16012 self.change_selections(Some(autoscroll), window, cx, |s| {
16013 s.select_ranges([destination..destination]);
16014 });
16015 }
16016 }
16017
16018 fn do_stage_or_unstage(
16019 &self,
16020 stage: bool,
16021 buffer_id: BufferId,
16022 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16023 cx: &mut App,
16024 ) -> Option<()> {
16025 let project = self.project.as_ref()?;
16026 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16027 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16028 let buffer_snapshot = buffer.read(cx).snapshot();
16029 let file_exists = buffer_snapshot
16030 .file()
16031 .is_some_and(|file| file.disk_state().exists());
16032 diff.update(cx, |diff, cx| {
16033 diff.stage_or_unstage_hunks(
16034 stage,
16035 &hunks
16036 .map(|hunk| buffer_diff::DiffHunk {
16037 buffer_range: hunk.buffer_range,
16038 diff_base_byte_range: hunk.diff_base_byte_range,
16039 secondary_status: hunk.secondary_status,
16040 range: Point::zero()..Point::zero(), // unused
16041 })
16042 .collect::<Vec<_>>(),
16043 &buffer_snapshot,
16044 file_exists,
16045 cx,
16046 )
16047 });
16048 None
16049 }
16050
16051 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16052 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16053 self.buffer
16054 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16055 }
16056
16057 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16058 self.buffer.update(cx, |buffer, cx| {
16059 let ranges = vec![Anchor::min()..Anchor::max()];
16060 if !buffer.all_diff_hunks_expanded()
16061 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16062 {
16063 buffer.collapse_diff_hunks(ranges, cx);
16064 true
16065 } else {
16066 false
16067 }
16068 })
16069 }
16070
16071 fn toggle_diff_hunks_in_ranges(
16072 &mut self,
16073 ranges: Vec<Range<Anchor>>,
16074 cx: &mut Context<Editor>,
16075 ) {
16076 self.buffer.update(cx, |buffer, cx| {
16077 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16078 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16079 })
16080 }
16081
16082 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16083 self.buffer.update(cx, |buffer, cx| {
16084 let snapshot = buffer.snapshot(cx);
16085 let excerpt_id = range.end.excerpt_id;
16086 let point_range = range.to_point(&snapshot);
16087 let expand = !buffer.single_hunk_is_expanded(range, cx);
16088 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16089 })
16090 }
16091
16092 pub(crate) fn apply_all_diff_hunks(
16093 &mut self,
16094 _: &ApplyAllDiffHunks,
16095 window: &mut Window,
16096 cx: &mut Context<Self>,
16097 ) {
16098 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16099
16100 let buffers = self.buffer.read(cx).all_buffers();
16101 for branch_buffer in buffers {
16102 branch_buffer.update(cx, |branch_buffer, cx| {
16103 branch_buffer.merge_into_base(Vec::new(), cx);
16104 });
16105 }
16106
16107 if let Some(project) = self.project.clone() {
16108 self.save(true, project, window, cx).detach_and_log_err(cx);
16109 }
16110 }
16111
16112 pub(crate) fn apply_selected_diff_hunks(
16113 &mut self,
16114 _: &ApplyDiffHunk,
16115 window: &mut Window,
16116 cx: &mut Context<Self>,
16117 ) {
16118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16119 let snapshot = self.snapshot(window, cx);
16120 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16121 let mut ranges_by_buffer = HashMap::default();
16122 self.transact(window, cx, |editor, _window, cx| {
16123 for hunk in hunks {
16124 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16125 ranges_by_buffer
16126 .entry(buffer.clone())
16127 .or_insert_with(Vec::new)
16128 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16129 }
16130 }
16131
16132 for (buffer, ranges) in ranges_by_buffer {
16133 buffer.update(cx, |buffer, cx| {
16134 buffer.merge_into_base(ranges, cx);
16135 });
16136 }
16137 });
16138
16139 if let Some(project) = self.project.clone() {
16140 self.save(true, project, window, cx).detach_and_log_err(cx);
16141 }
16142 }
16143
16144 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16145 if hovered != self.gutter_hovered {
16146 self.gutter_hovered = hovered;
16147 cx.notify();
16148 }
16149 }
16150
16151 pub fn insert_blocks(
16152 &mut self,
16153 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16154 autoscroll: Option<Autoscroll>,
16155 cx: &mut Context<Self>,
16156 ) -> Vec<CustomBlockId> {
16157 let blocks = self
16158 .display_map
16159 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16160 if let Some(autoscroll) = autoscroll {
16161 self.request_autoscroll(autoscroll, cx);
16162 }
16163 cx.notify();
16164 blocks
16165 }
16166
16167 pub fn resize_blocks(
16168 &mut self,
16169 heights: HashMap<CustomBlockId, u32>,
16170 autoscroll: Option<Autoscroll>,
16171 cx: &mut Context<Self>,
16172 ) {
16173 self.display_map
16174 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16175 if let Some(autoscroll) = autoscroll {
16176 self.request_autoscroll(autoscroll, cx);
16177 }
16178 cx.notify();
16179 }
16180
16181 pub fn replace_blocks(
16182 &mut self,
16183 renderers: HashMap<CustomBlockId, RenderBlock>,
16184 autoscroll: Option<Autoscroll>,
16185 cx: &mut Context<Self>,
16186 ) {
16187 self.display_map
16188 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16189 if let Some(autoscroll) = autoscroll {
16190 self.request_autoscroll(autoscroll, cx);
16191 }
16192 cx.notify();
16193 }
16194
16195 pub fn remove_blocks(
16196 &mut self,
16197 block_ids: HashSet<CustomBlockId>,
16198 autoscroll: Option<Autoscroll>,
16199 cx: &mut Context<Self>,
16200 ) {
16201 self.display_map.update(cx, |display_map, cx| {
16202 display_map.remove_blocks(block_ids, cx)
16203 });
16204 if let Some(autoscroll) = autoscroll {
16205 self.request_autoscroll(autoscroll, cx);
16206 }
16207 cx.notify();
16208 }
16209
16210 pub fn row_for_block(
16211 &self,
16212 block_id: CustomBlockId,
16213 cx: &mut Context<Self>,
16214 ) -> Option<DisplayRow> {
16215 self.display_map
16216 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16217 }
16218
16219 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16220 self.focused_block = Some(focused_block);
16221 }
16222
16223 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16224 self.focused_block.take()
16225 }
16226
16227 pub fn insert_creases(
16228 &mut self,
16229 creases: impl IntoIterator<Item = Crease<Anchor>>,
16230 cx: &mut Context<Self>,
16231 ) -> Vec<CreaseId> {
16232 self.display_map
16233 .update(cx, |map, cx| map.insert_creases(creases, cx))
16234 }
16235
16236 pub fn remove_creases(
16237 &mut self,
16238 ids: impl IntoIterator<Item = CreaseId>,
16239 cx: &mut Context<Self>,
16240 ) {
16241 self.display_map
16242 .update(cx, |map, cx| map.remove_creases(ids, cx));
16243 }
16244
16245 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16246 self.display_map
16247 .update(cx, |map, cx| map.snapshot(cx))
16248 .longest_row()
16249 }
16250
16251 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16252 self.display_map
16253 .update(cx, |map, cx| map.snapshot(cx))
16254 .max_point()
16255 }
16256
16257 pub fn text(&self, cx: &App) -> String {
16258 self.buffer.read(cx).read(cx).text()
16259 }
16260
16261 pub fn is_empty(&self, cx: &App) -> bool {
16262 self.buffer.read(cx).read(cx).is_empty()
16263 }
16264
16265 pub fn text_option(&self, cx: &App) -> Option<String> {
16266 let text = self.text(cx);
16267 let text = text.trim();
16268
16269 if text.is_empty() {
16270 return None;
16271 }
16272
16273 Some(text.to_string())
16274 }
16275
16276 pub fn set_text(
16277 &mut self,
16278 text: impl Into<Arc<str>>,
16279 window: &mut Window,
16280 cx: &mut Context<Self>,
16281 ) {
16282 self.transact(window, cx, |this, _, cx| {
16283 this.buffer
16284 .read(cx)
16285 .as_singleton()
16286 .expect("you can only call set_text on editors for singleton buffers")
16287 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16288 });
16289 }
16290
16291 pub fn display_text(&self, cx: &mut App) -> String {
16292 self.display_map
16293 .update(cx, |map, cx| map.snapshot(cx))
16294 .text()
16295 }
16296
16297 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16298 let mut wrap_guides = smallvec::smallvec![];
16299
16300 if self.show_wrap_guides == Some(false) {
16301 return wrap_guides;
16302 }
16303
16304 let settings = self.buffer.read(cx).language_settings(cx);
16305 if settings.show_wrap_guides {
16306 match self.soft_wrap_mode(cx) {
16307 SoftWrap::Column(soft_wrap) => {
16308 wrap_guides.push((soft_wrap as usize, true));
16309 }
16310 SoftWrap::Bounded(soft_wrap) => {
16311 wrap_guides.push((soft_wrap as usize, true));
16312 }
16313 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16314 }
16315 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16316 }
16317
16318 wrap_guides
16319 }
16320
16321 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16322 let settings = self.buffer.read(cx).language_settings(cx);
16323 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16324 match mode {
16325 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16326 SoftWrap::None
16327 }
16328 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16329 language_settings::SoftWrap::PreferredLineLength => {
16330 SoftWrap::Column(settings.preferred_line_length)
16331 }
16332 language_settings::SoftWrap::Bounded => {
16333 SoftWrap::Bounded(settings.preferred_line_length)
16334 }
16335 }
16336 }
16337
16338 pub fn set_soft_wrap_mode(
16339 &mut self,
16340 mode: language_settings::SoftWrap,
16341
16342 cx: &mut Context<Self>,
16343 ) {
16344 self.soft_wrap_mode_override = Some(mode);
16345 cx.notify();
16346 }
16347
16348 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16349 self.hard_wrap = hard_wrap;
16350 cx.notify();
16351 }
16352
16353 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16354 self.text_style_refinement = Some(style);
16355 }
16356
16357 /// called by the Element so we know what style we were most recently rendered with.
16358 pub(crate) fn set_style(
16359 &mut self,
16360 style: EditorStyle,
16361 window: &mut Window,
16362 cx: &mut Context<Self>,
16363 ) {
16364 let rem_size = window.rem_size();
16365 self.display_map.update(cx, |map, cx| {
16366 map.set_font(
16367 style.text.font(),
16368 style.text.font_size.to_pixels(rem_size),
16369 cx,
16370 )
16371 });
16372 self.style = Some(style);
16373 }
16374
16375 pub fn style(&self) -> Option<&EditorStyle> {
16376 self.style.as_ref()
16377 }
16378
16379 // Called by the element. This method is not designed to be called outside of the editor
16380 // element's layout code because it does not notify when rewrapping is computed synchronously.
16381 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16382 self.display_map
16383 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16384 }
16385
16386 pub fn set_soft_wrap(&mut self) {
16387 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16388 }
16389
16390 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16391 if self.soft_wrap_mode_override.is_some() {
16392 self.soft_wrap_mode_override.take();
16393 } else {
16394 let soft_wrap = match self.soft_wrap_mode(cx) {
16395 SoftWrap::GitDiff => return,
16396 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16397 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16398 language_settings::SoftWrap::None
16399 }
16400 };
16401 self.soft_wrap_mode_override = Some(soft_wrap);
16402 }
16403 cx.notify();
16404 }
16405
16406 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16407 let Some(workspace) = self.workspace() else {
16408 return;
16409 };
16410 let fs = workspace.read(cx).app_state().fs.clone();
16411 let current_show = TabBarSettings::get_global(cx).show;
16412 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16413 setting.show = Some(!current_show);
16414 });
16415 }
16416
16417 pub fn toggle_indent_guides(
16418 &mut self,
16419 _: &ToggleIndentGuides,
16420 _: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) {
16423 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16424 self.buffer
16425 .read(cx)
16426 .language_settings(cx)
16427 .indent_guides
16428 .enabled
16429 });
16430 self.show_indent_guides = Some(!currently_enabled);
16431 cx.notify();
16432 }
16433
16434 fn should_show_indent_guides(&self) -> Option<bool> {
16435 self.show_indent_guides
16436 }
16437
16438 pub fn toggle_line_numbers(
16439 &mut self,
16440 _: &ToggleLineNumbers,
16441 _: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let mut editor_settings = EditorSettings::get_global(cx).clone();
16445 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16446 EditorSettings::override_global(editor_settings, cx);
16447 }
16448
16449 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16450 if let Some(show_line_numbers) = self.show_line_numbers {
16451 return show_line_numbers;
16452 }
16453 EditorSettings::get_global(cx).gutter.line_numbers
16454 }
16455
16456 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16457 self.use_relative_line_numbers
16458 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16459 }
16460
16461 pub fn toggle_relative_line_numbers(
16462 &mut self,
16463 _: &ToggleRelativeLineNumbers,
16464 _: &mut Window,
16465 cx: &mut Context<Self>,
16466 ) {
16467 let is_relative = self.should_use_relative_line_numbers(cx);
16468 self.set_relative_line_number(Some(!is_relative), cx)
16469 }
16470
16471 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16472 self.use_relative_line_numbers = is_relative;
16473 cx.notify();
16474 }
16475
16476 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16477 self.show_gutter = show_gutter;
16478 cx.notify();
16479 }
16480
16481 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16482 self.show_scrollbars = show_scrollbars;
16483 cx.notify();
16484 }
16485
16486 pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
16487 self.disable_scrolling = true;
16488 cx.notify();
16489 }
16490
16491 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16492 self.show_line_numbers = Some(show_line_numbers);
16493 cx.notify();
16494 }
16495
16496 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16497 self.disable_expand_excerpt_buttons = true;
16498 cx.notify();
16499 }
16500
16501 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16502 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16503 cx.notify();
16504 }
16505
16506 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16507 self.show_code_actions = Some(show_code_actions);
16508 cx.notify();
16509 }
16510
16511 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16512 self.show_runnables = Some(show_runnables);
16513 cx.notify();
16514 }
16515
16516 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16517 self.show_breakpoints = Some(show_breakpoints);
16518 cx.notify();
16519 }
16520
16521 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16522 if self.display_map.read(cx).masked != masked {
16523 self.display_map.update(cx, |map, _| map.masked = masked);
16524 }
16525 cx.notify()
16526 }
16527
16528 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16529 self.show_wrap_guides = Some(show_wrap_guides);
16530 cx.notify();
16531 }
16532
16533 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16534 self.show_indent_guides = Some(show_indent_guides);
16535 cx.notify();
16536 }
16537
16538 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16539 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16540 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16541 if let Some(dir) = file.abs_path(cx).parent() {
16542 return Some(dir.to_owned());
16543 }
16544 }
16545
16546 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16547 return Some(project_path.path.to_path_buf());
16548 }
16549 }
16550
16551 None
16552 }
16553
16554 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16555 self.active_excerpt(cx)?
16556 .1
16557 .read(cx)
16558 .file()
16559 .and_then(|f| f.as_local())
16560 }
16561
16562 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16563 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16564 let buffer = buffer.read(cx);
16565 if let Some(project_path) = buffer.project_path(cx) {
16566 let project = self.project.as_ref()?.read(cx);
16567 project.absolute_path(&project_path, cx)
16568 } else {
16569 buffer
16570 .file()
16571 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16572 }
16573 })
16574 }
16575
16576 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16577 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16578 let project_path = buffer.read(cx).project_path(cx)?;
16579 let project = self.project.as_ref()?.read(cx);
16580 let entry = project.entry_for_path(&project_path, cx)?;
16581 let path = entry.path.to_path_buf();
16582 Some(path)
16583 })
16584 }
16585
16586 pub fn reveal_in_finder(
16587 &mut self,
16588 _: &RevealInFileManager,
16589 _window: &mut Window,
16590 cx: &mut Context<Self>,
16591 ) {
16592 if let Some(target) = self.target_file(cx) {
16593 cx.reveal_path(&target.abs_path(cx));
16594 }
16595 }
16596
16597 pub fn copy_path(
16598 &mut self,
16599 _: &zed_actions::workspace::CopyPath,
16600 _window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 if let Some(path) = self.target_file_abs_path(cx) {
16604 if let Some(path) = path.to_str() {
16605 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16606 }
16607 }
16608 }
16609
16610 pub fn copy_relative_path(
16611 &mut self,
16612 _: &zed_actions::workspace::CopyRelativePath,
16613 _window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 if let Some(path) = self.target_file_path(cx) {
16617 if let Some(path) = path.to_str() {
16618 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16619 }
16620 }
16621 }
16622
16623 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16624 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16625 buffer.read(cx).project_path(cx)
16626 } else {
16627 None
16628 }
16629 }
16630
16631 // Returns true if the editor handled a go-to-line request
16632 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16633 maybe!({
16634 let breakpoint_store = self.breakpoint_store.as_ref()?;
16635
16636 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16637 else {
16638 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16639 return None;
16640 };
16641
16642 let position = active_stack_frame.position;
16643 let buffer_id = position.buffer_id?;
16644 let snapshot = self
16645 .project
16646 .as_ref()?
16647 .read(cx)
16648 .buffer_for_id(buffer_id, cx)?
16649 .read(cx)
16650 .snapshot();
16651
16652 let mut handled = false;
16653 for (id, ExcerptRange { context, .. }) in
16654 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16655 {
16656 if context.start.cmp(&position, &snapshot).is_ge()
16657 || context.end.cmp(&position, &snapshot).is_lt()
16658 {
16659 continue;
16660 }
16661 let snapshot = self.buffer.read(cx).snapshot(cx);
16662 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16663
16664 handled = true;
16665 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16666 self.go_to_line::<DebugCurrentRowHighlight>(
16667 multibuffer_anchor,
16668 Some(cx.theme().colors().editor_debugger_active_line_background),
16669 window,
16670 cx,
16671 );
16672
16673 cx.notify();
16674 }
16675
16676 handled.then_some(())
16677 })
16678 .is_some()
16679 }
16680
16681 pub fn copy_file_name_without_extension(
16682 &mut self,
16683 _: &CopyFileNameWithoutExtension,
16684 _: &mut Window,
16685 cx: &mut Context<Self>,
16686 ) {
16687 if let Some(file) = self.target_file(cx) {
16688 if let Some(file_stem) = file.path().file_stem() {
16689 if let Some(name) = file_stem.to_str() {
16690 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16691 }
16692 }
16693 }
16694 }
16695
16696 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16697 if let Some(file) = self.target_file(cx) {
16698 if let Some(file_name) = file.path().file_name() {
16699 if let Some(name) = file_name.to_str() {
16700 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16701 }
16702 }
16703 }
16704 }
16705
16706 pub fn toggle_git_blame(
16707 &mut self,
16708 _: &::git::Blame,
16709 window: &mut Window,
16710 cx: &mut Context<Self>,
16711 ) {
16712 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16713
16714 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16715 self.start_git_blame(true, window, cx);
16716 }
16717
16718 cx.notify();
16719 }
16720
16721 pub fn toggle_git_blame_inline(
16722 &mut self,
16723 _: &ToggleGitBlameInline,
16724 window: &mut Window,
16725 cx: &mut Context<Self>,
16726 ) {
16727 self.toggle_git_blame_inline_internal(true, window, cx);
16728 cx.notify();
16729 }
16730
16731 pub fn open_git_blame_commit(
16732 &mut self,
16733 _: &OpenGitBlameCommit,
16734 window: &mut Window,
16735 cx: &mut Context<Self>,
16736 ) {
16737 self.open_git_blame_commit_internal(window, cx);
16738 }
16739
16740 fn open_git_blame_commit_internal(
16741 &mut self,
16742 window: &mut Window,
16743 cx: &mut Context<Self>,
16744 ) -> Option<()> {
16745 let blame = self.blame.as_ref()?;
16746 let snapshot = self.snapshot(window, cx);
16747 let cursor = self.selections.newest::<Point>(cx).head();
16748 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16749 let blame_entry = blame
16750 .update(cx, |blame, cx| {
16751 blame
16752 .blame_for_rows(
16753 &[RowInfo {
16754 buffer_id: Some(buffer.remote_id()),
16755 buffer_row: Some(point.row),
16756 ..Default::default()
16757 }],
16758 cx,
16759 )
16760 .next()
16761 })
16762 .flatten()?;
16763 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16764 let repo = blame.read(cx).repository(cx)?;
16765 let workspace = self.workspace()?.downgrade();
16766 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16767 None
16768 }
16769
16770 pub fn git_blame_inline_enabled(&self) -> bool {
16771 self.git_blame_inline_enabled
16772 }
16773
16774 pub fn toggle_selection_menu(
16775 &mut self,
16776 _: &ToggleSelectionMenu,
16777 _: &mut Window,
16778 cx: &mut Context<Self>,
16779 ) {
16780 self.show_selection_menu = self
16781 .show_selection_menu
16782 .map(|show_selections_menu| !show_selections_menu)
16783 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16784
16785 cx.notify();
16786 }
16787
16788 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16789 self.show_selection_menu
16790 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16791 }
16792
16793 fn start_git_blame(
16794 &mut self,
16795 user_triggered: bool,
16796 window: &mut Window,
16797 cx: &mut Context<Self>,
16798 ) {
16799 if let Some(project) = self.project.as_ref() {
16800 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16801 return;
16802 };
16803
16804 if buffer.read(cx).file().is_none() {
16805 return;
16806 }
16807
16808 let focused = self.focus_handle(cx).contains_focused(window, cx);
16809
16810 let project = project.clone();
16811 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16812 self.blame_subscription =
16813 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16814 self.blame = Some(blame);
16815 }
16816 }
16817
16818 fn toggle_git_blame_inline_internal(
16819 &mut self,
16820 user_triggered: bool,
16821 window: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) {
16824 if self.git_blame_inline_enabled {
16825 self.git_blame_inline_enabled = false;
16826 self.show_git_blame_inline = false;
16827 self.show_git_blame_inline_delay_task.take();
16828 } else {
16829 self.git_blame_inline_enabled = true;
16830 self.start_git_blame_inline(user_triggered, window, cx);
16831 }
16832
16833 cx.notify();
16834 }
16835
16836 fn start_git_blame_inline(
16837 &mut self,
16838 user_triggered: bool,
16839 window: &mut Window,
16840 cx: &mut Context<Self>,
16841 ) {
16842 self.start_git_blame(user_triggered, window, cx);
16843
16844 if ProjectSettings::get_global(cx)
16845 .git
16846 .inline_blame_delay()
16847 .is_some()
16848 {
16849 self.start_inline_blame_timer(window, cx);
16850 } else {
16851 self.show_git_blame_inline = true
16852 }
16853 }
16854
16855 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16856 self.blame.as_ref()
16857 }
16858
16859 pub fn show_git_blame_gutter(&self) -> bool {
16860 self.show_git_blame_gutter
16861 }
16862
16863 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16864 self.show_git_blame_gutter && self.has_blame_entries(cx)
16865 }
16866
16867 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16868 self.show_git_blame_inline
16869 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16870 && !self.newest_selection_head_on_empty_line(cx)
16871 && self.has_blame_entries(cx)
16872 }
16873
16874 fn has_blame_entries(&self, cx: &App) -> bool {
16875 self.blame()
16876 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16877 }
16878
16879 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16880 let cursor_anchor = self.selections.newest_anchor().head();
16881
16882 let snapshot = self.buffer.read(cx).snapshot(cx);
16883 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16884
16885 snapshot.line_len(buffer_row) == 0
16886 }
16887
16888 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16889 let buffer_and_selection = maybe!({
16890 let selection = self.selections.newest::<Point>(cx);
16891 let selection_range = selection.range();
16892
16893 let multi_buffer = self.buffer().read(cx);
16894 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16895 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16896
16897 let (buffer, range, _) = if selection.reversed {
16898 buffer_ranges.first()
16899 } else {
16900 buffer_ranges.last()
16901 }?;
16902
16903 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16904 ..text::ToPoint::to_point(&range.end, &buffer).row;
16905 Some((
16906 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16907 selection,
16908 ))
16909 });
16910
16911 let Some((buffer, selection)) = buffer_and_selection else {
16912 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16913 };
16914
16915 let Some(project) = self.project.as_ref() else {
16916 return Task::ready(Err(anyhow!("editor does not have project")));
16917 };
16918
16919 project.update(cx, |project, cx| {
16920 project.get_permalink_to_line(&buffer, selection, cx)
16921 })
16922 }
16923
16924 pub fn copy_permalink_to_line(
16925 &mut self,
16926 _: &CopyPermalinkToLine,
16927 window: &mut Window,
16928 cx: &mut Context<Self>,
16929 ) {
16930 let permalink_task = self.get_permalink_to_line(cx);
16931 let workspace = self.workspace();
16932
16933 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16934 Ok(permalink) => {
16935 cx.update(|_, cx| {
16936 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16937 })
16938 .ok();
16939 }
16940 Err(err) => {
16941 let message = format!("Failed to copy permalink: {err}");
16942
16943 Err::<(), anyhow::Error>(err).log_err();
16944
16945 if let Some(workspace) = workspace {
16946 workspace
16947 .update_in(cx, |workspace, _, cx| {
16948 struct CopyPermalinkToLine;
16949
16950 workspace.show_toast(
16951 Toast::new(
16952 NotificationId::unique::<CopyPermalinkToLine>(),
16953 message,
16954 ),
16955 cx,
16956 )
16957 })
16958 .ok();
16959 }
16960 }
16961 })
16962 .detach();
16963 }
16964
16965 pub fn copy_file_location(
16966 &mut self,
16967 _: &CopyFileLocation,
16968 _: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16972 if let Some(file) = self.target_file(cx) {
16973 if let Some(path) = file.path().to_str() {
16974 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16975 }
16976 }
16977 }
16978
16979 pub fn open_permalink_to_line(
16980 &mut self,
16981 _: &OpenPermalinkToLine,
16982 window: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) {
16985 let permalink_task = self.get_permalink_to_line(cx);
16986 let workspace = self.workspace();
16987
16988 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16989 Ok(permalink) => {
16990 cx.update(|_, cx| {
16991 cx.open_url(permalink.as_ref());
16992 })
16993 .ok();
16994 }
16995 Err(err) => {
16996 let message = format!("Failed to open permalink: {err}");
16997
16998 Err::<(), anyhow::Error>(err).log_err();
16999
17000 if let Some(workspace) = workspace {
17001 workspace
17002 .update(cx, |workspace, cx| {
17003 struct OpenPermalinkToLine;
17004
17005 workspace.show_toast(
17006 Toast::new(
17007 NotificationId::unique::<OpenPermalinkToLine>(),
17008 message,
17009 ),
17010 cx,
17011 )
17012 })
17013 .ok();
17014 }
17015 }
17016 })
17017 .detach();
17018 }
17019
17020 pub fn insert_uuid_v4(
17021 &mut self,
17022 _: &InsertUuidV4,
17023 window: &mut Window,
17024 cx: &mut Context<Self>,
17025 ) {
17026 self.insert_uuid(UuidVersion::V4, window, cx);
17027 }
17028
17029 pub fn insert_uuid_v7(
17030 &mut self,
17031 _: &InsertUuidV7,
17032 window: &mut Window,
17033 cx: &mut Context<Self>,
17034 ) {
17035 self.insert_uuid(UuidVersion::V7, window, cx);
17036 }
17037
17038 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17039 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17040 self.transact(window, cx, |this, window, cx| {
17041 let edits = this
17042 .selections
17043 .all::<Point>(cx)
17044 .into_iter()
17045 .map(|selection| {
17046 let uuid = match version {
17047 UuidVersion::V4 => uuid::Uuid::new_v4(),
17048 UuidVersion::V7 => uuid::Uuid::now_v7(),
17049 };
17050
17051 (selection.range(), uuid.to_string())
17052 });
17053 this.edit(edits, cx);
17054 this.refresh_inline_completion(true, false, window, cx);
17055 });
17056 }
17057
17058 pub fn open_selections_in_multibuffer(
17059 &mut self,
17060 _: &OpenSelectionsInMultibuffer,
17061 window: &mut Window,
17062 cx: &mut Context<Self>,
17063 ) {
17064 let multibuffer = self.buffer.read(cx);
17065
17066 let Some(buffer) = multibuffer.as_singleton() else {
17067 return;
17068 };
17069
17070 let Some(workspace) = self.workspace() else {
17071 return;
17072 };
17073
17074 let locations = self
17075 .selections
17076 .disjoint_anchors()
17077 .iter()
17078 .map(|range| Location {
17079 buffer: buffer.clone(),
17080 range: range.start.text_anchor..range.end.text_anchor,
17081 })
17082 .collect::<Vec<_>>();
17083
17084 let title = multibuffer.title(cx).to_string();
17085
17086 cx.spawn_in(window, async move |_, cx| {
17087 workspace.update_in(cx, |workspace, window, cx| {
17088 Self::open_locations_in_multibuffer(
17089 workspace,
17090 locations,
17091 format!("Selections for '{title}'"),
17092 false,
17093 MultibufferSelectionMode::All,
17094 window,
17095 cx,
17096 );
17097 })
17098 })
17099 .detach();
17100 }
17101
17102 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17103 /// last highlight added will be used.
17104 ///
17105 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17106 pub fn highlight_rows<T: 'static>(
17107 &mut self,
17108 range: Range<Anchor>,
17109 color: Hsla,
17110 options: RowHighlightOptions,
17111 cx: &mut Context<Self>,
17112 ) {
17113 let snapshot = self.buffer().read(cx).snapshot(cx);
17114 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17115 let ix = row_highlights.binary_search_by(|highlight| {
17116 Ordering::Equal
17117 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17118 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17119 });
17120
17121 if let Err(mut ix) = ix {
17122 let index = post_inc(&mut self.highlight_order);
17123
17124 // If this range intersects with the preceding highlight, then merge it with
17125 // the preceding highlight. Otherwise insert a new highlight.
17126 let mut merged = false;
17127 if ix > 0 {
17128 let prev_highlight = &mut row_highlights[ix - 1];
17129 if prev_highlight
17130 .range
17131 .end
17132 .cmp(&range.start, &snapshot)
17133 .is_ge()
17134 {
17135 ix -= 1;
17136 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17137 prev_highlight.range.end = range.end;
17138 }
17139 merged = true;
17140 prev_highlight.index = index;
17141 prev_highlight.color = color;
17142 prev_highlight.options = options;
17143 }
17144 }
17145
17146 if !merged {
17147 row_highlights.insert(
17148 ix,
17149 RowHighlight {
17150 range: range.clone(),
17151 index,
17152 color,
17153 options,
17154 type_id: TypeId::of::<T>(),
17155 },
17156 );
17157 }
17158
17159 // If any of the following highlights intersect with this one, merge them.
17160 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17161 let highlight = &row_highlights[ix];
17162 if next_highlight
17163 .range
17164 .start
17165 .cmp(&highlight.range.end, &snapshot)
17166 .is_le()
17167 {
17168 if next_highlight
17169 .range
17170 .end
17171 .cmp(&highlight.range.end, &snapshot)
17172 .is_gt()
17173 {
17174 row_highlights[ix].range.end = next_highlight.range.end;
17175 }
17176 row_highlights.remove(ix + 1);
17177 } else {
17178 break;
17179 }
17180 }
17181 }
17182 }
17183
17184 /// Remove any highlighted row ranges of the given type that intersect the
17185 /// given ranges.
17186 pub fn remove_highlighted_rows<T: 'static>(
17187 &mut self,
17188 ranges_to_remove: Vec<Range<Anchor>>,
17189 cx: &mut Context<Self>,
17190 ) {
17191 let snapshot = self.buffer().read(cx).snapshot(cx);
17192 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17193 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17194 row_highlights.retain(|highlight| {
17195 while let Some(range_to_remove) = ranges_to_remove.peek() {
17196 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17197 Ordering::Less | Ordering::Equal => {
17198 ranges_to_remove.next();
17199 }
17200 Ordering::Greater => {
17201 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17202 Ordering::Less | Ordering::Equal => {
17203 return false;
17204 }
17205 Ordering::Greater => break,
17206 }
17207 }
17208 }
17209 }
17210
17211 true
17212 })
17213 }
17214
17215 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17216 pub fn clear_row_highlights<T: 'static>(&mut self) {
17217 self.highlighted_rows.remove(&TypeId::of::<T>());
17218 }
17219
17220 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17221 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17222 self.highlighted_rows
17223 .get(&TypeId::of::<T>())
17224 .map_or(&[] as &[_], |vec| vec.as_slice())
17225 .iter()
17226 .map(|highlight| (highlight.range.clone(), highlight.color))
17227 }
17228
17229 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17230 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17231 /// Allows to ignore certain kinds of highlights.
17232 pub fn highlighted_display_rows(
17233 &self,
17234 window: &mut Window,
17235 cx: &mut App,
17236 ) -> BTreeMap<DisplayRow, LineHighlight> {
17237 let snapshot = self.snapshot(window, cx);
17238 let mut used_highlight_orders = HashMap::default();
17239 self.highlighted_rows
17240 .iter()
17241 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17242 .fold(
17243 BTreeMap::<DisplayRow, LineHighlight>::new(),
17244 |mut unique_rows, highlight| {
17245 let start = highlight.range.start.to_display_point(&snapshot);
17246 let end = highlight.range.end.to_display_point(&snapshot);
17247 let start_row = start.row().0;
17248 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17249 && end.column() == 0
17250 {
17251 end.row().0.saturating_sub(1)
17252 } else {
17253 end.row().0
17254 };
17255 for row in start_row..=end_row {
17256 let used_index =
17257 used_highlight_orders.entry(row).or_insert(highlight.index);
17258 if highlight.index >= *used_index {
17259 *used_index = highlight.index;
17260 unique_rows.insert(
17261 DisplayRow(row),
17262 LineHighlight {
17263 include_gutter: highlight.options.include_gutter,
17264 border: None,
17265 background: highlight.color.into(),
17266 type_id: Some(highlight.type_id),
17267 },
17268 );
17269 }
17270 }
17271 unique_rows
17272 },
17273 )
17274 }
17275
17276 pub fn highlighted_display_row_for_autoscroll(
17277 &self,
17278 snapshot: &DisplaySnapshot,
17279 ) -> Option<DisplayRow> {
17280 self.highlighted_rows
17281 .values()
17282 .flat_map(|highlighted_rows| highlighted_rows.iter())
17283 .filter_map(|highlight| {
17284 if highlight.options.autoscroll {
17285 Some(highlight.range.start.to_display_point(snapshot).row())
17286 } else {
17287 None
17288 }
17289 })
17290 .min()
17291 }
17292
17293 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17294 self.highlight_background::<SearchWithinRange>(
17295 ranges,
17296 |colors| colors.editor_document_highlight_read_background,
17297 cx,
17298 )
17299 }
17300
17301 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17302 self.breadcrumb_header = Some(new_header);
17303 }
17304
17305 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17306 self.clear_background_highlights::<SearchWithinRange>(cx);
17307 }
17308
17309 pub fn highlight_background<T: 'static>(
17310 &mut self,
17311 ranges: &[Range<Anchor>],
17312 color_fetcher: fn(&ThemeColors) -> Hsla,
17313 cx: &mut Context<Self>,
17314 ) {
17315 self.background_highlights
17316 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17317 self.scrollbar_marker_state.dirty = true;
17318 cx.notify();
17319 }
17320
17321 pub fn clear_background_highlights<T: 'static>(
17322 &mut self,
17323 cx: &mut Context<Self>,
17324 ) -> Option<BackgroundHighlight> {
17325 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17326 if !text_highlights.1.is_empty() {
17327 self.scrollbar_marker_state.dirty = true;
17328 cx.notify();
17329 }
17330 Some(text_highlights)
17331 }
17332
17333 pub fn highlight_gutter<T: 'static>(
17334 &mut self,
17335 ranges: &[Range<Anchor>],
17336 color_fetcher: fn(&App) -> Hsla,
17337 cx: &mut Context<Self>,
17338 ) {
17339 self.gutter_highlights
17340 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17341 cx.notify();
17342 }
17343
17344 pub fn clear_gutter_highlights<T: 'static>(
17345 &mut self,
17346 cx: &mut Context<Self>,
17347 ) -> Option<GutterHighlight> {
17348 cx.notify();
17349 self.gutter_highlights.remove(&TypeId::of::<T>())
17350 }
17351
17352 #[cfg(feature = "test-support")]
17353 pub fn all_text_background_highlights(
17354 &self,
17355 window: &mut Window,
17356 cx: &mut Context<Self>,
17357 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17358 let snapshot = self.snapshot(window, cx);
17359 let buffer = &snapshot.buffer_snapshot;
17360 let start = buffer.anchor_before(0);
17361 let end = buffer.anchor_after(buffer.len());
17362 let theme = cx.theme().colors();
17363 self.background_highlights_in_range(start..end, &snapshot, theme)
17364 }
17365
17366 #[cfg(feature = "test-support")]
17367 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17368 let snapshot = self.buffer().read(cx).snapshot(cx);
17369
17370 let highlights = self
17371 .background_highlights
17372 .get(&TypeId::of::<items::BufferSearchHighlights>());
17373
17374 if let Some((_color, ranges)) = highlights {
17375 ranges
17376 .iter()
17377 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17378 .collect_vec()
17379 } else {
17380 vec![]
17381 }
17382 }
17383
17384 fn document_highlights_for_position<'a>(
17385 &'a self,
17386 position: Anchor,
17387 buffer: &'a MultiBufferSnapshot,
17388 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17389 let read_highlights = self
17390 .background_highlights
17391 .get(&TypeId::of::<DocumentHighlightRead>())
17392 .map(|h| &h.1);
17393 let write_highlights = self
17394 .background_highlights
17395 .get(&TypeId::of::<DocumentHighlightWrite>())
17396 .map(|h| &h.1);
17397 let left_position = position.bias_left(buffer);
17398 let right_position = position.bias_right(buffer);
17399 read_highlights
17400 .into_iter()
17401 .chain(write_highlights)
17402 .flat_map(move |ranges| {
17403 let start_ix = match ranges.binary_search_by(|probe| {
17404 let cmp = probe.end.cmp(&left_position, buffer);
17405 if cmp.is_ge() {
17406 Ordering::Greater
17407 } else {
17408 Ordering::Less
17409 }
17410 }) {
17411 Ok(i) | Err(i) => i,
17412 };
17413
17414 ranges[start_ix..]
17415 .iter()
17416 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17417 })
17418 }
17419
17420 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17421 self.background_highlights
17422 .get(&TypeId::of::<T>())
17423 .map_or(false, |(_, highlights)| !highlights.is_empty())
17424 }
17425
17426 pub fn background_highlights_in_range(
17427 &self,
17428 search_range: Range<Anchor>,
17429 display_snapshot: &DisplaySnapshot,
17430 theme: &ThemeColors,
17431 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17432 let mut results = Vec::new();
17433 for (color_fetcher, ranges) in self.background_highlights.values() {
17434 let color = color_fetcher(theme);
17435 let start_ix = match ranges.binary_search_by(|probe| {
17436 let cmp = probe
17437 .end
17438 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17439 if cmp.is_gt() {
17440 Ordering::Greater
17441 } else {
17442 Ordering::Less
17443 }
17444 }) {
17445 Ok(i) | Err(i) => i,
17446 };
17447 for range in &ranges[start_ix..] {
17448 if range
17449 .start
17450 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17451 .is_ge()
17452 {
17453 break;
17454 }
17455
17456 let start = range.start.to_display_point(display_snapshot);
17457 let end = range.end.to_display_point(display_snapshot);
17458 results.push((start..end, color))
17459 }
17460 }
17461 results
17462 }
17463
17464 pub fn background_highlight_row_ranges<T: 'static>(
17465 &self,
17466 search_range: Range<Anchor>,
17467 display_snapshot: &DisplaySnapshot,
17468 count: usize,
17469 ) -> Vec<RangeInclusive<DisplayPoint>> {
17470 let mut results = Vec::new();
17471 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17472 return vec![];
17473 };
17474
17475 let start_ix = match ranges.binary_search_by(|probe| {
17476 let cmp = probe
17477 .end
17478 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17479 if cmp.is_gt() {
17480 Ordering::Greater
17481 } else {
17482 Ordering::Less
17483 }
17484 }) {
17485 Ok(i) | Err(i) => i,
17486 };
17487 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17488 if let (Some(start_display), Some(end_display)) = (start, end) {
17489 results.push(
17490 start_display.to_display_point(display_snapshot)
17491 ..=end_display.to_display_point(display_snapshot),
17492 );
17493 }
17494 };
17495 let mut start_row: Option<Point> = None;
17496 let mut end_row: Option<Point> = None;
17497 if ranges.len() > count {
17498 return Vec::new();
17499 }
17500 for range in &ranges[start_ix..] {
17501 if range
17502 .start
17503 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17504 .is_ge()
17505 {
17506 break;
17507 }
17508 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17509 if let Some(current_row) = &end_row {
17510 if end.row == current_row.row {
17511 continue;
17512 }
17513 }
17514 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17515 if start_row.is_none() {
17516 assert_eq!(end_row, None);
17517 start_row = Some(start);
17518 end_row = Some(end);
17519 continue;
17520 }
17521 if let Some(current_end) = end_row.as_mut() {
17522 if start.row > current_end.row + 1 {
17523 push_region(start_row, end_row);
17524 start_row = Some(start);
17525 end_row = Some(end);
17526 } else {
17527 // Merge two hunks.
17528 *current_end = end;
17529 }
17530 } else {
17531 unreachable!();
17532 }
17533 }
17534 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17535 push_region(start_row, end_row);
17536 results
17537 }
17538
17539 pub fn gutter_highlights_in_range(
17540 &self,
17541 search_range: Range<Anchor>,
17542 display_snapshot: &DisplaySnapshot,
17543 cx: &App,
17544 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17545 let mut results = Vec::new();
17546 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17547 let color = color_fetcher(cx);
17548 let start_ix = match ranges.binary_search_by(|probe| {
17549 let cmp = probe
17550 .end
17551 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17552 if cmp.is_gt() {
17553 Ordering::Greater
17554 } else {
17555 Ordering::Less
17556 }
17557 }) {
17558 Ok(i) | Err(i) => i,
17559 };
17560 for range in &ranges[start_ix..] {
17561 if range
17562 .start
17563 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17564 .is_ge()
17565 {
17566 break;
17567 }
17568
17569 let start = range.start.to_display_point(display_snapshot);
17570 let end = range.end.to_display_point(display_snapshot);
17571 results.push((start..end, color))
17572 }
17573 }
17574 results
17575 }
17576
17577 /// Get the text ranges corresponding to the redaction query
17578 pub fn redacted_ranges(
17579 &self,
17580 search_range: Range<Anchor>,
17581 display_snapshot: &DisplaySnapshot,
17582 cx: &App,
17583 ) -> Vec<Range<DisplayPoint>> {
17584 display_snapshot
17585 .buffer_snapshot
17586 .redacted_ranges(search_range, |file| {
17587 if let Some(file) = file {
17588 file.is_private()
17589 && EditorSettings::get(
17590 Some(SettingsLocation {
17591 worktree_id: file.worktree_id(cx),
17592 path: file.path().as_ref(),
17593 }),
17594 cx,
17595 )
17596 .redact_private_values
17597 } else {
17598 false
17599 }
17600 })
17601 .map(|range| {
17602 range.start.to_display_point(display_snapshot)
17603 ..range.end.to_display_point(display_snapshot)
17604 })
17605 .collect()
17606 }
17607
17608 pub fn highlight_text<T: 'static>(
17609 &mut self,
17610 ranges: Vec<Range<Anchor>>,
17611 style: HighlightStyle,
17612 cx: &mut Context<Self>,
17613 ) {
17614 self.display_map.update(cx, |map, _| {
17615 map.highlight_text(TypeId::of::<T>(), ranges, style)
17616 });
17617 cx.notify();
17618 }
17619
17620 pub(crate) fn highlight_inlays<T: 'static>(
17621 &mut self,
17622 highlights: Vec<InlayHighlight>,
17623 style: HighlightStyle,
17624 cx: &mut Context<Self>,
17625 ) {
17626 self.display_map.update(cx, |map, _| {
17627 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17628 });
17629 cx.notify();
17630 }
17631
17632 pub fn text_highlights<'a, T: 'static>(
17633 &'a self,
17634 cx: &'a App,
17635 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17636 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17637 }
17638
17639 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17640 let cleared = self
17641 .display_map
17642 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17643 if cleared {
17644 cx.notify();
17645 }
17646 }
17647
17648 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17649 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17650 && self.focus_handle.is_focused(window)
17651 }
17652
17653 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17654 self.show_cursor_when_unfocused = is_enabled;
17655 cx.notify();
17656 }
17657
17658 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17659 cx.notify();
17660 }
17661
17662 fn on_debug_session_event(
17663 &mut self,
17664 _session: Entity<Session>,
17665 event: &SessionEvent,
17666 cx: &mut Context<Self>,
17667 ) {
17668 match event {
17669 SessionEvent::InvalidateInlineValue => {
17670 self.refresh_inline_values(cx);
17671 }
17672 _ => {}
17673 }
17674 }
17675
17676 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17677 let Some(project) = self.project.clone() else {
17678 return;
17679 };
17680 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17681 return;
17682 };
17683 if !self.inline_value_cache.enabled {
17684 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17685 self.splice_inlays(&inlays, Vec::new(), cx);
17686 return;
17687 }
17688
17689 let current_execution_position = self
17690 .highlighted_rows
17691 .get(&TypeId::of::<DebugCurrentRowHighlight>())
17692 .and_then(|lines| lines.last().map(|line| line.range.start));
17693
17694 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17695 let snapshot = editor
17696 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17697 .ok()?;
17698
17699 let inline_values = editor
17700 .update(cx, |_, cx| {
17701 let Some(current_execution_position) = current_execution_position else {
17702 return Some(Task::ready(Ok(Vec::new())));
17703 };
17704
17705 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17706 // anchor is in the same buffer
17707 let range =
17708 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17709 project.inline_values(buffer, range, cx)
17710 })
17711 .ok()
17712 .flatten()?
17713 .await
17714 .context("refreshing debugger inlays")
17715 .log_err()?;
17716
17717 let (excerpt_id, buffer_id) = snapshot
17718 .excerpts()
17719 .next()
17720 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17721 editor
17722 .update(cx, |editor, cx| {
17723 let new_inlays = inline_values
17724 .into_iter()
17725 .map(|debugger_value| {
17726 Inlay::debugger_hint(
17727 post_inc(&mut editor.next_inlay_id),
17728 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17729 debugger_value.text(),
17730 )
17731 })
17732 .collect::<Vec<_>>();
17733 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17734 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17735
17736 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17737 })
17738 .ok()?;
17739 Some(())
17740 });
17741 }
17742
17743 fn on_buffer_event(
17744 &mut self,
17745 multibuffer: &Entity<MultiBuffer>,
17746 event: &multi_buffer::Event,
17747 window: &mut Window,
17748 cx: &mut Context<Self>,
17749 ) {
17750 match event {
17751 multi_buffer::Event::Edited {
17752 singleton_buffer_edited,
17753 edited_buffer: buffer_edited,
17754 } => {
17755 self.scrollbar_marker_state.dirty = true;
17756 self.active_indent_guides_state.dirty = true;
17757 self.refresh_active_diagnostics(cx);
17758 self.refresh_code_actions(window, cx);
17759 self.refresh_selected_text_highlights(true, window, cx);
17760 refresh_matching_bracket_highlights(self, window, cx);
17761 if self.has_active_inline_completion() {
17762 self.update_visible_inline_completion(window, cx);
17763 }
17764 if let Some(buffer) = buffer_edited {
17765 let buffer_id = buffer.read(cx).remote_id();
17766 if !self.registered_buffers.contains_key(&buffer_id) {
17767 if let Some(project) = self.project.as_ref() {
17768 project.update(cx, |project, cx| {
17769 self.registered_buffers.insert(
17770 buffer_id,
17771 project.register_buffer_with_language_servers(&buffer, cx),
17772 );
17773 })
17774 }
17775 }
17776 }
17777 cx.emit(EditorEvent::BufferEdited);
17778 cx.emit(SearchEvent::MatchesInvalidated);
17779 if *singleton_buffer_edited {
17780 if let Some(project) = &self.project {
17781 #[allow(clippy::mutable_key_type)]
17782 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17783 multibuffer
17784 .all_buffers()
17785 .into_iter()
17786 .filter_map(|buffer| {
17787 buffer.update(cx, |buffer, cx| {
17788 let language = buffer.language()?;
17789 let should_discard = project.update(cx, |project, cx| {
17790 project.is_local()
17791 && !project.has_language_servers_for(buffer, cx)
17792 });
17793 should_discard.not().then_some(language.clone())
17794 })
17795 })
17796 .collect::<HashSet<_>>()
17797 });
17798 if !languages_affected.is_empty() {
17799 self.refresh_inlay_hints(
17800 InlayHintRefreshReason::BufferEdited(languages_affected),
17801 cx,
17802 );
17803 }
17804 }
17805 }
17806
17807 let Some(project) = &self.project else { return };
17808 let (telemetry, is_via_ssh) = {
17809 let project = project.read(cx);
17810 let telemetry = project.client().telemetry().clone();
17811 let is_via_ssh = project.is_via_ssh();
17812 (telemetry, is_via_ssh)
17813 };
17814 refresh_linked_ranges(self, window, cx);
17815 telemetry.log_edit_event("editor", is_via_ssh);
17816 }
17817 multi_buffer::Event::ExcerptsAdded {
17818 buffer,
17819 predecessor,
17820 excerpts,
17821 } => {
17822 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17823 let buffer_id = buffer.read(cx).remote_id();
17824 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17825 if let Some(project) = &self.project {
17826 get_uncommitted_diff_for_buffer(
17827 project,
17828 [buffer.clone()],
17829 self.buffer.clone(),
17830 cx,
17831 )
17832 .detach();
17833 }
17834 }
17835 cx.emit(EditorEvent::ExcerptsAdded {
17836 buffer: buffer.clone(),
17837 predecessor: *predecessor,
17838 excerpts: excerpts.clone(),
17839 });
17840 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17841 }
17842 multi_buffer::Event::ExcerptsRemoved {
17843 ids,
17844 removed_buffer_ids,
17845 } => {
17846 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17847 let buffer = self.buffer.read(cx);
17848 self.registered_buffers
17849 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17850 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17851 cx.emit(EditorEvent::ExcerptsRemoved {
17852 ids: ids.clone(),
17853 removed_buffer_ids: removed_buffer_ids.clone(),
17854 })
17855 }
17856 multi_buffer::Event::ExcerptsEdited {
17857 excerpt_ids,
17858 buffer_ids,
17859 } => {
17860 self.display_map.update(cx, |map, cx| {
17861 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17862 });
17863 cx.emit(EditorEvent::ExcerptsEdited {
17864 ids: excerpt_ids.clone(),
17865 })
17866 }
17867 multi_buffer::Event::ExcerptsExpanded { ids } => {
17868 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17869 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17870 }
17871 multi_buffer::Event::Reparsed(buffer_id) => {
17872 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17873 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17874
17875 cx.emit(EditorEvent::Reparsed(*buffer_id));
17876 }
17877 multi_buffer::Event::DiffHunksToggled => {
17878 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17879 }
17880 multi_buffer::Event::LanguageChanged(buffer_id) => {
17881 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17882 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17883 cx.emit(EditorEvent::Reparsed(*buffer_id));
17884 cx.notify();
17885 }
17886 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17887 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17888 multi_buffer::Event::FileHandleChanged
17889 | multi_buffer::Event::Reloaded
17890 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17891 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17892 multi_buffer::Event::DiagnosticsUpdated => {
17893 self.refresh_active_diagnostics(cx);
17894 self.refresh_inline_diagnostics(true, window, cx);
17895 self.scrollbar_marker_state.dirty = true;
17896 cx.notify();
17897 }
17898 _ => {}
17899 };
17900 }
17901
17902 fn on_display_map_changed(
17903 &mut self,
17904 _: Entity<DisplayMap>,
17905 _: &mut Window,
17906 cx: &mut Context<Self>,
17907 ) {
17908 cx.notify();
17909 }
17910
17911 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17912 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17913 self.update_edit_prediction_settings(cx);
17914 self.refresh_inline_completion(true, false, window, cx);
17915 self.refresh_inlay_hints(
17916 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17917 self.selections.newest_anchor().head(),
17918 &self.buffer.read(cx).snapshot(cx),
17919 cx,
17920 )),
17921 cx,
17922 );
17923
17924 let old_cursor_shape = self.cursor_shape;
17925
17926 {
17927 let editor_settings = EditorSettings::get_global(cx);
17928 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17929 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17930 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17931 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17932 }
17933
17934 if old_cursor_shape != self.cursor_shape {
17935 cx.emit(EditorEvent::CursorShapeChanged);
17936 }
17937
17938 let project_settings = ProjectSettings::get_global(cx);
17939 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17940
17941 if self.mode.is_full() {
17942 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17943 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17944 if self.show_inline_diagnostics != show_inline_diagnostics {
17945 self.show_inline_diagnostics = show_inline_diagnostics;
17946 self.refresh_inline_diagnostics(false, window, cx);
17947 }
17948
17949 if self.git_blame_inline_enabled != inline_blame_enabled {
17950 self.toggle_git_blame_inline_internal(false, window, cx);
17951 }
17952 }
17953
17954 cx.notify();
17955 }
17956
17957 pub fn set_searchable(&mut self, searchable: bool) {
17958 self.searchable = searchable;
17959 }
17960
17961 pub fn searchable(&self) -> bool {
17962 self.searchable
17963 }
17964
17965 fn open_proposed_changes_editor(
17966 &mut self,
17967 _: &OpenProposedChangesEditor,
17968 window: &mut Window,
17969 cx: &mut Context<Self>,
17970 ) {
17971 let Some(workspace) = self.workspace() else {
17972 cx.propagate();
17973 return;
17974 };
17975
17976 let selections = self.selections.all::<usize>(cx);
17977 let multi_buffer = self.buffer.read(cx);
17978 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17979 let mut new_selections_by_buffer = HashMap::default();
17980 for selection in selections {
17981 for (buffer, range, _) in
17982 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17983 {
17984 let mut range = range.to_point(buffer);
17985 range.start.column = 0;
17986 range.end.column = buffer.line_len(range.end.row);
17987 new_selections_by_buffer
17988 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17989 .or_insert(Vec::new())
17990 .push(range)
17991 }
17992 }
17993
17994 let proposed_changes_buffers = new_selections_by_buffer
17995 .into_iter()
17996 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17997 .collect::<Vec<_>>();
17998 let proposed_changes_editor = cx.new(|cx| {
17999 ProposedChangesEditor::new(
18000 "Proposed changes",
18001 proposed_changes_buffers,
18002 self.project.clone(),
18003 window,
18004 cx,
18005 )
18006 });
18007
18008 window.defer(cx, move |window, cx| {
18009 workspace.update(cx, |workspace, cx| {
18010 workspace.active_pane().update(cx, |pane, cx| {
18011 pane.add_item(
18012 Box::new(proposed_changes_editor),
18013 true,
18014 true,
18015 None,
18016 window,
18017 cx,
18018 );
18019 });
18020 });
18021 });
18022 }
18023
18024 pub fn open_excerpts_in_split(
18025 &mut self,
18026 _: &OpenExcerptsSplit,
18027 window: &mut Window,
18028 cx: &mut Context<Self>,
18029 ) {
18030 self.open_excerpts_common(None, true, window, cx)
18031 }
18032
18033 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18034 self.open_excerpts_common(None, false, window, cx)
18035 }
18036
18037 fn open_excerpts_common(
18038 &mut self,
18039 jump_data: Option<JumpData>,
18040 split: bool,
18041 window: &mut Window,
18042 cx: &mut Context<Self>,
18043 ) {
18044 let Some(workspace) = self.workspace() else {
18045 cx.propagate();
18046 return;
18047 };
18048
18049 if self.buffer.read(cx).is_singleton() {
18050 cx.propagate();
18051 return;
18052 }
18053
18054 let mut new_selections_by_buffer = HashMap::default();
18055 match &jump_data {
18056 Some(JumpData::MultiBufferPoint {
18057 excerpt_id,
18058 position,
18059 anchor,
18060 line_offset_from_top,
18061 }) => {
18062 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18063 if let Some(buffer) = multi_buffer_snapshot
18064 .buffer_id_for_excerpt(*excerpt_id)
18065 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18066 {
18067 let buffer_snapshot = buffer.read(cx).snapshot();
18068 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18069 language::ToPoint::to_point(anchor, &buffer_snapshot)
18070 } else {
18071 buffer_snapshot.clip_point(*position, Bias::Left)
18072 };
18073 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18074 new_selections_by_buffer.insert(
18075 buffer,
18076 (
18077 vec![jump_to_offset..jump_to_offset],
18078 Some(*line_offset_from_top),
18079 ),
18080 );
18081 }
18082 }
18083 Some(JumpData::MultiBufferRow {
18084 row,
18085 line_offset_from_top,
18086 }) => {
18087 let point = MultiBufferPoint::new(row.0, 0);
18088 if let Some((buffer, buffer_point, _)) =
18089 self.buffer.read(cx).point_to_buffer_point(point, cx)
18090 {
18091 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18092 new_selections_by_buffer
18093 .entry(buffer)
18094 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18095 .0
18096 .push(buffer_offset..buffer_offset)
18097 }
18098 }
18099 None => {
18100 let selections = self.selections.all::<usize>(cx);
18101 let multi_buffer = self.buffer.read(cx);
18102 for selection in selections {
18103 for (snapshot, range, _, anchor) in multi_buffer
18104 .snapshot(cx)
18105 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18106 {
18107 if let Some(anchor) = anchor {
18108 // selection is in a deleted hunk
18109 let Some(buffer_id) = anchor.buffer_id else {
18110 continue;
18111 };
18112 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18113 continue;
18114 };
18115 let offset = text::ToOffset::to_offset(
18116 &anchor.text_anchor,
18117 &buffer_handle.read(cx).snapshot(),
18118 );
18119 let range = offset..offset;
18120 new_selections_by_buffer
18121 .entry(buffer_handle)
18122 .or_insert((Vec::new(), None))
18123 .0
18124 .push(range)
18125 } else {
18126 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18127 else {
18128 continue;
18129 };
18130 new_selections_by_buffer
18131 .entry(buffer_handle)
18132 .or_insert((Vec::new(), None))
18133 .0
18134 .push(range)
18135 }
18136 }
18137 }
18138 }
18139 }
18140
18141 new_selections_by_buffer
18142 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18143
18144 if new_selections_by_buffer.is_empty() {
18145 return;
18146 }
18147
18148 // We defer the pane interaction because we ourselves are a workspace item
18149 // and activating a new item causes the pane to call a method on us reentrantly,
18150 // which panics if we're on the stack.
18151 window.defer(cx, move |window, cx| {
18152 workspace.update(cx, |workspace, cx| {
18153 let pane = if split {
18154 workspace.adjacent_pane(window, cx)
18155 } else {
18156 workspace.active_pane().clone()
18157 };
18158
18159 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18160 let editor = buffer
18161 .read(cx)
18162 .file()
18163 .is_none()
18164 .then(|| {
18165 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18166 // so `workspace.open_project_item` will never find them, always opening a new editor.
18167 // Instead, we try to activate the existing editor in the pane first.
18168 let (editor, pane_item_index) =
18169 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18170 let editor = item.downcast::<Editor>()?;
18171 let singleton_buffer =
18172 editor.read(cx).buffer().read(cx).as_singleton()?;
18173 if singleton_buffer == buffer {
18174 Some((editor, i))
18175 } else {
18176 None
18177 }
18178 })?;
18179 pane.update(cx, |pane, cx| {
18180 pane.activate_item(pane_item_index, true, true, window, cx)
18181 });
18182 Some(editor)
18183 })
18184 .flatten()
18185 .unwrap_or_else(|| {
18186 workspace.open_project_item::<Self>(
18187 pane.clone(),
18188 buffer,
18189 true,
18190 true,
18191 window,
18192 cx,
18193 )
18194 });
18195
18196 editor.update(cx, |editor, cx| {
18197 let autoscroll = match scroll_offset {
18198 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18199 None => Autoscroll::newest(),
18200 };
18201 let nav_history = editor.nav_history.take();
18202 editor.change_selections(Some(autoscroll), window, cx, |s| {
18203 s.select_ranges(ranges);
18204 });
18205 editor.nav_history = nav_history;
18206 });
18207 }
18208 })
18209 });
18210 }
18211
18212 // For now, don't allow opening excerpts in buffers that aren't backed by
18213 // regular project files.
18214 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18215 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18216 }
18217
18218 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18219 let snapshot = self.buffer.read(cx).read(cx);
18220 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18221 Some(
18222 ranges
18223 .iter()
18224 .map(move |range| {
18225 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18226 })
18227 .collect(),
18228 )
18229 }
18230
18231 fn selection_replacement_ranges(
18232 &self,
18233 range: Range<OffsetUtf16>,
18234 cx: &mut App,
18235 ) -> Vec<Range<OffsetUtf16>> {
18236 let selections = self.selections.all::<OffsetUtf16>(cx);
18237 let newest_selection = selections
18238 .iter()
18239 .max_by_key(|selection| selection.id)
18240 .unwrap();
18241 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18242 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18243 let snapshot = self.buffer.read(cx).read(cx);
18244 selections
18245 .into_iter()
18246 .map(|mut selection| {
18247 selection.start.0 =
18248 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18249 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18250 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18251 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18252 })
18253 .collect()
18254 }
18255
18256 fn report_editor_event(
18257 &self,
18258 event_type: &'static str,
18259 file_extension: Option<String>,
18260 cx: &App,
18261 ) {
18262 if cfg!(any(test, feature = "test-support")) {
18263 return;
18264 }
18265
18266 let Some(project) = &self.project else { return };
18267
18268 // If None, we are in a file without an extension
18269 let file = self
18270 .buffer
18271 .read(cx)
18272 .as_singleton()
18273 .and_then(|b| b.read(cx).file());
18274 let file_extension = file_extension.or(file
18275 .as_ref()
18276 .and_then(|file| Path::new(file.file_name(cx)).extension())
18277 .and_then(|e| e.to_str())
18278 .map(|a| a.to_string()));
18279
18280 let vim_mode = vim_enabled(cx);
18281
18282 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18283 let copilot_enabled = edit_predictions_provider
18284 == language::language_settings::EditPredictionProvider::Copilot;
18285 let copilot_enabled_for_language = self
18286 .buffer
18287 .read(cx)
18288 .language_settings(cx)
18289 .show_edit_predictions;
18290
18291 let project = project.read(cx);
18292 telemetry::event!(
18293 event_type,
18294 file_extension,
18295 vim_mode,
18296 copilot_enabled,
18297 copilot_enabled_for_language,
18298 edit_predictions_provider,
18299 is_via_ssh = project.is_via_ssh(),
18300 );
18301 }
18302
18303 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18304 /// with each line being an array of {text, highlight} objects.
18305 fn copy_highlight_json(
18306 &mut self,
18307 _: &CopyHighlightJson,
18308 window: &mut Window,
18309 cx: &mut Context<Self>,
18310 ) {
18311 #[derive(Serialize)]
18312 struct Chunk<'a> {
18313 text: String,
18314 highlight: Option<&'a str>,
18315 }
18316
18317 let snapshot = self.buffer.read(cx).snapshot(cx);
18318 let range = self
18319 .selected_text_range(false, window, cx)
18320 .and_then(|selection| {
18321 if selection.range.is_empty() {
18322 None
18323 } else {
18324 Some(selection.range)
18325 }
18326 })
18327 .unwrap_or_else(|| 0..snapshot.len());
18328
18329 let chunks = snapshot.chunks(range, true);
18330 let mut lines = Vec::new();
18331 let mut line: VecDeque<Chunk> = VecDeque::new();
18332
18333 let Some(style) = self.style.as_ref() else {
18334 return;
18335 };
18336
18337 for chunk in chunks {
18338 let highlight = chunk
18339 .syntax_highlight_id
18340 .and_then(|id| id.name(&style.syntax));
18341 let mut chunk_lines = chunk.text.split('\n').peekable();
18342 while let Some(text) = chunk_lines.next() {
18343 let mut merged_with_last_token = false;
18344 if let Some(last_token) = line.back_mut() {
18345 if last_token.highlight == highlight {
18346 last_token.text.push_str(text);
18347 merged_with_last_token = true;
18348 }
18349 }
18350
18351 if !merged_with_last_token {
18352 line.push_back(Chunk {
18353 text: text.into(),
18354 highlight,
18355 });
18356 }
18357
18358 if chunk_lines.peek().is_some() {
18359 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18360 line.pop_front();
18361 }
18362 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18363 line.pop_back();
18364 }
18365
18366 lines.push(mem::take(&mut line));
18367 }
18368 }
18369 }
18370
18371 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18372 return;
18373 };
18374 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18375 }
18376
18377 pub fn open_context_menu(
18378 &mut self,
18379 _: &OpenContextMenu,
18380 window: &mut Window,
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.request_autoscroll(Autoscroll::newest(), cx);
18384 let position = self.selections.newest_display(cx).start;
18385 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18386 }
18387
18388 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18389 &self.inlay_hint_cache
18390 }
18391
18392 pub fn replay_insert_event(
18393 &mut self,
18394 text: &str,
18395 relative_utf16_range: Option<Range<isize>>,
18396 window: &mut Window,
18397 cx: &mut Context<Self>,
18398 ) {
18399 if !self.input_enabled {
18400 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18401 return;
18402 }
18403 if let Some(relative_utf16_range) = relative_utf16_range {
18404 let selections = self.selections.all::<OffsetUtf16>(cx);
18405 self.change_selections(None, window, cx, |s| {
18406 let new_ranges = selections.into_iter().map(|range| {
18407 let start = OffsetUtf16(
18408 range
18409 .head()
18410 .0
18411 .saturating_add_signed(relative_utf16_range.start),
18412 );
18413 let end = OffsetUtf16(
18414 range
18415 .head()
18416 .0
18417 .saturating_add_signed(relative_utf16_range.end),
18418 );
18419 start..end
18420 });
18421 s.select_ranges(new_ranges);
18422 });
18423 }
18424
18425 self.handle_input(text, window, cx);
18426 }
18427
18428 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18429 let Some(provider) = self.semantics_provider.as_ref() else {
18430 return false;
18431 };
18432
18433 let mut supports = false;
18434 self.buffer().update(cx, |this, cx| {
18435 this.for_each_buffer(|buffer| {
18436 supports |= provider.supports_inlay_hints(buffer, cx);
18437 });
18438 });
18439
18440 supports
18441 }
18442
18443 pub fn is_focused(&self, window: &Window) -> bool {
18444 self.focus_handle.is_focused(window)
18445 }
18446
18447 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18448 cx.emit(EditorEvent::Focused);
18449
18450 if let Some(descendant) = self
18451 .last_focused_descendant
18452 .take()
18453 .and_then(|descendant| descendant.upgrade())
18454 {
18455 window.focus(&descendant);
18456 } else {
18457 if let Some(blame) = self.blame.as_ref() {
18458 blame.update(cx, GitBlame::focus)
18459 }
18460
18461 self.blink_manager.update(cx, BlinkManager::enable);
18462 self.show_cursor_names(window, cx);
18463 self.buffer.update(cx, |buffer, cx| {
18464 buffer.finalize_last_transaction(cx);
18465 if self.leader_peer_id.is_none() {
18466 buffer.set_active_selections(
18467 &self.selections.disjoint_anchors(),
18468 self.selections.line_mode,
18469 self.cursor_shape,
18470 cx,
18471 );
18472 }
18473 });
18474 }
18475 }
18476
18477 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18478 cx.emit(EditorEvent::FocusedIn)
18479 }
18480
18481 fn handle_focus_out(
18482 &mut self,
18483 event: FocusOutEvent,
18484 _window: &mut Window,
18485 cx: &mut Context<Self>,
18486 ) {
18487 if event.blurred != self.focus_handle {
18488 self.last_focused_descendant = Some(event.blurred);
18489 }
18490 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18491 }
18492
18493 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18494 self.blink_manager.update(cx, BlinkManager::disable);
18495 self.buffer
18496 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18497
18498 if let Some(blame) = self.blame.as_ref() {
18499 blame.update(cx, GitBlame::blur)
18500 }
18501 if !self.hover_state.focused(window, cx) {
18502 hide_hover(self, cx);
18503 }
18504 if !self
18505 .context_menu
18506 .borrow()
18507 .as_ref()
18508 .is_some_and(|context_menu| context_menu.focused(window, cx))
18509 {
18510 self.hide_context_menu(window, cx);
18511 }
18512 self.discard_inline_completion(false, cx);
18513 cx.emit(EditorEvent::Blurred);
18514 cx.notify();
18515 }
18516
18517 pub fn register_action<A: Action>(
18518 &mut self,
18519 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18520 ) -> Subscription {
18521 let id = self.next_editor_action_id.post_inc();
18522 let listener = Arc::new(listener);
18523 self.editor_actions.borrow_mut().insert(
18524 id,
18525 Box::new(move |window, _| {
18526 let listener = listener.clone();
18527 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18528 let action = action.downcast_ref().unwrap();
18529 if phase == DispatchPhase::Bubble {
18530 listener(action, window, cx)
18531 }
18532 })
18533 }),
18534 );
18535
18536 let editor_actions = self.editor_actions.clone();
18537 Subscription::new(move || {
18538 editor_actions.borrow_mut().remove(&id);
18539 })
18540 }
18541
18542 pub fn file_header_size(&self) -> u32 {
18543 FILE_HEADER_HEIGHT
18544 }
18545
18546 pub fn restore(
18547 &mut self,
18548 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18549 window: &mut Window,
18550 cx: &mut Context<Self>,
18551 ) {
18552 let workspace = self.workspace();
18553 let project = self.project.as_ref();
18554 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18555 let mut tasks = Vec::new();
18556 for (buffer_id, changes) in revert_changes {
18557 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18558 buffer.update(cx, |buffer, cx| {
18559 buffer.edit(
18560 changes
18561 .into_iter()
18562 .map(|(range, text)| (range, text.to_string())),
18563 None,
18564 cx,
18565 );
18566 });
18567
18568 if let Some(project) =
18569 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18570 {
18571 project.update(cx, |project, cx| {
18572 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18573 })
18574 }
18575 }
18576 }
18577 tasks
18578 });
18579 cx.spawn_in(window, async move |_, cx| {
18580 for (buffer, task) in save_tasks {
18581 let result = task.await;
18582 if result.is_err() {
18583 let Some(path) = buffer
18584 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18585 .ok()
18586 else {
18587 continue;
18588 };
18589 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18590 let Some(task) = cx
18591 .update_window_entity(&workspace, |workspace, window, cx| {
18592 workspace
18593 .open_path_preview(path, None, false, false, false, window, cx)
18594 })
18595 .ok()
18596 else {
18597 continue;
18598 };
18599 task.await.log_err();
18600 }
18601 }
18602 }
18603 })
18604 .detach();
18605 self.change_selections(None, window, cx, |selections| selections.refresh());
18606 }
18607
18608 pub fn to_pixel_point(
18609 &self,
18610 source: multi_buffer::Anchor,
18611 editor_snapshot: &EditorSnapshot,
18612 window: &mut Window,
18613 ) -> Option<gpui::Point<Pixels>> {
18614 let source_point = source.to_display_point(editor_snapshot);
18615 self.display_to_pixel_point(source_point, editor_snapshot, window)
18616 }
18617
18618 pub fn display_to_pixel_point(
18619 &self,
18620 source: DisplayPoint,
18621 editor_snapshot: &EditorSnapshot,
18622 window: &mut Window,
18623 ) -> Option<gpui::Point<Pixels>> {
18624 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18625 let text_layout_details = self.text_layout_details(window);
18626 let scroll_top = text_layout_details
18627 .scroll_anchor
18628 .scroll_position(editor_snapshot)
18629 .y;
18630
18631 if source.row().as_f32() < scroll_top.floor() {
18632 return None;
18633 }
18634 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18635 let source_y = line_height * (source.row().as_f32() - scroll_top);
18636 Some(gpui::Point::new(source_x, source_y))
18637 }
18638
18639 pub fn has_visible_completions_menu(&self) -> bool {
18640 !self.edit_prediction_preview_is_active()
18641 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18642 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18643 })
18644 }
18645
18646 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18647 self.addons
18648 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18649 }
18650
18651 pub fn unregister_addon<T: Addon>(&mut self) {
18652 self.addons.remove(&std::any::TypeId::of::<T>());
18653 }
18654
18655 pub fn addon<T: Addon>(&self) -> Option<&T> {
18656 let type_id = std::any::TypeId::of::<T>();
18657 self.addons
18658 .get(&type_id)
18659 .and_then(|item| item.to_any().downcast_ref::<T>())
18660 }
18661
18662 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18663 let type_id = std::any::TypeId::of::<T>();
18664 self.addons
18665 .get_mut(&type_id)
18666 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18667 }
18668
18669 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18670 let text_layout_details = self.text_layout_details(window);
18671 let style = &text_layout_details.editor_style;
18672 let font_id = window.text_system().resolve_font(&style.text.font());
18673 let font_size = style.text.font_size.to_pixels(window.rem_size());
18674 let line_height = style.text.line_height_in_pixels(window.rem_size());
18675 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18676
18677 gpui::Size::new(em_width, line_height)
18678 }
18679
18680 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18681 self.load_diff_task.clone()
18682 }
18683
18684 fn read_metadata_from_db(
18685 &mut self,
18686 item_id: u64,
18687 workspace_id: WorkspaceId,
18688 window: &mut Window,
18689 cx: &mut Context<Editor>,
18690 ) {
18691 if self.is_singleton(cx)
18692 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18693 {
18694 let buffer_snapshot = OnceCell::new();
18695
18696 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18697 if !folds.is_empty() {
18698 let snapshot =
18699 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18700 self.fold_ranges(
18701 folds
18702 .into_iter()
18703 .map(|(start, end)| {
18704 snapshot.clip_offset(start, Bias::Left)
18705 ..snapshot.clip_offset(end, Bias::Right)
18706 })
18707 .collect(),
18708 false,
18709 window,
18710 cx,
18711 );
18712 }
18713 }
18714
18715 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18716 if !selections.is_empty() {
18717 let snapshot =
18718 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18719 self.change_selections(None, window, cx, |s| {
18720 s.select_ranges(selections.into_iter().map(|(start, end)| {
18721 snapshot.clip_offset(start, Bias::Left)
18722 ..snapshot.clip_offset(end, Bias::Right)
18723 }));
18724 });
18725 }
18726 };
18727 }
18728
18729 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18730 }
18731}
18732
18733fn vim_enabled(cx: &App) -> bool {
18734 cx.global::<SettingsStore>()
18735 .raw_user_settings()
18736 .get("vim_mode")
18737 == Some(&serde_json::Value::Bool(true))
18738}
18739
18740// Consider user intent and default settings
18741fn choose_completion_range(
18742 completion: &Completion,
18743 intent: CompletionIntent,
18744 buffer: &Entity<Buffer>,
18745 cx: &mut Context<Editor>,
18746) -> Range<usize> {
18747 fn should_replace(
18748 completion: &Completion,
18749 insert_range: &Range<text::Anchor>,
18750 intent: CompletionIntent,
18751 completion_mode_setting: LspInsertMode,
18752 buffer: &Buffer,
18753 ) -> bool {
18754 // specific actions take precedence over settings
18755 match intent {
18756 CompletionIntent::CompleteWithInsert => return false,
18757 CompletionIntent::CompleteWithReplace => return true,
18758 CompletionIntent::Complete | CompletionIntent::Compose => {}
18759 }
18760
18761 match completion_mode_setting {
18762 LspInsertMode::Insert => false,
18763 LspInsertMode::Replace => true,
18764 LspInsertMode::ReplaceSubsequence => {
18765 let mut text_to_replace = buffer.chars_for_range(
18766 buffer.anchor_before(completion.replace_range.start)
18767 ..buffer.anchor_after(completion.replace_range.end),
18768 );
18769 let mut completion_text = completion.new_text.chars();
18770
18771 // is `text_to_replace` a subsequence of `completion_text`
18772 text_to_replace
18773 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18774 }
18775 LspInsertMode::ReplaceSuffix => {
18776 let range_after_cursor = insert_range.end..completion.replace_range.end;
18777
18778 let text_after_cursor = buffer
18779 .text_for_range(
18780 buffer.anchor_before(range_after_cursor.start)
18781 ..buffer.anchor_after(range_after_cursor.end),
18782 )
18783 .collect::<String>();
18784 completion.new_text.ends_with(&text_after_cursor)
18785 }
18786 }
18787 }
18788
18789 let buffer = buffer.read(cx);
18790
18791 if let CompletionSource::Lsp {
18792 insert_range: Some(insert_range),
18793 ..
18794 } = &completion.source
18795 {
18796 let completion_mode_setting =
18797 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18798 .completions
18799 .lsp_insert_mode;
18800
18801 if !should_replace(
18802 completion,
18803 &insert_range,
18804 intent,
18805 completion_mode_setting,
18806 buffer,
18807 ) {
18808 return insert_range.to_offset(buffer);
18809 }
18810 }
18811
18812 completion.replace_range.to_offset(buffer)
18813}
18814
18815fn insert_extra_newline_brackets(
18816 buffer: &MultiBufferSnapshot,
18817 range: Range<usize>,
18818 language: &language::LanguageScope,
18819) -> bool {
18820 let leading_whitespace_len = buffer
18821 .reversed_chars_at(range.start)
18822 .take_while(|c| c.is_whitespace() && *c != '\n')
18823 .map(|c| c.len_utf8())
18824 .sum::<usize>();
18825 let trailing_whitespace_len = buffer
18826 .chars_at(range.end)
18827 .take_while(|c| c.is_whitespace() && *c != '\n')
18828 .map(|c| c.len_utf8())
18829 .sum::<usize>();
18830 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18831
18832 language.brackets().any(|(pair, enabled)| {
18833 let pair_start = pair.start.trim_end();
18834 let pair_end = pair.end.trim_start();
18835
18836 enabled
18837 && pair.newline
18838 && buffer.contains_str_at(range.end, pair_end)
18839 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18840 })
18841}
18842
18843fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18844 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18845 [(buffer, range, _)] => (*buffer, range.clone()),
18846 _ => return false,
18847 };
18848 let pair = {
18849 let mut result: Option<BracketMatch> = None;
18850
18851 for pair in buffer
18852 .all_bracket_ranges(range.clone())
18853 .filter(move |pair| {
18854 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18855 })
18856 {
18857 let len = pair.close_range.end - pair.open_range.start;
18858
18859 if let Some(existing) = &result {
18860 let existing_len = existing.close_range.end - existing.open_range.start;
18861 if len > existing_len {
18862 continue;
18863 }
18864 }
18865
18866 result = Some(pair);
18867 }
18868
18869 result
18870 };
18871 let Some(pair) = pair else {
18872 return false;
18873 };
18874 pair.newline_only
18875 && buffer
18876 .chars_for_range(pair.open_range.end..range.start)
18877 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18878 .all(|c| c.is_whitespace() && c != '\n')
18879}
18880
18881fn get_uncommitted_diff_for_buffer(
18882 project: &Entity<Project>,
18883 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18884 buffer: Entity<MultiBuffer>,
18885 cx: &mut App,
18886) -> Task<()> {
18887 let mut tasks = Vec::new();
18888 project.update(cx, |project, cx| {
18889 for buffer in buffers {
18890 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18891 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18892 }
18893 }
18894 });
18895 cx.spawn(async move |cx| {
18896 let diffs = future::join_all(tasks).await;
18897 buffer
18898 .update(cx, |buffer, cx| {
18899 for diff in diffs.into_iter().flatten() {
18900 buffer.add_diff(diff, cx);
18901 }
18902 })
18903 .ok();
18904 })
18905}
18906
18907fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18908 let tab_size = tab_size.get() as usize;
18909 let mut width = offset;
18910
18911 for ch in text.chars() {
18912 width += if ch == '\t' {
18913 tab_size - (width % tab_size)
18914 } else {
18915 1
18916 };
18917 }
18918
18919 width - offset
18920}
18921
18922#[cfg(test)]
18923mod tests {
18924 use super::*;
18925
18926 #[test]
18927 fn test_string_size_with_expanded_tabs() {
18928 let nz = |val| NonZeroU32::new(val).unwrap();
18929 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18930 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18931 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18932 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18933 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18934 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18935 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18936 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18937 }
18938}
18939
18940/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18941struct WordBreakingTokenizer<'a> {
18942 input: &'a str,
18943}
18944
18945impl<'a> WordBreakingTokenizer<'a> {
18946 fn new(input: &'a str) -> Self {
18947 Self { input }
18948 }
18949}
18950
18951fn is_char_ideographic(ch: char) -> bool {
18952 use unicode_script::Script::*;
18953 use unicode_script::UnicodeScript;
18954 matches!(ch.script(), Han | Tangut | Yi)
18955}
18956
18957fn is_grapheme_ideographic(text: &str) -> bool {
18958 text.chars().any(is_char_ideographic)
18959}
18960
18961fn is_grapheme_whitespace(text: &str) -> bool {
18962 text.chars().any(|x| x.is_whitespace())
18963}
18964
18965fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18966 text.chars().next().map_or(false, |ch| {
18967 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18968 })
18969}
18970
18971#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18972enum WordBreakToken<'a> {
18973 Word { token: &'a str, grapheme_len: usize },
18974 InlineWhitespace { token: &'a str, grapheme_len: usize },
18975 Newline,
18976}
18977
18978impl<'a> Iterator for WordBreakingTokenizer<'a> {
18979 /// Yields a span, the count of graphemes in the token, and whether it was
18980 /// whitespace. Note that it also breaks at word boundaries.
18981 type Item = WordBreakToken<'a>;
18982
18983 fn next(&mut self) -> Option<Self::Item> {
18984 use unicode_segmentation::UnicodeSegmentation;
18985 if self.input.is_empty() {
18986 return None;
18987 }
18988
18989 let mut iter = self.input.graphemes(true).peekable();
18990 let mut offset = 0;
18991 let mut grapheme_len = 0;
18992 if let Some(first_grapheme) = iter.next() {
18993 let is_newline = first_grapheme == "\n";
18994 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18995 offset += first_grapheme.len();
18996 grapheme_len += 1;
18997 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18998 if let Some(grapheme) = iter.peek().copied() {
18999 if should_stay_with_preceding_ideograph(grapheme) {
19000 offset += grapheme.len();
19001 grapheme_len += 1;
19002 }
19003 }
19004 } else {
19005 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19006 let mut next_word_bound = words.peek().copied();
19007 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19008 next_word_bound = words.next();
19009 }
19010 while let Some(grapheme) = iter.peek().copied() {
19011 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19012 break;
19013 };
19014 if is_grapheme_whitespace(grapheme) != is_whitespace
19015 || (grapheme == "\n") != is_newline
19016 {
19017 break;
19018 };
19019 offset += grapheme.len();
19020 grapheme_len += 1;
19021 iter.next();
19022 }
19023 }
19024 let token = &self.input[..offset];
19025 self.input = &self.input[offset..];
19026 if token == "\n" {
19027 Some(WordBreakToken::Newline)
19028 } else if is_whitespace {
19029 Some(WordBreakToken::InlineWhitespace {
19030 token,
19031 grapheme_len,
19032 })
19033 } else {
19034 Some(WordBreakToken::Word {
19035 token,
19036 grapheme_len,
19037 })
19038 }
19039 } else {
19040 None
19041 }
19042 }
19043}
19044
19045#[test]
19046fn test_word_breaking_tokenizer() {
19047 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19048 ("", &[]),
19049 (" ", &[whitespace(" ", 2)]),
19050 ("Ʒ", &[word("Ʒ", 1)]),
19051 ("Ǽ", &[word("Ǽ", 1)]),
19052 ("⋑", &[word("⋑", 1)]),
19053 ("⋑⋑", &[word("⋑⋑", 2)]),
19054 (
19055 "原理,进而",
19056 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19057 ),
19058 (
19059 "hello world",
19060 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19061 ),
19062 (
19063 "hello, world",
19064 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19065 ),
19066 (
19067 " hello world",
19068 &[
19069 whitespace(" ", 2),
19070 word("hello", 5),
19071 whitespace(" ", 1),
19072 word("world", 5),
19073 ],
19074 ),
19075 (
19076 "这是什么 \n 钢笔",
19077 &[
19078 word("这", 1),
19079 word("是", 1),
19080 word("什", 1),
19081 word("么", 1),
19082 whitespace(" ", 1),
19083 newline(),
19084 whitespace(" ", 1),
19085 word("钢", 1),
19086 word("笔", 1),
19087 ],
19088 ),
19089 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19090 ];
19091
19092 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19093 WordBreakToken::Word {
19094 token,
19095 grapheme_len,
19096 }
19097 }
19098
19099 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19100 WordBreakToken::InlineWhitespace {
19101 token,
19102 grapheme_len,
19103 }
19104 }
19105
19106 fn newline() -> WordBreakToken<'static> {
19107 WordBreakToken::Newline
19108 }
19109
19110 for (input, result) in tests {
19111 assert_eq!(
19112 WordBreakingTokenizer::new(input)
19113 .collect::<Vec<_>>()
19114 .as_slice(),
19115 *result,
19116 );
19117 }
19118}
19119
19120fn wrap_with_prefix(
19121 line_prefix: String,
19122 unwrapped_text: String,
19123 wrap_column: usize,
19124 tab_size: NonZeroU32,
19125 preserve_existing_whitespace: bool,
19126) -> String {
19127 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19128 let mut wrapped_text = String::new();
19129 let mut current_line = line_prefix.clone();
19130
19131 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19132 let mut current_line_len = line_prefix_len;
19133 let mut in_whitespace = false;
19134 for token in tokenizer {
19135 let have_preceding_whitespace = in_whitespace;
19136 match token {
19137 WordBreakToken::Word {
19138 token,
19139 grapheme_len,
19140 } => {
19141 in_whitespace = false;
19142 if current_line_len + grapheme_len > wrap_column
19143 && current_line_len != line_prefix_len
19144 {
19145 wrapped_text.push_str(current_line.trim_end());
19146 wrapped_text.push('\n');
19147 current_line.truncate(line_prefix.len());
19148 current_line_len = line_prefix_len;
19149 }
19150 current_line.push_str(token);
19151 current_line_len += grapheme_len;
19152 }
19153 WordBreakToken::InlineWhitespace {
19154 mut token,
19155 mut grapheme_len,
19156 } => {
19157 in_whitespace = true;
19158 if have_preceding_whitespace && !preserve_existing_whitespace {
19159 continue;
19160 }
19161 if !preserve_existing_whitespace {
19162 token = " ";
19163 grapheme_len = 1;
19164 }
19165 if current_line_len + grapheme_len > wrap_column {
19166 wrapped_text.push_str(current_line.trim_end());
19167 wrapped_text.push('\n');
19168 current_line.truncate(line_prefix.len());
19169 current_line_len = line_prefix_len;
19170 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19171 current_line.push_str(token);
19172 current_line_len += grapheme_len;
19173 }
19174 }
19175 WordBreakToken::Newline => {
19176 in_whitespace = true;
19177 if preserve_existing_whitespace {
19178 wrapped_text.push_str(current_line.trim_end());
19179 wrapped_text.push('\n');
19180 current_line.truncate(line_prefix.len());
19181 current_line_len = line_prefix_len;
19182 } else if have_preceding_whitespace {
19183 continue;
19184 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19185 {
19186 wrapped_text.push_str(current_line.trim_end());
19187 wrapped_text.push('\n');
19188 current_line.truncate(line_prefix.len());
19189 current_line_len = line_prefix_len;
19190 } else if current_line_len != line_prefix_len {
19191 current_line.push(' ');
19192 current_line_len += 1;
19193 }
19194 }
19195 }
19196 }
19197
19198 if !current_line.is_empty() {
19199 wrapped_text.push_str(¤t_line);
19200 }
19201 wrapped_text
19202}
19203
19204#[test]
19205fn test_wrap_with_prefix() {
19206 assert_eq!(
19207 wrap_with_prefix(
19208 "# ".to_string(),
19209 "abcdefg".to_string(),
19210 4,
19211 NonZeroU32::new(4).unwrap(),
19212 false,
19213 ),
19214 "# abcdefg"
19215 );
19216 assert_eq!(
19217 wrap_with_prefix(
19218 "".to_string(),
19219 "\thello world".to_string(),
19220 8,
19221 NonZeroU32::new(4).unwrap(),
19222 false,
19223 ),
19224 "hello\nworld"
19225 );
19226 assert_eq!(
19227 wrap_with_prefix(
19228 "// ".to_string(),
19229 "xx \nyy zz aa bb cc".to_string(),
19230 12,
19231 NonZeroU32::new(4).unwrap(),
19232 false,
19233 ),
19234 "// xx yy zz\n// aa bb cc"
19235 );
19236 assert_eq!(
19237 wrap_with_prefix(
19238 String::new(),
19239 "这是什么 \n 钢笔".to_string(),
19240 3,
19241 NonZeroU32::new(4).unwrap(),
19242 false,
19243 ),
19244 "这是什\n么 钢\n笔"
19245 );
19246}
19247
19248pub trait CollaborationHub {
19249 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19250 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19251 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19252}
19253
19254impl CollaborationHub for Entity<Project> {
19255 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19256 self.read(cx).collaborators()
19257 }
19258
19259 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19260 self.read(cx).user_store().read(cx).participant_indices()
19261 }
19262
19263 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19264 let this = self.read(cx);
19265 let user_ids = this.collaborators().values().map(|c| c.user_id);
19266 this.user_store().read_with(cx, |user_store, cx| {
19267 user_store.participant_names(user_ids, cx)
19268 })
19269 }
19270}
19271
19272pub trait SemanticsProvider {
19273 fn hover(
19274 &self,
19275 buffer: &Entity<Buffer>,
19276 position: text::Anchor,
19277 cx: &mut App,
19278 ) -> Option<Task<Vec<project::Hover>>>;
19279
19280 fn inline_values(
19281 &self,
19282 buffer_handle: Entity<Buffer>,
19283 range: Range<text::Anchor>,
19284 cx: &mut App,
19285 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19286
19287 fn inlay_hints(
19288 &self,
19289 buffer_handle: Entity<Buffer>,
19290 range: Range<text::Anchor>,
19291 cx: &mut App,
19292 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19293
19294 fn resolve_inlay_hint(
19295 &self,
19296 hint: InlayHint,
19297 buffer_handle: Entity<Buffer>,
19298 server_id: LanguageServerId,
19299 cx: &mut App,
19300 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19301
19302 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19303
19304 fn document_highlights(
19305 &self,
19306 buffer: &Entity<Buffer>,
19307 position: text::Anchor,
19308 cx: &mut App,
19309 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19310
19311 fn definitions(
19312 &self,
19313 buffer: &Entity<Buffer>,
19314 position: text::Anchor,
19315 kind: GotoDefinitionKind,
19316 cx: &mut App,
19317 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19318
19319 fn range_for_rename(
19320 &self,
19321 buffer: &Entity<Buffer>,
19322 position: text::Anchor,
19323 cx: &mut App,
19324 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19325
19326 fn perform_rename(
19327 &self,
19328 buffer: &Entity<Buffer>,
19329 position: text::Anchor,
19330 new_name: String,
19331 cx: &mut App,
19332 ) -> Option<Task<Result<ProjectTransaction>>>;
19333}
19334
19335pub trait CompletionProvider {
19336 fn completions(
19337 &self,
19338 excerpt_id: ExcerptId,
19339 buffer: &Entity<Buffer>,
19340 buffer_position: text::Anchor,
19341 trigger: CompletionContext,
19342 window: &mut Window,
19343 cx: &mut Context<Editor>,
19344 ) -> Task<Result<Option<Vec<Completion>>>>;
19345
19346 fn resolve_completions(
19347 &self,
19348 buffer: Entity<Buffer>,
19349 completion_indices: Vec<usize>,
19350 completions: Rc<RefCell<Box<[Completion]>>>,
19351 cx: &mut Context<Editor>,
19352 ) -> Task<Result<bool>>;
19353
19354 fn apply_additional_edits_for_completion(
19355 &self,
19356 _buffer: Entity<Buffer>,
19357 _completions: Rc<RefCell<Box<[Completion]>>>,
19358 _completion_index: usize,
19359 _push_to_history: bool,
19360 _cx: &mut Context<Editor>,
19361 ) -> Task<Result<Option<language::Transaction>>> {
19362 Task::ready(Ok(None))
19363 }
19364
19365 fn is_completion_trigger(
19366 &self,
19367 buffer: &Entity<Buffer>,
19368 position: language::Anchor,
19369 text: &str,
19370 trigger_in_words: bool,
19371 cx: &mut Context<Editor>,
19372 ) -> bool;
19373
19374 fn sort_completions(&self) -> bool {
19375 true
19376 }
19377
19378 fn filter_completions(&self) -> bool {
19379 true
19380 }
19381}
19382
19383pub trait CodeActionProvider {
19384 fn id(&self) -> Arc<str>;
19385
19386 fn code_actions(
19387 &self,
19388 buffer: &Entity<Buffer>,
19389 range: Range<text::Anchor>,
19390 window: &mut Window,
19391 cx: &mut App,
19392 ) -> Task<Result<Vec<CodeAction>>>;
19393
19394 fn apply_code_action(
19395 &self,
19396 buffer_handle: Entity<Buffer>,
19397 action: CodeAction,
19398 excerpt_id: ExcerptId,
19399 push_to_history: bool,
19400 window: &mut Window,
19401 cx: &mut App,
19402 ) -> Task<Result<ProjectTransaction>>;
19403}
19404
19405impl CodeActionProvider for Entity<Project> {
19406 fn id(&self) -> Arc<str> {
19407 "project".into()
19408 }
19409
19410 fn code_actions(
19411 &self,
19412 buffer: &Entity<Buffer>,
19413 range: Range<text::Anchor>,
19414 _window: &mut Window,
19415 cx: &mut App,
19416 ) -> Task<Result<Vec<CodeAction>>> {
19417 self.update(cx, |project, cx| {
19418 let code_lens = project.code_lens(buffer, range.clone(), cx);
19419 let code_actions = project.code_actions(buffer, range, None, cx);
19420 cx.background_spawn(async move {
19421 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19422 Ok(code_lens
19423 .context("code lens fetch")?
19424 .into_iter()
19425 .chain(code_actions.context("code action fetch")?)
19426 .collect())
19427 })
19428 })
19429 }
19430
19431 fn apply_code_action(
19432 &self,
19433 buffer_handle: Entity<Buffer>,
19434 action: CodeAction,
19435 _excerpt_id: ExcerptId,
19436 push_to_history: bool,
19437 _window: &mut Window,
19438 cx: &mut App,
19439 ) -> Task<Result<ProjectTransaction>> {
19440 self.update(cx, |project, cx| {
19441 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19442 })
19443 }
19444}
19445
19446fn snippet_completions(
19447 project: &Project,
19448 buffer: &Entity<Buffer>,
19449 buffer_position: text::Anchor,
19450 cx: &mut App,
19451) -> Task<Result<Vec<Completion>>> {
19452 let languages = buffer.read(cx).languages_at(buffer_position);
19453 let snippet_store = project.snippets().read(cx);
19454
19455 let scopes: Vec<_> = languages
19456 .iter()
19457 .filter_map(|language| {
19458 let language_name = language.lsp_id();
19459 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19460
19461 if snippets.is_empty() {
19462 None
19463 } else {
19464 Some((language.default_scope(), snippets))
19465 }
19466 })
19467 .collect();
19468
19469 if scopes.is_empty() {
19470 return Task::ready(Ok(vec![]));
19471 }
19472
19473 let snapshot = buffer.read(cx).text_snapshot();
19474 let chars: String = snapshot
19475 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19476 .collect();
19477 let executor = cx.background_executor().clone();
19478
19479 cx.background_spawn(async move {
19480 let mut all_results: Vec<Completion> = Vec::new();
19481 for (scope, snippets) in scopes.into_iter() {
19482 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19483 let mut last_word = chars
19484 .chars()
19485 .take_while(|c| classifier.is_word(*c))
19486 .collect::<String>();
19487 last_word = last_word.chars().rev().collect();
19488
19489 if last_word.is_empty() {
19490 return Ok(vec![]);
19491 }
19492
19493 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19494 let to_lsp = |point: &text::Anchor| {
19495 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19496 point_to_lsp(end)
19497 };
19498 let lsp_end = to_lsp(&buffer_position);
19499
19500 let candidates = snippets
19501 .iter()
19502 .enumerate()
19503 .flat_map(|(ix, snippet)| {
19504 snippet
19505 .prefix
19506 .iter()
19507 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19508 })
19509 .collect::<Vec<StringMatchCandidate>>();
19510
19511 let mut matches = fuzzy::match_strings(
19512 &candidates,
19513 &last_word,
19514 last_word.chars().any(|c| c.is_uppercase()),
19515 100,
19516 &Default::default(),
19517 executor.clone(),
19518 )
19519 .await;
19520
19521 // Remove all candidates where the query's start does not match the start of any word in the candidate
19522 if let Some(query_start) = last_word.chars().next() {
19523 matches.retain(|string_match| {
19524 split_words(&string_match.string).any(|word| {
19525 // Check that the first codepoint of the word as lowercase matches the first
19526 // codepoint of the query as lowercase
19527 word.chars()
19528 .flat_map(|codepoint| codepoint.to_lowercase())
19529 .zip(query_start.to_lowercase())
19530 .all(|(word_cp, query_cp)| word_cp == query_cp)
19531 })
19532 });
19533 }
19534
19535 let matched_strings = matches
19536 .into_iter()
19537 .map(|m| m.string)
19538 .collect::<HashSet<_>>();
19539
19540 let mut result: Vec<Completion> = snippets
19541 .iter()
19542 .filter_map(|snippet| {
19543 let matching_prefix = snippet
19544 .prefix
19545 .iter()
19546 .find(|prefix| matched_strings.contains(*prefix))?;
19547 let start = as_offset - last_word.len();
19548 let start = snapshot.anchor_before(start);
19549 let range = start..buffer_position;
19550 let lsp_start = to_lsp(&start);
19551 let lsp_range = lsp::Range {
19552 start: lsp_start,
19553 end: lsp_end,
19554 };
19555 Some(Completion {
19556 replace_range: range,
19557 new_text: snippet.body.clone(),
19558 source: CompletionSource::Lsp {
19559 insert_range: None,
19560 server_id: LanguageServerId(usize::MAX),
19561 resolved: true,
19562 lsp_completion: Box::new(lsp::CompletionItem {
19563 label: snippet.prefix.first().unwrap().clone(),
19564 kind: Some(CompletionItemKind::SNIPPET),
19565 label_details: snippet.description.as_ref().map(|description| {
19566 lsp::CompletionItemLabelDetails {
19567 detail: Some(description.clone()),
19568 description: None,
19569 }
19570 }),
19571 insert_text_format: Some(InsertTextFormat::SNIPPET),
19572 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19573 lsp::InsertReplaceEdit {
19574 new_text: snippet.body.clone(),
19575 insert: lsp_range,
19576 replace: lsp_range,
19577 },
19578 )),
19579 filter_text: Some(snippet.body.clone()),
19580 sort_text: Some(char::MAX.to_string()),
19581 ..lsp::CompletionItem::default()
19582 }),
19583 lsp_defaults: None,
19584 },
19585 label: CodeLabel {
19586 text: matching_prefix.clone(),
19587 runs: Vec::new(),
19588 filter_range: 0..matching_prefix.len(),
19589 },
19590 icon_path: None,
19591 documentation: snippet.description.clone().map(|description| {
19592 CompletionDocumentation::SingleLine(description.into())
19593 }),
19594 insert_text_mode: None,
19595 confirm: None,
19596 })
19597 })
19598 .collect();
19599
19600 all_results.append(&mut result);
19601 }
19602
19603 Ok(all_results)
19604 })
19605}
19606
19607impl CompletionProvider for Entity<Project> {
19608 fn completions(
19609 &self,
19610 _excerpt_id: ExcerptId,
19611 buffer: &Entity<Buffer>,
19612 buffer_position: text::Anchor,
19613 options: CompletionContext,
19614 _window: &mut Window,
19615 cx: &mut Context<Editor>,
19616 ) -> Task<Result<Option<Vec<Completion>>>> {
19617 self.update(cx, |project, cx| {
19618 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19619 let project_completions = project.completions(buffer, buffer_position, options, cx);
19620 cx.background_spawn(async move {
19621 let snippets_completions = snippets.await?;
19622 match project_completions.await? {
19623 Some(mut completions) => {
19624 completions.extend(snippets_completions);
19625 Ok(Some(completions))
19626 }
19627 None => {
19628 if snippets_completions.is_empty() {
19629 Ok(None)
19630 } else {
19631 Ok(Some(snippets_completions))
19632 }
19633 }
19634 }
19635 })
19636 })
19637 }
19638
19639 fn resolve_completions(
19640 &self,
19641 buffer: Entity<Buffer>,
19642 completion_indices: Vec<usize>,
19643 completions: Rc<RefCell<Box<[Completion]>>>,
19644 cx: &mut Context<Editor>,
19645 ) -> Task<Result<bool>> {
19646 self.update(cx, |project, cx| {
19647 project.lsp_store().update(cx, |lsp_store, cx| {
19648 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19649 })
19650 })
19651 }
19652
19653 fn apply_additional_edits_for_completion(
19654 &self,
19655 buffer: Entity<Buffer>,
19656 completions: Rc<RefCell<Box<[Completion]>>>,
19657 completion_index: usize,
19658 push_to_history: bool,
19659 cx: &mut Context<Editor>,
19660 ) -> Task<Result<Option<language::Transaction>>> {
19661 self.update(cx, |project, cx| {
19662 project.lsp_store().update(cx, |lsp_store, cx| {
19663 lsp_store.apply_additional_edits_for_completion(
19664 buffer,
19665 completions,
19666 completion_index,
19667 push_to_history,
19668 cx,
19669 )
19670 })
19671 })
19672 }
19673
19674 fn is_completion_trigger(
19675 &self,
19676 buffer: &Entity<Buffer>,
19677 position: language::Anchor,
19678 text: &str,
19679 trigger_in_words: bool,
19680 cx: &mut Context<Editor>,
19681 ) -> bool {
19682 let mut chars = text.chars();
19683 let char = if let Some(char) = chars.next() {
19684 char
19685 } else {
19686 return false;
19687 };
19688 if chars.next().is_some() {
19689 return false;
19690 }
19691
19692 let buffer = buffer.read(cx);
19693 let snapshot = buffer.snapshot();
19694 if !snapshot.settings_at(position, cx).show_completions_on_input {
19695 return false;
19696 }
19697 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19698 if trigger_in_words && classifier.is_word(char) {
19699 return true;
19700 }
19701
19702 buffer.completion_triggers().contains(text)
19703 }
19704}
19705
19706impl SemanticsProvider for Entity<Project> {
19707 fn hover(
19708 &self,
19709 buffer: &Entity<Buffer>,
19710 position: text::Anchor,
19711 cx: &mut App,
19712 ) -> Option<Task<Vec<project::Hover>>> {
19713 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19714 }
19715
19716 fn document_highlights(
19717 &self,
19718 buffer: &Entity<Buffer>,
19719 position: text::Anchor,
19720 cx: &mut App,
19721 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19722 Some(self.update(cx, |project, cx| {
19723 project.document_highlights(buffer, position, cx)
19724 }))
19725 }
19726
19727 fn definitions(
19728 &self,
19729 buffer: &Entity<Buffer>,
19730 position: text::Anchor,
19731 kind: GotoDefinitionKind,
19732 cx: &mut App,
19733 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19734 Some(self.update(cx, |project, cx| match kind {
19735 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19736 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19737 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19738 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19739 }))
19740 }
19741
19742 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19743 // TODO: make this work for remote projects
19744 self.update(cx, |project, cx| {
19745 if project
19746 .active_debug_session(cx)
19747 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19748 {
19749 return true;
19750 }
19751
19752 buffer.update(cx, |buffer, cx| {
19753 project.any_language_server_supports_inlay_hints(buffer, cx)
19754 })
19755 })
19756 }
19757
19758 fn inline_values(
19759 &self,
19760 buffer_handle: Entity<Buffer>,
19761 range: Range<text::Anchor>,
19762 cx: &mut App,
19763 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19764 self.update(cx, |project, cx| {
19765 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19766
19767 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19768 })
19769 }
19770
19771 fn inlay_hints(
19772 &self,
19773 buffer_handle: Entity<Buffer>,
19774 range: Range<text::Anchor>,
19775 cx: &mut App,
19776 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19777 Some(self.update(cx, |project, cx| {
19778 project.inlay_hints(buffer_handle, range, cx)
19779 }))
19780 }
19781
19782 fn resolve_inlay_hint(
19783 &self,
19784 hint: InlayHint,
19785 buffer_handle: Entity<Buffer>,
19786 server_id: LanguageServerId,
19787 cx: &mut App,
19788 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19789 Some(self.update(cx, |project, cx| {
19790 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19791 }))
19792 }
19793
19794 fn range_for_rename(
19795 &self,
19796 buffer: &Entity<Buffer>,
19797 position: text::Anchor,
19798 cx: &mut App,
19799 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19800 Some(self.update(cx, |project, cx| {
19801 let buffer = buffer.clone();
19802 let task = project.prepare_rename(buffer.clone(), position, cx);
19803 cx.spawn(async move |_, cx| {
19804 Ok(match task.await? {
19805 PrepareRenameResponse::Success(range) => Some(range),
19806 PrepareRenameResponse::InvalidPosition => None,
19807 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19808 // Fallback on using TreeSitter info to determine identifier range
19809 buffer.update(cx, |buffer, _| {
19810 let snapshot = buffer.snapshot();
19811 let (range, kind) = snapshot.surrounding_word(position);
19812 if kind != Some(CharKind::Word) {
19813 return None;
19814 }
19815 Some(
19816 snapshot.anchor_before(range.start)
19817 ..snapshot.anchor_after(range.end),
19818 )
19819 })?
19820 }
19821 })
19822 })
19823 }))
19824 }
19825
19826 fn perform_rename(
19827 &self,
19828 buffer: &Entity<Buffer>,
19829 position: text::Anchor,
19830 new_name: String,
19831 cx: &mut App,
19832 ) -> Option<Task<Result<ProjectTransaction>>> {
19833 Some(self.update(cx, |project, cx| {
19834 project.perform_rename(buffer.clone(), position, new_name, cx)
19835 }))
19836 }
19837}
19838
19839fn inlay_hint_settings(
19840 location: Anchor,
19841 snapshot: &MultiBufferSnapshot,
19842 cx: &mut Context<Editor>,
19843) -> InlayHintSettings {
19844 let file = snapshot.file_at(location);
19845 let language = snapshot.language_at(location).map(|l| l.name());
19846 language_settings(language, file, cx).inlay_hints
19847}
19848
19849fn consume_contiguous_rows(
19850 contiguous_row_selections: &mut Vec<Selection<Point>>,
19851 selection: &Selection<Point>,
19852 display_map: &DisplaySnapshot,
19853 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19854) -> (MultiBufferRow, MultiBufferRow) {
19855 contiguous_row_selections.push(selection.clone());
19856 let start_row = MultiBufferRow(selection.start.row);
19857 let mut end_row = ending_row(selection, display_map);
19858
19859 while let Some(next_selection) = selections.peek() {
19860 if next_selection.start.row <= end_row.0 {
19861 end_row = ending_row(next_selection, display_map);
19862 contiguous_row_selections.push(selections.next().unwrap().clone());
19863 } else {
19864 break;
19865 }
19866 }
19867 (start_row, end_row)
19868}
19869
19870fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19871 if next_selection.end.column > 0 || next_selection.is_empty() {
19872 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19873 } else {
19874 MultiBufferRow(next_selection.end.row)
19875 }
19876}
19877
19878impl EditorSnapshot {
19879 pub fn remote_selections_in_range<'a>(
19880 &'a self,
19881 range: &'a Range<Anchor>,
19882 collaboration_hub: &dyn CollaborationHub,
19883 cx: &'a App,
19884 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19885 let participant_names = collaboration_hub.user_names(cx);
19886 let participant_indices = collaboration_hub.user_participant_indices(cx);
19887 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19888 let collaborators_by_replica_id = collaborators_by_peer_id
19889 .iter()
19890 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19891 .collect::<HashMap<_, _>>();
19892 self.buffer_snapshot
19893 .selections_in_range(range, false)
19894 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19895 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19896 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19897 let user_name = participant_names.get(&collaborator.user_id).cloned();
19898 Some(RemoteSelection {
19899 replica_id,
19900 selection,
19901 cursor_shape,
19902 line_mode,
19903 participant_index,
19904 peer_id: collaborator.peer_id,
19905 user_name,
19906 })
19907 })
19908 }
19909
19910 pub fn hunks_for_ranges(
19911 &self,
19912 ranges: impl IntoIterator<Item = Range<Point>>,
19913 ) -> Vec<MultiBufferDiffHunk> {
19914 let mut hunks = Vec::new();
19915 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19916 HashMap::default();
19917 for query_range in ranges {
19918 let query_rows =
19919 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19920 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19921 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19922 ) {
19923 // Include deleted hunks that are adjacent to the query range, because
19924 // otherwise they would be missed.
19925 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19926 if hunk.status().is_deleted() {
19927 intersects_range |= hunk.row_range.start == query_rows.end;
19928 intersects_range |= hunk.row_range.end == query_rows.start;
19929 }
19930 if intersects_range {
19931 if !processed_buffer_rows
19932 .entry(hunk.buffer_id)
19933 .or_default()
19934 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19935 {
19936 continue;
19937 }
19938 hunks.push(hunk);
19939 }
19940 }
19941 }
19942
19943 hunks
19944 }
19945
19946 fn display_diff_hunks_for_rows<'a>(
19947 &'a self,
19948 display_rows: Range<DisplayRow>,
19949 folded_buffers: &'a HashSet<BufferId>,
19950 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19951 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19952 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19953
19954 self.buffer_snapshot
19955 .diff_hunks_in_range(buffer_start..buffer_end)
19956 .filter_map(|hunk| {
19957 if folded_buffers.contains(&hunk.buffer_id) {
19958 return None;
19959 }
19960
19961 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19962 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19963
19964 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19965 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19966
19967 let display_hunk = if hunk_display_start.column() != 0 {
19968 DisplayDiffHunk::Folded {
19969 display_row: hunk_display_start.row(),
19970 }
19971 } else {
19972 let mut end_row = hunk_display_end.row();
19973 if hunk_display_end.column() > 0 {
19974 end_row.0 += 1;
19975 }
19976 let is_created_file = hunk.is_created_file();
19977 DisplayDiffHunk::Unfolded {
19978 status: hunk.status(),
19979 diff_base_byte_range: hunk.diff_base_byte_range,
19980 display_row_range: hunk_display_start.row()..end_row,
19981 multi_buffer_range: Anchor::range_in_buffer(
19982 hunk.excerpt_id,
19983 hunk.buffer_id,
19984 hunk.buffer_range,
19985 ),
19986 is_created_file,
19987 }
19988 };
19989
19990 Some(display_hunk)
19991 })
19992 }
19993
19994 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19995 self.display_snapshot.buffer_snapshot.language_at(position)
19996 }
19997
19998 pub fn is_focused(&self) -> bool {
19999 self.is_focused
20000 }
20001
20002 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20003 self.placeholder_text.as_ref()
20004 }
20005
20006 pub fn scroll_position(&self) -> gpui::Point<f32> {
20007 self.scroll_anchor.scroll_position(&self.display_snapshot)
20008 }
20009
20010 fn gutter_dimensions(
20011 &self,
20012 font_id: FontId,
20013 font_size: Pixels,
20014 max_line_number_width: Pixels,
20015 cx: &App,
20016 ) -> Option<GutterDimensions> {
20017 if !self.show_gutter {
20018 return None;
20019 }
20020
20021 let descent = cx.text_system().descent(font_id, font_size);
20022 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20023 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20024
20025 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20026 matches!(
20027 ProjectSettings::get_global(cx).git.git_gutter,
20028 Some(GitGutterSetting::TrackedFiles)
20029 )
20030 });
20031 let gutter_settings = EditorSettings::get_global(cx).gutter;
20032 let show_line_numbers = self
20033 .show_line_numbers
20034 .unwrap_or(gutter_settings.line_numbers);
20035 let line_gutter_width = if show_line_numbers {
20036 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20037 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20038 max_line_number_width.max(min_width_for_number_on_gutter)
20039 } else {
20040 0.0.into()
20041 };
20042
20043 let show_code_actions = self
20044 .show_code_actions
20045 .unwrap_or(gutter_settings.code_actions);
20046
20047 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20048 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20049
20050 let git_blame_entries_width =
20051 self.git_blame_gutter_max_author_length
20052 .map(|max_author_length| {
20053 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20054 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20055
20056 /// The number of characters to dedicate to gaps and margins.
20057 const SPACING_WIDTH: usize = 4;
20058
20059 let max_char_count = max_author_length.min(renderer.max_author_length())
20060 + ::git::SHORT_SHA_LENGTH
20061 + MAX_RELATIVE_TIMESTAMP.len()
20062 + SPACING_WIDTH;
20063
20064 em_advance * max_char_count
20065 });
20066
20067 let is_singleton = self.buffer_snapshot.is_singleton();
20068
20069 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20070 left_padding += if !is_singleton {
20071 em_width * 4.0
20072 } else if show_code_actions || show_runnables || show_breakpoints {
20073 em_width * 3.0
20074 } else if show_git_gutter && show_line_numbers {
20075 em_width * 2.0
20076 } else if show_git_gutter || show_line_numbers {
20077 em_width
20078 } else {
20079 px(0.)
20080 };
20081
20082 let shows_folds = is_singleton && gutter_settings.folds;
20083
20084 let right_padding = if shows_folds && show_line_numbers {
20085 em_width * 4.0
20086 } else if shows_folds || (!is_singleton && show_line_numbers) {
20087 em_width * 3.0
20088 } else if show_line_numbers {
20089 em_width
20090 } else {
20091 px(0.)
20092 };
20093
20094 Some(GutterDimensions {
20095 left_padding,
20096 right_padding,
20097 width: line_gutter_width + left_padding + right_padding,
20098 margin: -descent,
20099 git_blame_entries_width,
20100 })
20101 }
20102
20103 pub fn render_crease_toggle(
20104 &self,
20105 buffer_row: MultiBufferRow,
20106 row_contains_cursor: bool,
20107 editor: Entity<Editor>,
20108 window: &mut Window,
20109 cx: &mut App,
20110 ) -> Option<AnyElement> {
20111 let folded = self.is_line_folded(buffer_row);
20112 let mut is_foldable = false;
20113
20114 if let Some(crease) = self
20115 .crease_snapshot
20116 .query_row(buffer_row, &self.buffer_snapshot)
20117 {
20118 is_foldable = true;
20119 match crease {
20120 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20121 if let Some(render_toggle) = render_toggle {
20122 let toggle_callback =
20123 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20124 if folded {
20125 editor.update(cx, |editor, cx| {
20126 editor.fold_at(buffer_row, window, cx)
20127 });
20128 } else {
20129 editor.update(cx, |editor, cx| {
20130 editor.unfold_at(buffer_row, window, cx)
20131 });
20132 }
20133 });
20134 return Some((render_toggle)(
20135 buffer_row,
20136 folded,
20137 toggle_callback,
20138 window,
20139 cx,
20140 ));
20141 }
20142 }
20143 }
20144 }
20145
20146 is_foldable |= self.starts_indent(buffer_row);
20147
20148 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20149 Some(
20150 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20151 .toggle_state(folded)
20152 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20153 if folded {
20154 this.unfold_at(buffer_row, window, cx);
20155 } else {
20156 this.fold_at(buffer_row, window, cx);
20157 }
20158 }))
20159 .into_any_element(),
20160 )
20161 } else {
20162 None
20163 }
20164 }
20165
20166 pub fn render_crease_trailer(
20167 &self,
20168 buffer_row: MultiBufferRow,
20169 window: &mut Window,
20170 cx: &mut App,
20171 ) -> Option<AnyElement> {
20172 let folded = self.is_line_folded(buffer_row);
20173 if let Crease::Inline { render_trailer, .. } = self
20174 .crease_snapshot
20175 .query_row(buffer_row, &self.buffer_snapshot)?
20176 {
20177 let render_trailer = render_trailer.as_ref()?;
20178 Some(render_trailer(buffer_row, folded, window, cx))
20179 } else {
20180 None
20181 }
20182 }
20183}
20184
20185impl Deref for EditorSnapshot {
20186 type Target = DisplaySnapshot;
20187
20188 fn deref(&self) -> &Self::Target {
20189 &self.display_snapshot
20190 }
20191}
20192
20193#[derive(Clone, Debug, PartialEq, Eq)]
20194pub enum EditorEvent {
20195 InputIgnored {
20196 text: Arc<str>,
20197 },
20198 InputHandled {
20199 utf16_range_to_replace: Option<Range<isize>>,
20200 text: Arc<str>,
20201 },
20202 ExcerptsAdded {
20203 buffer: Entity<Buffer>,
20204 predecessor: ExcerptId,
20205 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20206 },
20207 ExcerptsRemoved {
20208 ids: Vec<ExcerptId>,
20209 removed_buffer_ids: Vec<BufferId>,
20210 },
20211 BufferFoldToggled {
20212 ids: Vec<ExcerptId>,
20213 folded: bool,
20214 },
20215 ExcerptsEdited {
20216 ids: Vec<ExcerptId>,
20217 },
20218 ExcerptsExpanded {
20219 ids: Vec<ExcerptId>,
20220 },
20221 BufferEdited,
20222 Edited {
20223 transaction_id: clock::Lamport,
20224 },
20225 Reparsed(BufferId),
20226 Focused,
20227 FocusedIn,
20228 Blurred,
20229 DirtyChanged,
20230 Saved,
20231 TitleChanged,
20232 DiffBaseChanged,
20233 SelectionsChanged {
20234 local: bool,
20235 },
20236 ScrollPositionChanged {
20237 local: bool,
20238 autoscroll: bool,
20239 },
20240 Closed,
20241 TransactionUndone {
20242 transaction_id: clock::Lamport,
20243 },
20244 TransactionBegun {
20245 transaction_id: clock::Lamport,
20246 },
20247 Reloaded,
20248 CursorShapeChanged,
20249 PushedToNavHistory {
20250 anchor: Anchor,
20251 is_deactivate: bool,
20252 },
20253}
20254
20255impl EventEmitter<EditorEvent> for Editor {}
20256
20257impl Focusable for Editor {
20258 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20259 self.focus_handle.clone()
20260 }
20261}
20262
20263impl Render for Editor {
20264 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20265 let settings = ThemeSettings::get_global(cx);
20266
20267 let mut text_style = match self.mode {
20268 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20269 color: cx.theme().colors().editor_foreground,
20270 font_family: settings.ui_font.family.clone(),
20271 font_features: settings.ui_font.features.clone(),
20272 font_fallbacks: settings.ui_font.fallbacks.clone(),
20273 font_size: rems(0.875).into(),
20274 font_weight: settings.ui_font.weight,
20275 line_height: relative(settings.buffer_line_height.value()),
20276 ..Default::default()
20277 },
20278 EditorMode::Full { .. } => TextStyle {
20279 color: cx.theme().colors().editor_foreground,
20280 font_family: settings.buffer_font.family.clone(),
20281 font_features: settings.buffer_font.features.clone(),
20282 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20283 font_size: settings.buffer_font_size(cx).into(),
20284 font_weight: settings.buffer_font.weight,
20285 line_height: relative(settings.buffer_line_height.value()),
20286 ..Default::default()
20287 },
20288 };
20289 if let Some(text_style_refinement) = &self.text_style_refinement {
20290 text_style.refine(text_style_refinement)
20291 }
20292
20293 let background = match self.mode {
20294 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20295 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20296 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20297 };
20298
20299 EditorElement::new(
20300 &cx.entity(),
20301 EditorStyle {
20302 background,
20303 local_player: cx.theme().players().local(),
20304 text: text_style,
20305 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20306 syntax: cx.theme().syntax().clone(),
20307 status: cx.theme().status().clone(),
20308 inlay_hints_style: make_inlay_hints_style(cx),
20309 inline_completion_styles: make_suggestion_styles(cx),
20310 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20311 },
20312 )
20313 }
20314}
20315
20316impl EntityInputHandler for Editor {
20317 fn text_for_range(
20318 &mut self,
20319 range_utf16: Range<usize>,
20320 adjusted_range: &mut Option<Range<usize>>,
20321 _: &mut Window,
20322 cx: &mut Context<Self>,
20323 ) -> Option<String> {
20324 let snapshot = self.buffer.read(cx).read(cx);
20325 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20326 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20327 if (start.0..end.0) != range_utf16 {
20328 adjusted_range.replace(start.0..end.0);
20329 }
20330 Some(snapshot.text_for_range(start..end).collect())
20331 }
20332
20333 fn selected_text_range(
20334 &mut self,
20335 ignore_disabled_input: bool,
20336 _: &mut Window,
20337 cx: &mut Context<Self>,
20338 ) -> Option<UTF16Selection> {
20339 // Prevent the IME menu from appearing when holding down an alphabetic key
20340 // while input is disabled.
20341 if !ignore_disabled_input && !self.input_enabled {
20342 return None;
20343 }
20344
20345 let selection = self.selections.newest::<OffsetUtf16>(cx);
20346 let range = selection.range();
20347
20348 Some(UTF16Selection {
20349 range: range.start.0..range.end.0,
20350 reversed: selection.reversed,
20351 })
20352 }
20353
20354 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20355 let snapshot = self.buffer.read(cx).read(cx);
20356 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20357 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20358 }
20359
20360 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20361 self.clear_highlights::<InputComposition>(cx);
20362 self.ime_transaction.take();
20363 }
20364
20365 fn replace_text_in_range(
20366 &mut self,
20367 range_utf16: Option<Range<usize>>,
20368 text: &str,
20369 window: &mut Window,
20370 cx: &mut Context<Self>,
20371 ) {
20372 if !self.input_enabled {
20373 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20374 return;
20375 }
20376
20377 self.transact(window, cx, |this, window, cx| {
20378 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20379 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20380 Some(this.selection_replacement_ranges(range_utf16, cx))
20381 } else {
20382 this.marked_text_ranges(cx)
20383 };
20384
20385 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20386 let newest_selection_id = this.selections.newest_anchor().id;
20387 this.selections
20388 .all::<OffsetUtf16>(cx)
20389 .iter()
20390 .zip(ranges_to_replace.iter())
20391 .find_map(|(selection, range)| {
20392 if selection.id == newest_selection_id {
20393 Some(
20394 (range.start.0 as isize - selection.head().0 as isize)
20395 ..(range.end.0 as isize - selection.head().0 as isize),
20396 )
20397 } else {
20398 None
20399 }
20400 })
20401 });
20402
20403 cx.emit(EditorEvent::InputHandled {
20404 utf16_range_to_replace: range_to_replace,
20405 text: text.into(),
20406 });
20407
20408 if let Some(new_selected_ranges) = new_selected_ranges {
20409 this.change_selections(None, window, cx, |selections| {
20410 selections.select_ranges(new_selected_ranges)
20411 });
20412 this.backspace(&Default::default(), window, cx);
20413 }
20414
20415 this.handle_input(text, window, cx);
20416 });
20417
20418 if let Some(transaction) = self.ime_transaction {
20419 self.buffer.update(cx, |buffer, cx| {
20420 buffer.group_until_transaction(transaction, cx);
20421 });
20422 }
20423
20424 self.unmark_text(window, cx);
20425 }
20426
20427 fn replace_and_mark_text_in_range(
20428 &mut self,
20429 range_utf16: Option<Range<usize>>,
20430 text: &str,
20431 new_selected_range_utf16: Option<Range<usize>>,
20432 window: &mut Window,
20433 cx: &mut Context<Self>,
20434 ) {
20435 if !self.input_enabled {
20436 return;
20437 }
20438
20439 let transaction = self.transact(window, cx, |this, window, cx| {
20440 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20441 let snapshot = this.buffer.read(cx).read(cx);
20442 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20443 for marked_range in &mut marked_ranges {
20444 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20445 marked_range.start.0 += relative_range_utf16.start;
20446 marked_range.start =
20447 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20448 marked_range.end =
20449 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20450 }
20451 }
20452 Some(marked_ranges)
20453 } else if let Some(range_utf16) = range_utf16 {
20454 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20455 Some(this.selection_replacement_ranges(range_utf16, cx))
20456 } else {
20457 None
20458 };
20459
20460 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20461 let newest_selection_id = this.selections.newest_anchor().id;
20462 this.selections
20463 .all::<OffsetUtf16>(cx)
20464 .iter()
20465 .zip(ranges_to_replace.iter())
20466 .find_map(|(selection, range)| {
20467 if selection.id == newest_selection_id {
20468 Some(
20469 (range.start.0 as isize - selection.head().0 as isize)
20470 ..(range.end.0 as isize - selection.head().0 as isize),
20471 )
20472 } else {
20473 None
20474 }
20475 })
20476 });
20477
20478 cx.emit(EditorEvent::InputHandled {
20479 utf16_range_to_replace: range_to_replace,
20480 text: text.into(),
20481 });
20482
20483 if let Some(ranges) = ranges_to_replace {
20484 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20485 }
20486
20487 let marked_ranges = {
20488 let snapshot = this.buffer.read(cx).read(cx);
20489 this.selections
20490 .disjoint_anchors()
20491 .iter()
20492 .map(|selection| {
20493 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20494 })
20495 .collect::<Vec<_>>()
20496 };
20497
20498 if text.is_empty() {
20499 this.unmark_text(window, cx);
20500 } else {
20501 this.highlight_text::<InputComposition>(
20502 marked_ranges.clone(),
20503 HighlightStyle {
20504 underline: Some(UnderlineStyle {
20505 thickness: px(1.),
20506 color: None,
20507 wavy: false,
20508 }),
20509 ..Default::default()
20510 },
20511 cx,
20512 );
20513 }
20514
20515 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20516 let use_autoclose = this.use_autoclose;
20517 let use_auto_surround = this.use_auto_surround;
20518 this.set_use_autoclose(false);
20519 this.set_use_auto_surround(false);
20520 this.handle_input(text, window, cx);
20521 this.set_use_autoclose(use_autoclose);
20522 this.set_use_auto_surround(use_auto_surround);
20523
20524 if let Some(new_selected_range) = new_selected_range_utf16 {
20525 let snapshot = this.buffer.read(cx).read(cx);
20526 let new_selected_ranges = marked_ranges
20527 .into_iter()
20528 .map(|marked_range| {
20529 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20530 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20531 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20532 snapshot.clip_offset_utf16(new_start, Bias::Left)
20533 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20534 })
20535 .collect::<Vec<_>>();
20536
20537 drop(snapshot);
20538 this.change_selections(None, window, cx, |selections| {
20539 selections.select_ranges(new_selected_ranges)
20540 });
20541 }
20542 });
20543
20544 self.ime_transaction = self.ime_transaction.or(transaction);
20545 if let Some(transaction) = self.ime_transaction {
20546 self.buffer.update(cx, |buffer, cx| {
20547 buffer.group_until_transaction(transaction, cx);
20548 });
20549 }
20550
20551 if self.text_highlights::<InputComposition>(cx).is_none() {
20552 self.ime_transaction.take();
20553 }
20554 }
20555
20556 fn bounds_for_range(
20557 &mut self,
20558 range_utf16: Range<usize>,
20559 element_bounds: gpui::Bounds<Pixels>,
20560 window: &mut Window,
20561 cx: &mut Context<Self>,
20562 ) -> Option<gpui::Bounds<Pixels>> {
20563 let text_layout_details = self.text_layout_details(window);
20564 let gpui::Size {
20565 width: em_width,
20566 height: line_height,
20567 } = self.character_size(window);
20568
20569 let snapshot = self.snapshot(window, cx);
20570 let scroll_position = snapshot.scroll_position();
20571 let scroll_left = scroll_position.x * em_width;
20572
20573 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20574 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20575 + self.gutter_dimensions.width
20576 + self.gutter_dimensions.margin;
20577 let y = line_height * (start.row().as_f32() - scroll_position.y);
20578
20579 Some(Bounds {
20580 origin: element_bounds.origin + point(x, y),
20581 size: size(em_width, line_height),
20582 })
20583 }
20584
20585 fn character_index_for_point(
20586 &mut self,
20587 point: gpui::Point<Pixels>,
20588 _window: &mut Window,
20589 _cx: &mut Context<Self>,
20590 ) -> Option<usize> {
20591 let position_map = self.last_position_map.as_ref()?;
20592 if !position_map.text_hitbox.contains(&point) {
20593 return None;
20594 }
20595 let display_point = position_map.point_for_position(point).previous_valid;
20596 let anchor = position_map
20597 .snapshot
20598 .display_point_to_anchor(display_point, Bias::Left);
20599 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20600 Some(utf16_offset.0)
20601 }
20602}
20603
20604trait SelectionExt {
20605 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20606 fn spanned_rows(
20607 &self,
20608 include_end_if_at_line_start: bool,
20609 map: &DisplaySnapshot,
20610 ) -> Range<MultiBufferRow>;
20611}
20612
20613impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20614 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20615 let start = self
20616 .start
20617 .to_point(&map.buffer_snapshot)
20618 .to_display_point(map);
20619 let end = self
20620 .end
20621 .to_point(&map.buffer_snapshot)
20622 .to_display_point(map);
20623 if self.reversed {
20624 end..start
20625 } else {
20626 start..end
20627 }
20628 }
20629
20630 fn spanned_rows(
20631 &self,
20632 include_end_if_at_line_start: bool,
20633 map: &DisplaySnapshot,
20634 ) -> Range<MultiBufferRow> {
20635 let start = self.start.to_point(&map.buffer_snapshot);
20636 let mut end = self.end.to_point(&map.buffer_snapshot);
20637 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20638 end.row -= 1;
20639 }
20640
20641 let buffer_start = map.prev_line_boundary(start).0;
20642 let buffer_end = map.next_line_boundary(end).0;
20643 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20644 }
20645}
20646
20647impl<T: InvalidationRegion> InvalidationStack<T> {
20648 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20649 where
20650 S: Clone + ToOffset,
20651 {
20652 while let Some(region) = self.last() {
20653 let all_selections_inside_invalidation_ranges =
20654 if selections.len() == region.ranges().len() {
20655 selections
20656 .iter()
20657 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20658 .all(|(selection, invalidation_range)| {
20659 let head = selection.head().to_offset(buffer);
20660 invalidation_range.start <= head && invalidation_range.end >= head
20661 })
20662 } else {
20663 false
20664 };
20665
20666 if all_selections_inside_invalidation_ranges {
20667 break;
20668 } else {
20669 self.pop();
20670 }
20671 }
20672 }
20673}
20674
20675impl<T> Default for InvalidationStack<T> {
20676 fn default() -> Self {
20677 Self(Default::default())
20678 }
20679}
20680
20681impl<T> Deref for InvalidationStack<T> {
20682 type Target = Vec<T>;
20683
20684 fn deref(&self) -> &Self::Target {
20685 &self.0
20686 }
20687}
20688
20689impl<T> DerefMut for InvalidationStack<T> {
20690 fn deref_mut(&mut self) -> &mut Self::Target {
20691 &mut self.0
20692 }
20693}
20694
20695impl InvalidationRegion for SnippetState {
20696 fn ranges(&self) -> &[Range<Anchor>] {
20697 &self.ranges[self.active_index]
20698 }
20699}
20700
20701fn inline_completion_edit_text(
20702 current_snapshot: &BufferSnapshot,
20703 edits: &[(Range<Anchor>, String)],
20704 edit_preview: &EditPreview,
20705 include_deletions: bool,
20706 cx: &App,
20707) -> HighlightedText {
20708 let edits = edits
20709 .iter()
20710 .map(|(anchor, text)| {
20711 (
20712 anchor.start.text_anchor..anchor.end.text_anchor,
20713 text.clone(),
20714 )
20715 })
20716 .collect::<Vec<_>>();
20717
20718 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20719}
20720
20721pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20722 match severity {
20723 DiagnosticSeverity::ERROR => colors.error,
20724 DiagnosticSeverity::WARNING => colors.warning,
20725 DiagnosticSeverity::INFORMATION => colors.info,
20726 DiagnosticSeverity::HINT => colors.info,
20727 _ => colors.ignored,
20728 }
20729}
20730
20731pub fn styled_runs_for_code_label<'a>(
20732 label: &'a CodeLabel,
20733 syntax_theme: &'a theme::SyntaxTheme,
20734) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20735 let fade_out = HighlightStyle {
20736 fade_out: Some(0.35),
20737 ..Default::default()
20738 };
20739
20740 let mut prev_end = label.filter_range.end;
20741 label
20742 .runs
20743 .iter()
20744 .enumerate()
20745 .flat_map(move |(ix, (range, highlight_id))| {
20746 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20747 style
20748 } else {
20749 return Default::default();
20750 };
20751 let mut muted_style = style;
20752 muted_style.highlight(fade_out);
20753
20754 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20755 if range.start >= label.filter_range.end {
20756 if range.start > prev_end {
20757 runs.push((prev_end..range.start, fade_out));
20758 }
20759 runs.push((range.clone(), muted_style));
20760 } else if range.end <= label.filter_range.end {
20761 runs.push((range.clone(), style));
20762 } else {
20763 runs.push((range.start..label.filter_range.end, style));
20764 runs.push((label.filter_range.end..range.end, muted_style));
20765 }
20766 prev_end = cmp::max(prev_end, range.end);
20767
20768 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20769 runs.push((prev_end..label.text.len(), fade_out));
20770 }
20771
20772 runs
20773 })
20774}
20775
20776pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20777 let mut prev_index = 0;
20778 let mut prev_codepoint: Option<char> = None;
20779 text.char_indices()
20780 .chain([(text.len(), '\0')])
20781 .filter_map(move |(index, codepoint)| {
20782 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20783 let is_boundary = index == text.len()
20784 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20785 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20786 if is_boundary {
20787 let chunk = &text[prev_index..index];
20788 prev_index = index;
20789 Some(chunk)
20790 } else {
20791 None
20792 }
20793 })
20794}
20795
20796pub trait RangeToAnchorExt: Sized {
20797 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20798
20799 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20800 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20801 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20802 }
20803}
20804
20805impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20806 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20807 let start_offset = self.start.to_offset(snapshot);
20808 let end_offset = self.end.to_offset(snapshot);
20809 if start_offset == end_offset {
20810 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20811 } else {
20812 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20813 }
20814 }
20815}
20816
20817pub trait RowExt {
20818 fn as_f32(&self) -> f32;
20819
20820 fn next_row(&self) -> Self;
20821
20822 fn previous_row(&self) -> Self;
20823
20824 fn minus(&self, other: Self) -> u32;
20825}
20826
20827impl RowExt for DisplayRow {
20828 fn as_f32(&self) -> f32 {
20829 self.0 as f32
20830 }
20831
20832 fn next_row(&self) -> Self {
20833 Self(self.0 + 1)
20834 }
20835
20836 fn previous_row(&self) -> Self {
20837 Self(self.0.saturating_sub(1))
20838 }
20839
20840 fn minus(&self, other: Self) -> u32 {
20841 self.0 - other.0
20842 }
20843}
20844
20845impl RowExt for MultiBufferRow {
20846 fn as_f32(&self) -> f32 {
20847 self.0 as f32
20848 }
20849
20850 fn next_row(&self) -> Self {
20851 Self(self.0 + 1)
20852 }
20853
20854 fn previous_row(&self) -> Self {
20855 Self(self.0.saturating_sub(1))
20856 }
20857
20858 fn minus(&self, other: Self) -> u32 {
20859 self.0 - other.0
20860 }
20861}
20862
20863trait RowRangeExt {
20864 type Row;
20865
20866 fn len(&self) -> usize;
20867
20868 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20869}
20870
20871impl RowRangeExt for Range<MultiBufferRow> {
20872 type Row = MultiBufferRow;
20873
20874 fn len(&self) -> usize {
20875 (self.end.0 - self.start.0) as usize
20876 }
20877
20878 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20879 (self.start.0..self.end.0).map(MultiBufferRow)
20880 }
20881}
20882
20883impl RowRangeExt for Range<DisplayRow> {
20884 type Row = DisplayRow;
20885
20886 fn len(&self) -> usize {
20887 (self.end.0 - self.start.0) as usize
20888 }
20889
20890 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20891 (self.start.0..self.end.0).map(DisplayRow)
20892 }
20893}
20894
20895/// If select range has more than one line, we
20896/// just point the cursor to range.start.
20897fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20898 if range.start.row == range.end.row {
20899 range
20900 } else {
20901 range.start..range.start
20902 }
20903}
20904pub struct KillRing(ClipboardItem);
20905impl Global for KillRing {}
20906
20907const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20908
20909enum BreakpointPromptEditAction {
20910 Log,
20911 Condition,
20912 HitCondition,
20913}
20914
20915struct BreakpointPromptEditor {
20916 pub(crate) prompt: Entity<Editor>,
20917 editor: WeakEntity<Editor>,
20918 breakpoint_anchor: Anchor,
20919 breakpoint: Breakpoint,
20920 edit_action: BreakpointPromptEditAction,
20921 block_ids: HashSet<CustomBlockId>,
20922 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20923 _subscriptions: Vec<Subscription>,
20924}
20925
20926impl BreakpointPromptEditor {
20927 const MAX_LINES: u8 = 4;
20928
20929 fn new(
20930 editor: WeakEntity<Editor>,
20931 breakpoint_anchor: Anchor,
20932 breakpoint: Breakpoint,
20933 edit_action: BreakpointPromptEditAction,
20934 window: &mut Window,
20935 cx: &mut Context<Self>,
20936 ) -> Self {
20937 let base_text = match edit_action {
20938 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20939 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20940 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20941 }
20942 .map(|msg| msg.to_string())
20943 .unwrap_or_default();
20944
20945 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20946 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20947
20948 let prompt = cx.new(|cx| {
20949 let mut prompt = Editor::new(
20950 EditorMode::AutoHeight {
20951 max_lines: Self::MAX_LINES as usize,
20952 },
20953 buffer,
20954 None,
20955 window,
20956 cx,
20957 );
20958 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20959 prompt.set_show_cursor_when_unfocused(false, cx);
20960 prompt.set_placeholder_text(
20961 match edit_action {
20962 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20963 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20964 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20965 },
20966 cx,
20967 );
20968
20969 prompt
20970 });
20971
20972 Self {
20973 prompt,
20974 editor,
20975 breakpoint_anchor,
20976 breakpoint,
20977 edit_action,
20978 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20979 block_ids: Default::default(),
20980 _subscriptions: vec![],
20981 }
20982 }
20983
20984 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20985 self.block_ids.extend(block_ids)
20986 }
20987
20988 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20989 if let Some(editor) = self.editor.upgrade() {
20990 let message = self
20991 .prompt
20992 .read(cx)
20993 .buffer
20994 .read(cx)
20995 .as_singleton()
20996 .expect("A multi buffer in breakpoint prompt isn't possible")
20997 .read(cx)
20998 .as_rope()
20999 .to_string();
21000
21001 editor.update(cx, |editor, cx| {
21002 editor.edit_breakpoint_at_anchor(
21003 self.breakpoint_anchor,
21004 self.breakpoint.clone(),
21005 match self.edit_action {
21006 BreakpointPromptEditAction::Log => {
21007 BreakpointEditAction::EditLogMessage(message.into())
21008 }
21009 BreakpointPromptEditAction::Condition => {
21010 BreakpointEditAction::EditCondition(message.into())
21011 }
21012 BreakpointPromptEditAction::HitCondition => {
21013 BreakpointEditAction::EditHitCondition(message.into())
21014 }
21015 },
21016 cx,
21017 );
21018
21019 editor.remove_blocks(self.block_ids.clone(), None, cx);
21020 cx.focus_self(window);
21021 });
21022 }
21023 }
21024
21025 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21026 self.editor
21027 .update(cx, |editor, cx| {
21028 editor.remove_blocks(self.block_ids.clone(), None, cx);
21029 window.focus(&editor.focus_handle);
21030 })
21031 .log_err();
21032 }
21033
21034 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21035 let settings = ThemeSettings::get_global(cx);
21036 let text_style = TextStyle {
21037 color: if self.prompt.read(cx).read_only(cx) {
21038 cx.theme().colors().text_disabled
21039 } else {
21040 cx.theme().colors().text
21041 },
21042 font_family: settings.buffer_font.family.clone(),
21043 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21044 font_size: settings.buffer_font_size(cx).into(),
21045 font_weight: settings.buffer_font.weight,
21046 line_height: relative(settings.buffer_line_height.value()),
21047 ..Default::default()
21048 };
21049 EditorElement::new(
21050 &self.prompt,
21051 EditorStyle {
21052 background: cx.theme().colors().editor_background,
21053 local_player: cx.theme().players().local(),
21054 text: text_style,
21055 ..Default::default()
21056 },
21057 )
21058 }
21059}
21060
21061impl Render for BreakpointPromptEditor {
21062 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21063 let gutter_dimensions = *self.gutter_dimensions.lock();
21064 h_flex()
21065 .key_context("Editor")
21066 .bg(cx.theme().colors().editor_background)
21067 .border_y_1()
21068 .border_color(cx.theme().status().info_border)
21069 .size_full()
21070 .py(window.line_height() / 2.5)
21071 .on_action(cx.listener(Self::confirm))
21072 .on_action(cx.listener(Self::cancel))
21073 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21074 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21075 }
21076}
21077
21078impl Focusable for BreakpointPromptEditor {
21079 fn focus_handle(&self, cx: &App) -> FocusHandle {
21080 self.prompt.focus_handle(cx)
21081 }
21082}
21083
21084fn all_edits_insertions_or_deletions(
21085 edits: &Vec<(Range<Anchor>, String)>,
21086 snapshot: &MultiBufferSnapshot,
21087) -> bool {
21088 let mut all_insertions = true;
21089 let mut all_deletions = true;
21090
21091 for (range, new_text) in edits.iter() {
21092 let range_is_empty = range.to_offset(&snapshot).is_empty();
21093 let text_is_empty = new_text.is_empty();
21094
21095 if range_is_empty != text_is_empty {
21096 if range_is_empty {
21097 all_deletions = false;
21098 } else {
21099 all_insertions = false;
21100 }
21101 } else {
21102 return false;
21103 }
21104
21105 if !all_insertions && !all_deletions {
21106 return false;
21107 }
21108 }
21109 all_insertions || all_deletions
21110}
21111
21112struct MissingEditPredictionKeybindingTooltip;
21113
21114impl Render for MissingEditPredictionKeybindingTooltip {
21115 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21116 ui::tooltip_container(window, cx, |container, _, cx| {
21117 container
21118 .flex_shrink_0()
21119 .max_w_80()
21120 .min_h(rems_from_px(124.))
21121 .justify_between()
21122 .child(
21123 v_flex()
21124 .flex_1()
21125 .text_ui_sm(cx)
21126 .child(Label::new("Conflict with Accept Keybinding"))
21127 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21128 )
21129 .child(
21130 h_flex()
21131 .pb_1()
21132 .gap_1()
21133 .items_end()
21134 .w_full()
21135 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21136 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21137 }))
21138 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21139 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21140 })),
21141 )
21142 })
21143 }
21144}
21145
21146#[derive(Debug, Clone, Copy, PartialEq)]
21147pub struct LineHighlight {
21148 pub background: Background,
21149 pub border: Option<gpui::Hsla>,
21150 pub include_gutter: bool,
21151 pub type_id: Option<TypeId>,
21152}
21153
21154fn render_diff_hunk_controls(
21155 row: u32,
21156 status: &DiffHunkStatus,
21157 hunk_range: Range<Anchor>,
21158 is_created_file: bool,
21159 line_height: Pixels,
21160 editor: &Entity<Editor>,
21161 _window: &mut Window,
21162 cx: &mut App,
21163) -> AnyElement {
21164 h_flex()
21165 .h(line_height)
21166 .mr_1()
21167 .gap_1()
21168 .px_0p5()
21169 .pb_1()
21170 .border_x_1()
21171 .border_b_1()
21172 .border_color(cx.theme().colors().border_variant)
21173 .rounded_b_lg()
21174 .bg(cx.theme().colors().editor_background)
21175 .gap_1()
21176 .occlude()
21177 .shadow_md()
21178 .child(if status.has_secondary_hunk() {
21179 Button::new(("stage", row as u64), "Stage")
21180 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21181 .tooltip({
21182 let focus_handle = editor.focus_handle(cx);
21183 move |window, cx| {
21184 Tooltip::for_action_in(
21185 "Stage Hunk",
21186 &::git::ToggleStaged,
21187 &focus_handle,
21188 window,
21189 cx,
21190 )
21191 }
21192 })
21193 .on_click({
21194 let editor = editor.clone();
21195 move |_event, _window, cx| {
21196 editor.update(cx, |editor, cx| {
21197 editor.stage_or_unstage_diff_hunks(
21198 true,
21199 vec![hunk_range.start..hunk_range.start],
21200 cx,
21201 );
21202 });
21203 }
21204 })
21205 } else {
21206 Button::new(("unstage", row as u64), "Unstage")
21207 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21208 .tooltip({
21209 let focus_handle = editor.focus_handle(cx);
21210 move |window, cx| {
21211 Tooltip::for_action_in(
21212 "Unstage Hunk",
21213 &::git::ToggleStaged,
21214 &focus_handle,
21215 window,
21216 cx,
21217 )
21218 }
21219 })
21220 .on_click({
21221 let editor = editor.clone();
21222 move |_event, _window, cx| {
21223 editor.update(cx, |editor, cx| {
21224 editor.stage_or_unstage_diff_hunks(
21225 false,
21226 vec![hunk_range.start..hunk_range.start],
21227 cx,
21228 );
21229 });
21230 }
21231 })
21232 })
21233 .child(
21234 Button::new(("restore", row as u64), "Restore")
21235 .tooltip({
21236 let focus_handle = editor.focus_handle(cx);
21237 move |window, cx| {
21238 Tooltip::for_action_in(
21239 "Restore Hunk",
21240 &::git::Restore,
21241 &focus_handle,
21242 window,
21243 cx,
21244 )
21245 }
21246 })
21247 .on_click({
21248 let editor = editor.clone();
21249 move |_event, window, cx| {
21250 editor.update(cx, |editor, cx| {
21251 let snapshot = editor.snapshot(window, cx);
21252 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21253 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21254 });
21255 }
21256 })
21257 .disabled(is_created_file),
21258 )
21259 .when(
21260 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21261 |el| {
21262 el.child(
21263 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21264 .shape(IconButtonShape::Square)
21265 .icon_size(IconSize::Small)
21266 // .disabled(!has_multiple_hunks)
21267 .tooltip({
21268 let focus_handle = editor.focus_handle(cx);
21269 move |window, cx| {
21270 Tooltip::for_action_in(
21271 "Next Hunk",
21272 &GoToHunk,
21273 &focus_handle,
21274 window,
21275 cx,
21276 )
21277 }
21278 })
21279 .on_click({
21280 let editor = editor.clone();
21281 move |_event, window, cx| {
21282 editor.update(cx, |editor, cx| {
21283 let snapshot = editor.snapshot(window, cx);
21284 let position =
21285 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21286 editor.go_to_hunk_before_or_after_position(
21287 &snapshot,
21288 position,
21289 Direction::Next,
21290 window,
21291 cx,
21292 );
21293 editor.expand_selected_diff_hunks(cx);
21294 });
21295 }
21296 }),
21297 )
21298 .child(
21299 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21300 .shape(IconButtonShape::Square)
21301 .icon_size(IconSize::Small)
21302 // .disabled(!has_multiple_hunks)
21303 .tooltip({
21304 let focus_handle = editor.focus_handle(cx);
21305 move |window, cx| {
21306 Tooltip::for_action_in(
21307 "Previous Hunk",
21308 &GoToPreviousHunk,
21309 &focus_handle,
21310 window,
21311 cx,
21312 )
21313 }
21314 })
21315 .on_click({
21316 let editor = editor.clone();
21317 move |_event, window, cx| {
21318 editor.update(cx, |editor, cx| {
21319 let snapshot = editor.snapshot(window, cx);
21320 let point =
21321 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21322 editor.go_to_hunk_before_or_after_position(
21323 &snapshot,
21324 point,
21325 Direction::Prev,
21326 window,
21327 cx,
21328 );
21329 editor.expand_selected_diff_hunks(cx);
21330 });
21331 }
21332 }),
21333 )
21334 },
21335 )
21336 .into_any_element()
21337}