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(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(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
5821 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5822 else {
5823 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5824 self.quick_selection_highlight_task.take();
5825 self.debounced_selection_highlight_task.take();
5826 return;
5827 };
5828 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5829 if self
5830 .quick_selection_highlight_task
5831 .as_ref()
5832 .map_or(true, |(prev_anchor_range, _)| {
5833 prev_anchor_range != &query_range
5834 })
5835 {
5836 let multi_buffer_visible_start = self
5837 .scroll_manager
5838 .anchor()
5839 .anchor
5840 .to_point(&multi_buffer_snapshot);
5841 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5842 multi_buffer_visible_start
5843 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5844 Bias::Left,
5845 );
5846 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5847 self.quick_selection_highlight_task = Some((
5848 query_range.clone(),
5849 self.update_selection_occurrence_highlights(
5850 query_text.clone(),
5851 query_range.clone(),
5852 multi_buffer_visible_range,
5853 false,
5854 window,
5855 cx,
5856 ),
5857 ));
5858 }
5859 if self
5860 .debounced_selection_highlight_task
5861 .as_ref()
5862 .map_or(true, |(prev_anchor_range, _)| {
5863 prev_anchor_range != &query_range
5864 })
5865 {
5866 let multi_buffer_start = multi_buffer_snapshot
5867 .anchor_before(0)
5868 .to_point(&multi_buffer_snapshot);
5869 let multi_buffer_end = multi_buffer_snapshot
5870 .anchor_after(multi_buffer_snapshot.len())
5871 .to_point(&multi_buffer_snapshot);
5872 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5873 self.debounced_selection_highlight_task = Some((
5874 query_range.clone(),
5875 self.update_selection_occurrence_highlights(
5876 query_text,
5877 query_range,
5878 multi_buffer_full_range,
5879 true,
5880 window,
5881 cx,
5882 ),
5883 ));
5884 }
5885 }
5886
5887 pub fn refresh_inline_completion(
5888 &mut self,
5889 debounce: bool,
5890 user_requested: bool,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) -> Option<()> {
5894 let provider = self.edit_prediction_provider()?;
5895 let cursor = self.selections.newest_anchor().head();
5896 let (buffer, cursor_buffer_position) =
5897 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5898
5899 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5900 self.discard_inline_completion(false, cx);
5901 return None;
5902 }
5903
5904 if !user_requested
5905 && (!self.should_show_edit_predictions()
5906 || !self.is_focused(window)
5907 || buffer.read(cx).is_empty())
5908 {
5909 self.discard_inline_completion(false, cx);
5910 return None;
5911 }
5912
5913 self.update_visible_inline_completion(window, cx);
5914 provider.refresh(
5915 self.project.clone(),
5916 buffer,
5917 cursor_buffer_position,
5918 debounce,
5919 cx,
5920 );
5921 Some(())
5922 }
5923
5924 fn show_edit_predictions_in_menu(&self) -> bool {
5925 match self.edit_prediction_settings {
5926 EditPredictionSettings::Disabled => false,
5927 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5928 }
5929 }
5930
5931 pub fn edit_predictions_enabled(&self) -> bool {
5932 match self.edit_prediction_settings {
5933 EditPredictionSettings::Disabled => false,
5934 EditPredictionSettings::Enabled { .. } => true,
5935 }
5936 }
5937
5938 fn edit_prediction_requires_modifier(&self) -> bool {
5939 match self.edit_prediction_settings {
5940 EditPredictionSettings::Disabled => false,
5941 EditPredictionSettings::Enabled {
5942 preview_requires_modifier,
5943 ..
5944 } => preview_requires_modifier,
5945 }
5946 }
5947
5948 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5949 if self.edit_prediction_provider.is_none() {
5950 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5951 } else {
5952 let selection = self.selections.newest_anchor();
5953 let cursor = selection.head();
5954
5955 if let Some((buffer, cursor_buffer_position)) =
5956 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5957 {
5958 self.edit_prediction_settings =
5959 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5960 }
5961 }
5962 }
5963
5964 fn edit_prediction_settings_at_position(
5965 &self,
5966 buffer: &Entity<Buffer>,
5967 buffer_position: language::Anchor,
5968 cx: &App,
5969 ) -> EditPredictionSettings {
5970 if !self.mode.is_full()
5971 || !self.show_inline_completions_override.unwrap_or(true)
5972 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5973 {
5974 return EditPredictionSettings::Disabled;
5975 }
5976
5977 let buffer = buffer.read(cx);
5978
5979 let file = buffer.file();
5980
5981 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5982 return EditPredictionSettings::Disabled;
5983 };
5984
5985 let by_provider = matches!(
5986 self.menu_inline_completions_policy,
5987 MenuInlineCompletionsPolicy::ByProvider
5988 );
5989
5990 let show_in_menu = by_provider
5991 && self
5992 .edit_prediction_provider
5993 .as_ref()
5994 .map_or(false, |provider| {
5995 provider.provider.show_completions_in_menu()
5996 });
5997
5998 let preview_requires_modifier =
5999 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6000
6001 EditPredictionSettings::Enabled {
6002 show_in_menu,
6003 preview_requires_modifier,
6004 }
6005 }
6006
6007 fn should_show_edit_predictions(&self) -> bool {
6008 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6009 }
6010
6011 pub fn edit_prediction_preview_is_active(&self) -> bool {
6012 matches!(
6013 self.edit_prediction_preview,
6014 EditPredictionPreview::Active { .. }
6015 )
6016 }
6017
6018 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6019 let cursor = self.selections.newest_anchor().head();
6020 if let Some((buffer, cursor_position)) =
6021 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6022 {
6023 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6024 } else {
6025 false
6026 }
6027 }
6028
6029 fn edit_predictions_enabled_in_buffer(
6030 &self,
6031 buffer: &Entity<Buffer>,
6032 buffer_position: language::Anchor,
6033 cx: &App,
6034 ) -> bool {
6035 maybe!({
6036 if self.read_only(cx) {
6037 return Some(false);
6038 }
6039 let provider = self.edit_prediction_provider()?;
6040 if !provider.is_enabled(&buffer, buffer_position, cx) {
6041 return Some(false);
6042 }
6043 let buffer = buffer.read(cx);
6044 let Some(file) = buffer.file() else {
6045 return Some(true);
6046 };
6047 let settings = all_language_settings(Some(file), cx);
6048 Some(settings.edit_predictions_enabled_for_file(file, cx))
6049 })
6050 .unwrap_or(false)
6051 }
6052
6053 fn cycle_inline_completion(
6054 &mut self,
6055 direction: Direction,
6056 window: &mut Window,
6057 cx: &mut Context<Self>,
6058 ) -> Option<()> {
6059 let provider = self.edit_prediction_provider()?;
6060 let cursor = self.selections.newest_anchor().head();
6061 let (buffer, cursor_buffer_position) =
6062 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6063 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6064 return None;
6065 }
6066
6067 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6068 self.update_visible_inline_completion(window, cx);
6069
6070 Some(())
6071 }
6072
6073 pub fn show_inline_completion(
6074 &mut self,
6075 _: &ShowEditPrediction,
6076 window: &mut Window,
6077 cx: &mut Context<Self>,
6078 ) {
6079 if !self.has_active_inline_completion() {
6080 self.refresh_inline_completion(false, true, window, cx);
6081 return;
6082 }
6083
6084 self.update_visible_inline_completion(window, cx);
6085 }
6086
6087 pub fn display_cursor_names(
6088 &mut self,
6089 _: &DisplayCursorNames,
6090 window: &mut Window,
6091 cx: &mut Context<Self>,
6092 ) {
6093 self.show_cursor_names(window, cx);
6094 }
6095
6096 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6097 self.show_cursor_names = true;
6098 cx.notify();
6099 cx.spawn_in(window, async move |this, cx| {
6100 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6101 this.update(cx, |this, cx| {
6102 this.show_cursor_names = false;
6103 cx.notify()
6104 })
6105 .ok()
6106 })
6107 .detach();
6108 }
6109
6110 pub fn next_edit_prediction(
6111 &mut self,
6112 _: &NextEditPrediction,
6113 window: &mut Window,
6114 cx: &mut Context<Self>,
6115 ) {
6116 if self.has_active_inline_completion() {
6117 self.cycle_inline_completion(Direction::Next, window, cx);
6118 } else {
6119 let is_copilot_disabled = self
6120 .refresh_inline_completion(false, true, window, cx)
6121 .is_none();
6122 if is_copilot_disabled {
6123 cx.propagate();
6124 }
6125 }
6126 }
6127
6128 pub fn previous_edit_prediction(
6129 &mut self,
6130 _: &PreviousEditPrediction,
6131 window: &mut Window,
6132 cx: &mut Context<Self>,
6133 ) {
6134 if self.has_active_inline_completion() {
6135 self.cycle_inline_completion(Direction::Prev, window, cx);
6136 } else {
6137 let is_copilot_disabled = self
6138 .refresh_inline_completion(false, true, window, cx)
6139 .is_none();
6140 if is_copilot_disabled {
6141 cx.propagate();
6142 }
6143 }
6144 }
6145
6146 pub fn accept_edit_prediction(
6147 &mut self,
6148 _: &AcceptEditPrediction,
6149 window: &mut Window,
6150 cx: &mut Context<Self>,
6151 ) {
6152 if self.show_edit_predictions_in_menu() {
6153 self.hide_context_menu(window, cx);
6154 }
6155
6156 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6157 return;
6158 };
6159
6160 self.report_inline_completion_event(
6161 active_inline_completion.completion_id.clone(),
6162 true,
6163 cx,
6164 );
6165
6166 match &active_inline_completion.completion {
6167 InlineCompletion::Move { target, .. } => {
6168 let target = *target;
6169
6170 if let Some(position_map) = &self.last_position_map {
6171 if position_map
6172 .visible_row_range
6173 .contains(&target.to_display_point(&position_map.snapshot).row())
6174 || !self.edit_prediction_requires_modifier()
6175 {
6176 self.unfold_ranges(&[target..target], true, false, cx);
6177 // Note that this is also done in vim's handler of the Tab action.
6178 self.change_selections(
6179 Some(Autoscroll::newest()),
6180 window,
6181 cx,
6182 |selections| {
6183 selections.select_anchor_ranges([target..target]);
6184 },
6185 );
6186 self.clear_row_highlights::<EditPredictionPreview>();
6187
6188 self.edit_prediction_preview
6189 .set_previous_scroll_position(None);
6190 } else {
6191 self.edit_prediction_preview
6192 .set_previous_scroll_position(Some(
6193 position_map.snapshot.scroll_anchor,
6194 ));
6195
6196 self.highlight_rows::<EditPredictionPreview>(
6197 target..target,
6198 cx.theme().colors().editor_highlighted_line_background,
6199 RowHighlightOptions {
6200 autoscroll: true,
6201 ..Default::default()
6202 },
6203 cx,
6204 );
6205 self.request_autoscroll(Autoscroll::fit(), cx);
6206 }
6207 }
6208 }
6209 InlineCompletion::Edit { edits, .. } => {
6210 if let Some(provider) = self.edit_prediction_provider() {
6211 provider.accept(cx);
6212 }
6213
6214 let snapshot = self.buffer.read(cx).snapshot(cx);
6215 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6216
6217 self.buffer.update(cx, |buffer, cx| {
6218 buffer.edit(edits.iter().cloned(), None, cx)
6219 });
6220
6221 self.change_selections(None, window, cx, |s| {
6222 s.select_anchor_ranges([last_edit_end..last_edit_end])
6223 });
6224
6225 self.update_visible_inline_completion(window, cx);
6226 if self.active_inline_completion.is_none() {
6227 self.refresh_inline_completion(true, true, window, cx);
6228 }
6229
6230 cx.notify();
6231 }
6232 }
6233
6234 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6235 }
6236
6237 pub fn accept_partial_inline_completion(
6238 &mut self,
6239 _: &AcceptPartialEditPrediction,
6240 window: &mut Window,
6241 cx: &mut Context<Self>,
6242 ) {
6243 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6244 return;
6245 };
6246 if self.selections.count() != 1 {
6247 return;
6248 }
6249
6250 self.report_inline_completion_event(
6251 active_inline_completion.completion_id.clone(),
6252 true,
6253 cx,
6254 );
6255
6256 match &active_inline_completion.completion {
6257 InlineCompletion::Move { target, .. } => {
6258 let target = *target;
6259 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6260 selections.select_anchor_ranges([target..target]);
6261 });
6262 }
6263 InlineCompletion::Edit { edits, .. } => {
6264 // Find an insertion that starts at the cursor position.
6265 let snapshot = self.buffer.read(cx).snapshot(cx);
6266 let cursor_offset = self.selections.newest::<usize>(cx).head();
6267 let insertion = edits.iter().find_map(|(range, text)| {
6268 let range = range.to_offset(&snapshot);
6269 if range.is_empty() && range.start == cursor_offset {
6270 Some(text)
6271 } else {
6272 None
6273 }
6274 });
6275
6276 if let Some(text) = insertion {
6277 let mut partial_completion = text
6278 .chars()
6279 .by_ref()
6280 .take_while(|c| c.is_alphabetic())
6281 .collect::<String>();
6282 if partial_completion.is_empty() {
6283 partial_completion = text
6284 .chars()
6285 .by_ref()
6286 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6287 .collect::<String>();
6288 }
6289
6290 cx.emit(EditorEvent::InputHandled {
6291 utf16_range_to_replace: None,
6292 text: partial_completion.clone().into(),
6293 });
6294
6295 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6296
6297 self.refresh_inline_completion(true, true, window, cx);
6298 cx.notify();
6299 } else {
6300 self.accept_edit_prediction(&Default::default(), window, cx);
6301 }
6302 }
6303 }
6304 }
6305
6306 fn discard_inline_completion(
6307 &mut self,
6308 should_report_inline_completion_event: bool,
6309 cx: &mut Context<Self>,
6310 ) -> bool {
6311 if should_report_inline_completion_event {
6312 let completion_id = self
6313 .active_inline_completion
6314 .as_ref()
6315 .and_then(|active_completion| active_completion.completion_id.clone());
6316
6317 self.report_inline_completion_event(completion_id, false, cx);
6318 }
6319
6320 if let Some(provider) = self.edit_prediction_provider() {
6321 provider.discard(cx);
6322 }
6323
6324 self.take_active_inline_completion(cx)
6325 }
6326
6327 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6328 let Some(provider) = self.edit_prediction_provider() else {
6329 return;
6330 };
6331
6332 let Some((_, buffer, _)) = self
6333 .buffer
6334 .read(cx)
6335 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6336 else {
6337 return;
6338 };
6339
6340 let extension = buffer
6341 .read(cx)
6342 .file()
6343 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6344
6345 let event_type = match accepted {
6346 true => "Edit Prediction Accepted",
6347 false => "Edit Prediction Discarded",
6348 };
6349 telemetry::event!(
6350 event_type,
6351 provider = provider.name(),
6352 prediction_id = id,
6353 suggestion_accepted = accepted,
6354 file_extension = extension,
6355 );
6356 }
6357
6358 pub fn has_active_inline_completion(&self) -> bool {
6359 self.active_inline_completion.is_some()
6360 }
6361
6362 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6363 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6364 return false;
6365 };
6366
6367 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6368 self.clear_highlights::<InlineCompletionHighlight>(cx);
6369 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6370 true
6371 }
6372
6373 /// Returns true when we're displaying the edit prediction popover below the cursor
6374 /// like we are not previewing and the LSP autocomplete menu is visible
6375 /// or we are in `when_holding_modifier` mode.
6376 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6377 if self.edit_prediction_preview_is_active()
6378 || !self.show_edit_predictions_in_menu()
6379 || !self.edit_predictions_enabled()
6380 {
6381 return false;
6382 }
6383
6384 if self.has_visible_completions_menu() {
6385 return true;
6386 }
6387
6388 has_completion && self.edit_prediction_requires_modifier()
6389 }
6390
6391 fn handle_modifiers_changed(
6392 &mut self,
6393 modifiers: Modifiers,
6394 position_map: &PositionMap,
6395 window: &mut Window,
6396 cx: &mut Context<Self>,
6397 ) {
6398 if self.show_edit_predictions_in_menu() {
6399 self.update_edit_prediction_preview(&modifiers, window, cx);
6400 }
6401
6402 self.update_selection_mode(&modifiers, position_map, window, cx);
6403
6404 let mouse_position = window.mouse_position();
6405 if !position_map.text_hitbox.is_hovered(window) {
6406 return;
6407 }
6408
6409 self.update_hovered_link(
6410 position_map.point_for_position(mouse_position),
6411 &position_map.snapshot,
6412 modifiers,
6413 window,
6414 cx,
6415 )
6416 }
6417
6418 fn update_selection_mode(
6419 &mut self,
6420 modifiers: &Modifiers,
6421 position_map: &PositionMap,
6422 window: &mut Window,
6423 cx: &mut Context<Self>,
6424 ) {
6425 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6426 return;
6427 }
6428
6429 let mouse_position = window.mouse_position();
6430 let point_for_position = position_map.point_for_position(mouse_position);
6431 let position = point_for_position.previous_valid;
6432
6433 self.select(
6434 SelectPhase::BeginColumnar {
6435 position,
6436 reset: false,
6437 goal_column: point_for_position.exact_unclipped.column(),
6438 },
6439 window,
6440 cx,
6441 );
6442 }
6443
6444 fn update_edit_prediction_preview(
6445 &mut self,
6446 modifiers: &Modifiers,
6447 window: &mut Window,
6448 cx: &mut Context<Self>,
6449 ) {
6450 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6451 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6452 return;
6453 };
6454
6455 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6456 if matches!(
6457 self.edit_prediction_preview,
6458 EditPredictionPreview::Inactive { .. }
6459 ) {
6460 self.edit_prediction_preview = EditPredictionPreview::Active {
6461 previous_scroll_position: None,
6462 since: Instant::now(),
6463 };
6464
6465 self.update_visible_inline_completion(window, cx);
6466 cx.notify();
6467 }
6468 } else if let EditPredictionPreview::Active {
6469 previous_scroll_position,
6470 since,
6471 } = self.edit_prediction_preview
6472 {
6473 if let (Some(previous_scroll_position), Some(position_map)) =
6474 (previous_scroll_position, self.last_position_map.as_ref())
6475 {
6476 self.set_scroll_position(
6477 previous_scroll_position
6478 .scroll_position(&position_map.snapshot.display_snapshot),
6479 window,
6480 cx,
6481 );
6482 }
6483
6484 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6485 released_too_fast: since.elapsed() < Duration::from_millis(200),
6486 };
6487 self.clear_row_highlights::<EditPredictionPreview>();
6488 self.update_visible_inline_completion(window, cx);
6489 cx.notify();
6490 }
6491 }
6492
6493 fn update_visible_inline_completion(
6494 &mut self,
6495 _window: &mut Window,
6496 cx: &mut Context<Self>,
6497 ) -> Option<()> {
6498 let selection = self.selections.newest_anchor();
6499 let cursor = selection.head();
6500 let multibuffer = self.buffer.read(cx).snapshot(cx);
6501 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6502 let excerpt_id = cursor.excerpt_id;
6503
6504 let show_in_menu = self.show_edit_predictions_in_menu();
6505 let completions_menu_has_precedence = !show_in_menu
6506 && (self.context_menu.borrow().is_some()
6507 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6508
6509 if completions_menu_has_precedence
6510 || !offset_selection.is_empty()
6511 || self
6512 .active_inline_completion
6513 .as_ref()
6514 .map_or(false, |completion| {
6515 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6516 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6517 !invalidation_range.contains(&offset_selection.head())
6518 })
6519 {
6520 self.discard_inline_completion(false, cx);
6521 return None;
6522 }
6523
6524 self.take_active_inline_completion(cx);
6525 let Some(provider) = self.edit_prediction_provider() else {
6526 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6527 return None;
6528 };
6529
6530 let (buffer, cursor_buffer_position) =
6531 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6532
6533 self.edit_prediction_settings =
6534 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6535
6536 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6537
6538 if self.edit_prediction_indent_conflict {
6539 let cursor_point = cursor.to_point(&multibuffer);
6540
6541 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6542
6543 if let Some((_, indent)) = indents.iter().next() {
6544 if indent.len == cursor_point.column {
6545 self.edit_prediction_indent_conflict = false;
6546 }
6547 }
6548 }
6549
6550 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6551 let edits = inline_completion
6552 .edits
6553 .into_iter()
6554 .flat_map(|(range, new_text)| {
6555 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6556 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6557 Some((start..end, new_text))
6558 })
6559 .collect::<Vec<_>>();
6560 if edits.is_empty() {
6561 return None;
6562 }
6563
6564 let first_edit_start = edits.first().unwrap().0.start;
6565 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6566 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6567
6568 let last_edit_end = edits.last().unwrap().0.end;
6569 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6570 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6571
6572 let cursor_row = cursor.to_point(&multibuffer).row;
6573
6574 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6575
6576 let mut inlay_ids = Vec::new();
6577 let invalidation_row_range;
6578 let move_invalidation_row_range = if cursor_row < edit_start_row {
6579 Some(cursor_row..edit_end_row)
6580 } else if cursor_row > edit_end_row {
6581 Some(edit_start_row..cursor_row)
6582 } else {
6583 None
6584 };
6585 let is_move =
6586 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6587 let completion = if is_move {
6588 invalidation_row_range =
6589 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6590 let target = first_edit_start;
6591 InlineCompletion::Move { target, snapshot }
6592 } else {
6593 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6594 && !self.inline_completions_hidden_for_vim_mode;
6595
6596 if show_completions_in_buffer {
6597 if edits
6598 .iter()
6599 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6600 {
6601 let mut inlays = Vec::new();
6602 for (range, new_text) in &edits {
6603 let inlay = Inlay::inline_completion(
6604 post_inc(&mut self.next_inlay_id),
6605 range.start,
6606 new_text.as_str(),
6607 );
6608 inlay_ids.push(inlay.id);
6609 inlays.push(inlay);
6610 }
6611
6612 self.splice_inlays(&[], inlays, cx);
6613 } else {
6614 let background_color = cx.theme().status().deleted_background;
6615 self.highlight_text::<InlineCompletionHighlight>(
6616 edits.iter().map(|(range, _)| range.clone()).collect(),
6617 HighlightStyle {
6618 background_color: Some(background_color),
6619 ..Default::default()
6620 },
6621 cx,
6622 );
6623 }
6624 }
6625
6626 invalidation_row_range = edit_start_row..edit_end_row;
6627
6628 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6629 if provider.show_tab_accept_marker() {
6630 EditDisplayMode::TabAccept
6631 } else {
6632 EditDisplayMode::Inline
6633 }
6634 } else {
6635 EditDisplayMode::DiffPopover
6636 };
6637
6638 InlineCompletion::Edit {
6639 edits,
6640 edit_preview: inline_completion.edit_preview,
6641 display_mode,
6642 snapshot,
6643 }
6644 };
6645
6646 let invalidation_range = multibuffer
6647 .anchor_before(Point::new(invalidation_row_range.start, 0))
6648 ..multibuffer.anchor_after(Point::new(
6649 invalidation_row_range.end,
6650 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6651 ));
6652
6653 self.stale_inline_completion_in_menu = None;
6654 self.active_inline_completion = Some(InlineCompletionState {
6655 inlay_ids,
6656 completion,
6657 completion_id: inline_completion.id,
6658 invalidation_range,
6659 });
6660
6661 cx.notify();
6662
6663 Some(())
6664 }
6665
6666 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6667 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6668 }
6669
6670 fn render_code_actions_indicator(
6671 &self,
6672 _style: &EditorStyle,
6673 row: DisplayRow,
6674 is_active: bool,
6675 breakpoint: Option<&(Anchor, Breakpoint)>,
6676 cx: &mut Context<Self>,
6677 ) -> Option<IconButton> {
6678 let color = Color::Muted;
6679 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6680 let show_tooltip = !self.context_menu_visible();
6681
6682 if self.available_code_actions.is_some() {
6683 Some(
6684 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6685 .shape(ui::IconButtonShape::Square)
6686 .icon_size(IconSize::XSmall)
6687 .icon_color(color)
6688 .toggle_state(is_active)
6689 .when(show_tooltip, |this| {
6690 this.tooltip({
6691 let focus_handle = self.focus_handle.clone();
6692 move |window, cx| {
6693 Tooltip::for_action_in(
6694 "Toggle Code Actions",
6695 &ToggleCodeActions {
6696 deployed_from_indicator: None,
6697 quick_launch: false,
6698 },
6699 &focus_handle,
6700 window,
6701 cx,
6702 )
6703 }
6704 })
6705 })
6706 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
6707 let quick_launch = e.down.button == MouseButton::Left;
6708 window.focus(&editor.focus_handle(cx));
6709 editor.toggle_code_actions(
6710 &ToggleCodeActions {
6711 deployed_from_indicator: Some(row),
6712 quick_launch,
6713 },
6714 window,
6715 cx,
6716 );
6717 }))
6718 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6719 editor.set_breakpoint_context_menu(
6720 row,
6721 position,
6722 event.down.position,
6723 window,
6724 cx,
6725 );
6726 })),
6727 )
6728 } else {
6729 None
6730 }
6731 }
6732
6733 fn clear_tasks(&mut self) {
6734 self.tasks.clear()
6735 }
6736
6737 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6738 if self.tasks.insert(key, value).is_some() {
6739 // This case should hopefully be rare, but just in case...
6740 log::error!(
6741 "multiple different run targets found on a single line, only the last target will be rendered"
6742 )
6743 }
6744 }
6745
6746 /// Get all display points of breakpoints that will be rendered within editor
6747 ///
6748 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6749 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6750 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6751 fn active_breakpoints(
6752 &self,
6753 range: Range<DisplayRow>,
6754 window: &mut Window,
6755 cx: &mut Context<Self>,
6756 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6757 let mut breakpoint_display_points = HashMap::default();
6758
6759 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6760 return breakpoint_display_points;
6761 };
6762
6763 let snapshot = self.snapshot(window, cx);
6764
6765 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6766 let Some(project) = self.project.as_ref() else {
6767 return breakpoint_display_points;
6768 };
6769
6770 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6771 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6772
6773 for (buffer_snapshot, range, excerpt_id) in
6774 multi_buffer_snapshot.range_to_buffer_ranges(range)
6775 {
6776 let Some(buffer) = project.read_with(cx, |this, cx| {
6777 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6778 }) else {
6779 continue;
6780 };
6781 let breakpoints = breakpoint_store.read(cx).breakpoints(
6782 &buffer,
6783 Some(
6784 buffer_snapshot.anchor_before(range.start)
6785 ..buffer_snapshot.anchor_after(range.end),
6786 ),
6787 buffer_snapshot,
6788 cx,
6789 );
6790 for (anchor, breakpoint) in breakpoints {
6791 let multi_buffer_anchor =
6792 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6793 let position = multi_buffer_anchor
6794 .to_point(&multi_buffer_snapshot)
6795 .to_display_point(&snapshot);
6796
6797 breakpoint_display_points
6798 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6799 }
6800 }
6801
6802 breakpoint_display_points
6803 }
6804
6805 fn breakpoint_context_menu(
6806 &self,
6807 anchor: Anchor,
6808 window: &mut Window,
6809 cx: &mut Context<Self>,
6810 ) -> Entity<ui::ContextMenu> {
6811 let weak_editor = cx.weak_entity();
6812 let focus_handle = self.focus_handle(cx);
6813
6814 let row = self
6815 .buffer
6816 .read(cx)
6817 .snapshot(cx)
6818 .summary_for_anchor::<Point>(&anchor)
6819 .row;
6820
6821 let breakpoint = self
6822 .breakpoint_at_row(row, window, cx)
6823 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6824
6825 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6826 "Edit Log Breakpoint"
6827 } else {
6828 "Set Log Breakpoint"
6829 };
6830
6831 let condition_breakpoint_msg = if breakpoint
6832 .as_ref()
6833 .is_some_and(|bp| bp.1.condition.is_some())
6834 {
6835 "Edit Condition Breakpoint"
6836 } else {
6837 "Set Condition Breakpoint"
6838 };
6839
6840 let hit_condition_breakpoint_msg = if breakpoint
6841 .as_ref()
6842 .is_some_and(|bp| bp.1.hit_condition.is_some())
6843 {
6844 "Edit Hit Condition Breakpoint"
6845 } else {
6846 "Set Hit Condition Breakpoint"
6847 };
6848
6849 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6850 "Unset Breakpoint"
6851 } else {
6852 "Set Breakpoint"
6853 };
6854
6855 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6856 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6857
6858 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6859 BreakpointState::Enabled => Some("Disable"),
6860 BreakpointState::Disabled => Some("Enable"),
6861 });
6862
6863 let (anchor, breakpoint) =
6864 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6865
6866 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6867 menu.on_blur_subscription(Subscription::new(|| {}))
6868 .context(focus_handle)
6869 .when(run_to_cursor, |this| {
6870 let weak_editor = weak_editor.clone();
6871 this.entry("Run to cursor", None, move |window, cx| {
6872 weak_editor
6873 .update(cx, |editor, cx| {
6874 editor.change_selections(None, window, cx, |s| {
6875 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6876 });
6877 })
6878 .ok();
6879
6880 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6881 })
6882 .separator()
6883 })
6884 .when_some(toggle_state_msg, |this, msg| {
6885 this.entry(msg, None, {
6886 let weak_editor = weak_editor.clone();
6887 let breakpoint = breakpoint.clone();
6888 move |_window, cx| {
6889 weak_editor
6890 .update(cx, |this, cx| {
6891 this.edit_breakpoint_at_anchor(
6892 anchor,
6893 breakpoint.as_ref().clone(),
6894 BreakpointEditAction::InvertState,
6895 cx,
6896 );
6897 })
6898 .log_err();
6899 }
6900 })
6901 })
6902 .entry(set_breakpoint_msg, None, {
6903 let weak_editor = weak_editor.clone();
6904 let breakpoint = breakpoint.clone();
6905 move |_window, cx| {
6906 weak_editor
6907 .update(cx, |this, cx| {
6908 this.edit_breakpoint_at_anchor(
6909 anchor,
6910 breakpoint.as_ref().clone(),
6911 BreakpointEditAction::Toggle,
6912 cx,
6913 );
6914 })
6915 .log_err();
6916 }
6917 })
6918 .entry(log_breakpoint_msg, None, {
6919 let breakpoint = breakpoint.clone();
6920 let weak_editor = weak_editor.clone();
6921 move |window, cx| {
6922 weak_editor
6923 .update(cx, |this, cx| {
6924 this.add_edit_breakpoint_block(
6925 anchor,
6926 breakpoint.as_ref(),
6927 BreakpointPromptEditAction::Log,
6928 window,
6929 cx,
6930 );
6931 })
6932 .log_err();
6933 }
6934 })
6935 .entry(condition_breakpoint_msg, None, {
6936 let breakpoint = breakpoint.clone();
6937 let weak_editor = weak_editor.clone();
6938 move |window, cx| {
6939 weak_editor
6940 .update(cx, |this, cx| {
6941 this.add_edit_breakpoint_block(
6942 anchor,
6943 breakpoint.as_ref(),
6944 BreakpointPromptEditAction::Condition,
6945 window,
6946 cx,
6947 );
6948 })
6949 .log_err();
6950 }
6951 })
6952 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6953 weak_editor
6954 .update(cx, |this, cx| {
6955 this.add_edit_breakpoint_block(
6956 anchor,
6957 breakpoint.as_ref(),
6958 BreakpointPromptEditAction::HitCondition,
6959 window,
6960 cx,
6961 );
6962 })
6963 .log_err();
6964 })
6965 })
6966 }
6967
6968 fn render_breakpoint(
6969 &self,
6970 position: Anchor,
6971 row: DisplayRow,
6972 breakpoint: &Breakpoint,
6973 cx: &mut Context<Self>,
6974 ) -> IconButton {
6975 // Is it a breakpoint that shows up when hovering over gutter?
6976 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
6977 (false, false),
6978 |PhantomBreakpointIndicator {
6979 is_active,
6980 display_row,
6981 collides_with_existing_breakpoint,
6982 }| {
6983 (
6984 is_active && display_row == row,
6985 collides_with_existing_breakpoint,
6986 )
6987 },
6988 );
6989
6990 let (color, icon) = {
6991 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
6992 (false, false) => ui::IconName::DebugBreakpoint,
6993 (true, false) => ui::IconName::DebugLogBreakpoint,
6994 (false, true) => ui::IconName::DebugDisabledBreakpoint,
6995 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
6996 };
6997
6998 let color = if is_phantom {
6999 Color::Hint
7000 } else {
7001 Color::Debugger
7002 };
7003
7004 (color, icon)
7005 };
7006
7007 let breakpoint = Arc::from(breakpoint.clone());
7008
7009 let alt_as_text = gpui::Keystroke {
7010 modifiers: Modifiers::secondary_key(),
7011 ..Default::default()
7012 };
7013 let primary_action_text = if breakpoint.is_disabled() {
7014 "enable"
7015 } else if is_phantom && !collides_with_existing {
7016 "set"
7017 } else {
7018 "unset"
7019 };
7020 let mut primary_text = format!("Click to {primary_action_text}");
7021 if collides_with_existing && !breakpoint.is_disabled() {
7022 use std::fmt::Write;
7023 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7024 }
7025 let primary_text = SharedString::from(primary_text);
7026 let focus_handle = self.focus_handle.clone();
7027 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7028 .icon_size(IconSize::XSmall)
7029 .size(ui::ButtonSize::None)
7030 .icon_color(color)
7031 .style(ButtonStyle::Transparent)
7032 .on_click(cx.listener({
7033 let breakpoint = breakpoint.clone();
7034
7035 move |editor, event: &ClickEvent, window, cx| {
7036 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7037 BreakpointEditAction::InvertState
7038 } else {
7039 BreakpointEditAction::Toggle
7040 };
7041
7042 window.focus(&editor.focus_handle(cx));
7043 editor.edit_breakpoint_at_anchor(
7044 position,
7045 breakpoint.as_ref().clone(),
7046 edit_action,
7047 cx,
7048 );
7049 }
7050 }))
7051 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7052 editor.set_breakpoint_context_menu(
7053 row,
7054 Some(position),
7055 event.down.position,
7056 window,
7057 cx,
7058 );
7059 }))
7060 .tooltip(move |window, cx| {
7061 Tooltip::with_meta_in(
7062 primary_text.clone(),
7063 None,
7064 "Right-click for more options",
7065 &focus_handle,
7066 window,
7067 cx,
7068 )
7069 })
7070 }
7071
7072 fn build_tasks_context(
7073 project: &Entity<Project>,
7074 buffer: &Entity<Buffer>,
7075 buffer_row: u32,
7076 tasks: &Arc<RunnableTasks>,
7077 cx: &mut Context<Self>,
7078 ) -> Task<Option<task::TaskContext>> {
7079 let position = Point::new(buffer_row, tasks.column);
7080 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7081 let location = Location {
7082 buffer: buffer.clone(),
7083 range: range_start..range_start,
7084 };
7085 // Fill in the environmental variables from the tree-sitter captures
7086 let mut captured_task_variables = TaskVariables::default();
7087 for (capture_name, value) in tasks.extra_variables.clone() {
7088 captured_task_variables.insert(
7089 task::VariableName::Custom(capture_name.into()),
7090 value.clone(),
7091 );
7092 }
7093 project.update(cx, |project, cx| {
7094 project.task_store().update(cx, |task_store, cx| {
7095 task_store.task_context_for_location(captured_task_variables, location, cx)
7096 })
7097 })
7098 }
7099
7100 pub fn spawn_nearest_task(
7101 &mut self,
7102 action: &SpawnNearestTask,
7103 window: &mut Window,
7104 cx: &mut Context<Self>,
7105 ) {
7106 let Some((workspace, _)) = self.workspace.clone() else {
7107 return;
7108 };
7109 let Some(project) = self.project.clone() else {
7110 return;
7111 };
7112
7113 // Try to find a closest, enclosing node using tree-sitter that has a
7114 // task
7115 let Some((buffer, buffer_row, tasks)) = self
7116 .find_enclosing_node_task(cx)
7117 // Or find the task that's closest in row-distance.
7118 .or_else(|| self.find_closest_task(cx))
7119 else {
7120 return;
7121 };
7122
7123 let reveal_strategy = action.reveal;
7124 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7125 cx.spawn_in(window, async move |_, cx| {
7126 let context = task_context.await?;
7127 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7128
7129 let resolved = &mut resolved_task.resolved;
7130 resolved.reveal = reveal_strategy;
7131
7132 workspace
7133 .update_in(cx, |workspace, window, cx| {
7134 workspace.schedule_resolved_task(
7135 task_source_kind,
7136 resolved_task,
7137 false,
7138 window,
7139 cx,
7140 );
7141 })
7142 .ok()
7143 })
7144 .detach();
7145 }
7146
7147 fn find_closest_task(
7148 &mut self,
7149 cx: &mut Context<Self>,
7150 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7151 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7152
7153 let ((buffer_id, row), tasks) = self
7154 .tasks
7155 .iter()
7156 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7157
7158 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7159 let tasks = Arc::new(tasks.to_owned());
7160 Some((buffer, *row, tasks))
7161 }
7162
7163 fn find_enclosing_node_task(
7164 &mut self,
7165 cx: &mut Context<Self>,
7166 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7167 let snapshot = self.buffer.read(cx).snapshot(cx);
7168 let offset = self.selections.newest::<usize>(cx).head();
7169 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7170 let buffer_id = excerpt.buffer().remote_id();
7171
7172 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7173 let mut cursor = layer.node().walk();
7174
7175 while cursor.goto_first_child_for_byte(offset).is_some() {
7176 if cursor.node().end_byte() == offset {
7177 cursor.goto_next_sibling();
7178 }
7179 }
7180
7181 // Ascend to the smallest ancestor that contains the range and has a task.
7182 loop {
7183 let node = cursor.node();
7184 let node_range = node.byte_range();
7185 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7186
7187 // Check if this node contains our offset
7188 if node_range.start <= offset && node_range.end >= offset {
7189 // If it contains offset, check for task
7190 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7191 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7192 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7193 }
7194 }
7195
7196 if !cursor.goto_parent() {
7197 break;
7198 }
7199 }
7200 None
7201 }
7202
7203 fn render_run_indicator(
7204 &self,
7205 _style: &EditorStyle,
7206 is_active: bool,
7207 row: DisplayRow,
7208 breakpoint: Option<(Anchor, Breakpoint)>,
7209 cx: &mut Context<Self>,
7210 ) -> IconButton {
7211 let color = Color::Muted;
7212 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7213
7214 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7215 .shape(ui::IconButtonShape::Square)
7216 .icon_size(IconSize::XSmall)
7217 .icon_color(color)
7218 .toggle_state(is_active)
7219 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7220 let quick_launch = e.down.button == MouseButton::Left;
7221 window.focus(&editor.focus_handle(cx));
7222 editor.toggle_code_actions(
7223 &ToggleCodeActions {
7224 deployed_from_indicator: Some(row),
7225 quick_launch,
7226 },
7227 window,
7228 cx,
7229 );
7230 }))
7231 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7232 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7233 }))
7234 }
7235
7236 pub fn context_menu_visible(&self) -> bool {
7237 !self.edit_prediction_preview_is_active()
7238 && self
7239 .context_menu
7240 .borrow()
7241 .as_ref()
7242 .map_or(false, |menu| menu.visible())
7243 }
7244
7245 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7246 self.context_menu
7247 .borrow()
7248 .as_ref()
7249 .map(|menu| menu.origin())
7250 }
7251
7252 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7253 self.context_menu_options = Some(options);
7254 }
7255
7256 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7257 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7258
7259 fn render_edit_prediction_popover(
7260 &mut self,
7261 text_bounds: &Bounds<Pixels>,
7262 content_origin: gpui::Point<Pixels>,
7263 editor_snapshot: &EditorSnapshot,
7264 visible_row_range: Range<DisplayRow>,
7265 scroll_top: f32,
7266 scroll_bottom: f32,
7267 line_layouts: &[LineWithInvisibles],
7268 line_height: Pixels,
7269 scroll_pixel_position: gpui::Point<Pixels>,
7270 newest_selection_head: Option<DisplayPoint>,
7271 editor_width: Pixels,
7272 style: &EditorStyle,
7273 window: &mut Window,
7274 cx: &mut App,
7275 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7276 let active_inline_completion = self.active_inline_completion.as_ref()?;
7277
7278 if self.edit_prediction_visible_in_cursor_popover(true) {
7279 return None;
7280 }
7281
7282 match &active_inline_completion.completion {
7283 InlineCompletion::Move { target, .. } => {
7284 let target_display_point = target.to_display_point(editor_snapshot);
7285
7286 if self.edit_prediction_requires_modifier() {
7287 if !self.edit_prediction_preview_is_active() {
7288 return None;
7289 }
7290
7291 self.render_edit_prediction_modifier_jump_popover(
7292 text_bounds,
7293 content_origin,
7294 visible_row_range,
7295 line_layouts,
7296 line_height,
7297 scroll_pixel_position,
7298 newest_selection_head,
7299 target_display_point,
7300 window,
7301 cx,
7302 )
7303 } else {
7304 self.render_edit_prediction_eager_jump_popover(
7305 text_bounds,
7306 content_origin,
7307 editor_snapshot,
7308 visible_row_range,
7309 scroll_top,
7310 scroll_bottom,
7311 line_height,
7312 scroll_pixel_position,
7313 target_display_point,
7314 editor_width,
7315 window,
7316 cx,
7317 )
7318 }
7319 }
7320 InlineCompletion::Edit {
7321 display_mode: EditDisplayMode::Inline,
7322 ..
7323 } => None,
7324 InlineCompletion::Edit {
7325 display_mode: EditDisplayMode::TabAccept,
7326 edits,
7327 ..
7328 } => {
7329 let range = &edits.first()?.0;
7330 let target_display_point = range.end.to_display_point(editor_snapshot);
7331
7332 self.render_edit_prediction_end_of_line_popover(
7333 "Accept",
7334 editor_snapshot,
7335 visible_row_range,
7336 target_display_point,
7337 line_height,
7338 scroll_pixel_position,
7339 content_origin,
7340 editor_width,
7341 window,
7342 cx,
7343 )
7344 }
7345 InlineCompletion::Edit {
7346 edits,
7347 edit_preview,
7348 display_mode: EditDisplayMode::DiffPopover,
7349 snapshot,
7350 } => self.render_edit_prediction_diff_popover(
7351 text_bounds,
7352 content_origin,
7353 editor_snapshot,
7354 visible_row_range,
7355 line_layouts,
7356 line_height,
7357 scroll_pixel_position,
7358 newest_selection_head,
7359 editor_width,
7360 style,
7361 edits,
7362 edit_preview,
7363 snapshot,
7364 window,
7365 cx,
7366 ),
7367 }
7368 }
7369
7370 fn render_edit_prediction_modifier_jump_popover(
7371 &mut self,
7372 text_bounds: &Bounds<Pixels>,
7373 content_origin: gpui::Point<Pixels>,
7374 visible_row_range: Range<DisplayRow>,
7375 line_layouts: &[LineWithInvisibles],
7376 line_height: Pixels,
7377 scroll_pixel_position: gpui::Point<Pixels>,
7378 newest_selection_head: Option<DisplayPoint>,
7379 target_display_point: DisplayPoint,
7380 window: &mut Window,
7381 cx: &mut App,
7382 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7383 let scrolled_content_origin =
7384 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7385
7386 const SCROLL_PADDING_Y: Pixels = px(12.);
7387
7388 if target_display_point.row() < visible_row_range.start {
7389 return self.render_edit_prediction_scroll_popover(
7390 |_| SCROLL_PADDING_Y,
7391 IconName::ArrowUp,
7392 visible_row_range,
7393 line_layouts,
7394 newest_selection_head,
7395 scrolled_content_origin,
7396 window,
7397 cx,
7398 );
7399 } else if target_display_point.row() >= visible_row_range.end {
7400 return self.render_edit_prediction_scroll_popover(
7401 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7402 IconName::ArrowDown,
7403 visible_row_range,
7404 line_layouts,
7405 newest_selection_head,
7406 scrolled_content_origin,
7407 window,
7408 cx,
7409 );
7410 }
7411
7412 const POLE_WIDTH: Pixels = px(2.);
7413
7414 let line_layout =
7415 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7416 let target_column = target_display_point.column() as usize;
7417
7418 let target_x = line_layout.x_for_index(target_column);
7419 let target_y =
7420 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7421
7422 let flag_on_right = target_x < text_bounds.size.width / 2.;
7423
7424 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7425 border_color.l += 0.001;
7426
7427 let mut element = v_flex()
7428 .items_end()
7429 .when(flag_on_right, |el| el.items_start())
7430 .child(if flag_on_right {
7431 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7432 .rounded_bl(px(0.))
7433 .rounded_tl(px(0.))
7434 .border_l_2()
7435 .border_color(border_color)
7436 } else {
7437 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7438 .rounded_br(px(0.))
7439 .rounded_tr(px(0.))
7440 .border_r_2()
7441 .border_color(border_color)
7442 })
7443 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7444 .into_any();
7445
7446 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7447
7448 let mut origin = scrolled_content_origin + point(target_x, target_y)
7449 - point(
7450 if flag_on_right {
7451 POLE_WIDTH
7452 } else {
7453 size.width - POLE_WIDTH
7454 },
7455 size.height - line_height,
7456 );
7457
7458 origin.x = origin.x.max(content_origin.x);
7459
7460 element.prepaint_at(origin, window, cx);
7461
7462 Some((element, origin))
7463 }
7464
7465 fn render_edit_prediction_scroll_popover(
7466 &mut self,
7467 to_y: impl Fn(Size<Pixels>) -> Pixels,
7468 scroll_icon: IconName,
7469 visible_row_range: Range<DisplayRow>,
7470 line_layouts: &[LineWithInvisibles],
7471 newest_selection_head: Option<DisplayPoint>,
7472 scrolled_content_origin: gpui::Point<Pixels>,
7473 window: &mut Window,
7474 cx: &mut App,
7475 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7476 let mut element = self
7477 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7478 .into_any();
7479
7480 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7481
7482 let cursor = newest_selection_head?;
7483 let cursor_row_layout =
7484 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7485 let cursor_column = cursor.column() as usize;
7486
7487 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7488
7489 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7490
7491 element.prepaint_at(origin, window, cx);
7492 Some((element, origin))
7493 }
7494
7495 fn render_edit_prediction_eager_jump_popover(
7496 &mut self,
7497 text_bounds: &Bounds<Pixels>,
7498 content_origin: gpui::Point<Pixels>,
7499 editor_snapshot: &EditorSnapshot,
7500 visible_row_range: Range<DisplayRow>,
7501 scroll_top: f32,
7502 scroll_bottom: f32,
7503 line_height: Pixels,
7504 scroll_pixel_position: gpui::Point<Pixels>,
7505 target_display_point: DisplayPoint,
7506 editor_width: Pixels,
7507 window: &mut Window,
7508 cx: &mut App,
7509 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7510 if target_display_point.row().as_f32() < scroll_top {
7511 let mut element = self
7512 .render_edit_prediction_line_popover(
7513 "Jump to Edit",
7514 Some(IconName::ArrowUp),
7515 window,
7516 cx,
7517 )?
7518 .into_any();
7519
7520 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7521 let offset = point(
7522 (text_bounds.size.width - size.width) / 2.,
7523 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7524 );
7525
7526 let origin = text_bounds.origin + offset;
7527 element.prepaint_at(origin, window, cx);
7528 Some((element, origin))
7529 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7530 let mut element = self
7531 .render_edit_prediction_line_popover(
7532 "Jump to Edit",
7533 Some(IconName::ArrowDown),
7534 window,
7535 cx,
7536 )?
7537 .into_any();
7538
7539 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7540 let offset = point(
7541 (text_bounds.size.width - size.width) / 2.,
7542 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7543 );
7544
7545 let origin = text_bounds.origin + offset;
7546 element.prepaint_at(origin, window, cx);
7547 Some((element, origin))
7548 } else {
7549 self.render_edit_prediction_end_of_line_popover(
7550 "Jump to Edit",
7551 editor_snapshot,
7552 visible_row_range,
7553 target_display_point,
7554 line_height,
7555 scroll_pixel_position,
7556 content_origin,
7557 editor_width,
7558 window,
7559 cx,
7560 )
7561 }
7562 }
7563
7564 fn render_edit_prediction_end_of_line_popover(
7565 self: &mut Editor,
7566 label: &'static str,
7567 editor_snapshot: &EditorSnapshot,
7568 visible_row_range: Range<DisplayRow>,
7569 target_display_point: DisplayPoint,
7570 line_height: Pixels,
7571 scroll_pixel_position: gpui::Point<Pixels>,
7572 content_origin: gpui::Point<Pixels>,
7573 editor_width: Pixels,
7574 window: &mut Window,
7575 cx: &mut App,
7576 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7577 let target_line_end = DisplayPoint::new(
7578 target_display_point.row(),
7579 editor_snapshot.line_len(target_display_point.row()),
7580 );
7581
7582 let mut element = self
7583 .render_edit_prediction_line_popover(label, None, window, cx)?
7584 .into_any();
7585
7586 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7587
7588 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7589
7590 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7591 let mut origin = start_point
7592 + line_origin
7593 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7594 origin.x = origin.x.max(content_origin.x);
7595
7596 let max_x = content_origin.x + editor_width - size.width;
7597
7598 if origin.x > max_x {
7599 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7600
7601 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7602 origin.y += offset;
7603 IconName::ArrowUp
7604 } else {
7605 origin.y -= offset;
7606 IconName::ArrowDown
7607 };
7608
7609 element = self
7610 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7611 .into_any();
7612
7613 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7614
7615 origin.x = content_origin.x + editor_width - size.width - px(2.);
7616 }
7617
7618 element.prepaint_at(origin, window, cx);
7619 Some((element, origin))
7620 }
7621
7622 fn render_edit_prediction_diff_popover(
7623 self: &Editor,
7624 text_bounds: &Bounds<Pixels>,
7625 content_origin: gpui::Point<Pixels>,
7626 editor_snapshot: &EditorSnapshot,
7627 visible_row_range: Range<DisplayRow>,
7628 line_layouts: &[LineWithInvisibles],
7629 line_height: Pixels,
7630 scroll_pixel_position: gpui::Point<Pixels>,
7631 newest_selection_head: Option<DisplayPoint>,
7632 editor_width: Pixels,
7633 style: &EditorStyle,
7634 edits: &Vec<(Range<Anchor>, String)>,
7635 edit_preview: &Option<language::EditPreview>,
7636 snapshot: &language::BufferSnapshot,
7637 window: &mut Window,
7638 cx: &mut App,
7639 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7640 let edit_start = edits
7641 .first()
7642 .unwrap()
7643 .0
7644 .start
7645 .to_display_point(editor_snapshot);
7646 let edit_end = edits
7647 .last()
7648 .unwrap()
7649 .0
7650 .end
7651 .to_display_point(editor_snapshot);
7652
7653 let is_visible = visible_row_range.contains(&edit_start.row())
7654 || visible_row_range.contains(&edit_end.row());
7655 if !is_visible {
7656 return None;
7657 }
7658
7659 let highlighted_edits =
7660 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7661
7662 let styled_text = highlighted_edits.to_styled_text(&style.text);
7663 let line_count = highlighted_edits.text.lines().count();
7664
7665 const BORDER_WIDTH: Pixels = px(1.);
7666
7667 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7668 let has_keybind = keybind.is_some();
7669
7670 let mut element = h_flex()
7671 .items_start()
7672 .child(
7673 h_flex()
7674 .bg(cx.theme().colors().editor_background)
7675 .border(BORDER_WIDTH)
7676 .shadow_sm()
7677 .border_color(cx.theme().colors().border)
7678 .rounded_l_lg()
7679 .when(line_count > 1, |el| el.rounded_br_lg())
7680 .pr_1()
7681 .child(styled_text),
7682 )
7683 .child(
7684 h_flex()
7685 .h(line_height + BORDER_WIDTH * 2.)
7686 .px_1p5()
7687 .gap_1()
7688 // Workaround: For some reason, there's a gap if we don't do this
7689 .ml(-BORDER_WIDTH)
7690 .shadow(smallvec![gpui::BoxShadow {
7691 color: gpui::black().opacity(0.05),
7692 offset: point(px(1.), px(1.)),
7693 blur_radius: px(2.),
7694 spread_radius: px(0.),
7695 }])
7696 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7697 .border(BORDER_WIDTH)
7698 .border_color(cx.theme().colors().border)
7699 .rounded_r_lg()
7700 .id("edit_prediction_diff_popover_keybind")
7701 .when(!has_keybind, |el| {
7702 let status_colors = cx.theme().status();
7703
7704 el.bg(status_colors.error_background)
7705 .border_color(status_colors.error.opacity(0.6))
7706 .child(Icon::new(IconName::Info).color(Color::Error))
7707 .cursor_default()
7708 .hoverable_tooltip(move |_window, cx| {
7709 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7710 })
7711 })
7712 .children(keybind),
7713 )
7714 .into_any();
7715
7716 let longest_row =
7717 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7718 let longest_line_width = if visible_row_range.contains(&longest_row) {
7719 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7720 } else {
7721 layout_line(
7722 longest_row,
7723 editor_snapshot,
7724 style,
7725 editor_width,
7726 |_| false,
7727 window,
7728 cx,
7729 )
7730 .width
7731 };
7732
7733 let viewport_bounds =
7734 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7735 right: -EditorElement::SCROLLBAR_WIDTH,
7736 ..Default::default()
7737 });
7738
7739 let x_after_longest =
7740 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7741 - scroll_pixel_position.x;
7742
7743 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7744
7745 // Fully visible if it can be displayed within the window (allow overlapping other
7746 // panes). However, this is only allowed if the popover starts within text_bounds.
7747 let can_position_to_the_right = x_after_longest < text_bounds.right()
7748 && x_after_longest + element_bounds.width < viewport_bounds.right();
7749
7750 let mut origin = if can_position_to_the_right {
7751 point(
7752 x_after_longest,
7753 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7754 - scroll_pixel_position.y,
7755 )
7756 } else {
7757 let cursor_row = newest_selection_head.map(|head| head.row());
7758 let above_edit = edit_start
7759 .row()
7760 .0
7761 .checked_sub(line_count as u32)
7762 .map(DisplayRow);
7763 let below_edit = Some(edit_end.row() + 1);
7764 let above_cursor =
7765 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7766 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7767
7768 // Place the edit popover adjacent to the edit if there is a location
7769 // available that is onscreen and does not obscure the cursor. Otherwise,
7770 // place it adjacent to the cursor.
7771 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7772 .into_iter()
7773 .flatten()
7774 .find(|&start_row| {
7775 let end_row = start_row + line_count as u32;
7776 visible_row_range.contains(&start_row)
7777 && visible_row_range.contains(&end_row)
7778 && cursor_row.map_or(true, |cursor_row| {
7779 !((start_row..end_row).contains(&cursor_row))
7780 })
7781 })?;
7782
7783 content_origin
7784 + point(
7785 -scroll_pixel_position.x,
7786 row_target.as_f32() * line_height - scroll_pixel_position.y,
7787 )
7788 };
7789
7790 origin.x -= BORDER_WIDTH;
7791
7792 window.defer_draw(element, origin, 1);
7793
7794 // Do not return an element, since it will already be drawn due to defer_draw.
7795 None
7796 }
7797
7798 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7799 px(30.)
7800 }
7801
7802 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7803 if self.read_only(cx) {
7804 cx.theme().players().read_only()
7805 } else {
7806 self.style.as_ref().unwrap().local_player
7807 }
7808 }
7809
7810 fn render_edit_prediction_accept_keybind(
7811 &self,
7812 window: &mut Window,
7813 cx: &App,
7814 ) -> Option<AnyElement> {
7815 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7816 let accept_keystroke = accept_binding.keystroke()?;
7817
7818 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7819
7820 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7821 Color::Accent
7822 } else {
7823 Color::Muted
7824 };
7825
7826 h_flex()
7827 .px_0p5()
7828 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7829 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7830 .text_size(TextSize::XSmall.rems(cx))
7831 .child(h_flex().children(ui::render_modifiers(
7832 &accept_keystroke.modifiers,
7833 PlatformStyle::platform(),
7834 Some(modifiers_color),
7835 Some(IconSize::XSmall.rems().into()),
7836 true,
7837 )))
7838 .when(is_platform_style_mac, |parent| {
7839 parent.child(accept_keystroke.key.clone())
7840 })
7841 .when(!is_platform_style_mac, |parent| {
7842 parent.child(
7843 Key::new(
7844 util::capitalize(&accept_keystroke.key),
7845 Some(Color::Default),
7846 )
7847 .size(Some(IconSize::XSmall.rems().into())),
7848 )
7849 })
7850 .into_any()
7851 .into()
7852 }
7853
7854 fn render_edit_prediction_line_popover(
7855 &self,
7856 label: impl Into<SharedString>,
7857 icon: Option<IconName>,
7858 window: &mut Window,
7859 cx: &App,
7860 ) -> Option<Stateful<Div>> {
7861 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7862
7863 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7864 let has_keybind = keybind.is_some();
7865
7866 let result = h_flex()
7867 .id("ep-line-popover")
7868 .py_0p5()
7869 .pl_1()
7870 .pr(padding_right)
7871 .gap_1()
7872 .rounded_md()
7873 .border_1()
7874 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7875 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7876 .shadow_sm()
7877 .when(!has_keybind, |el| {
7878 let status_colors = cx.theme().status();
7879
7880 el.bg(status_colors.error_background)
7881 .border_color(status_colors.error.opacity(0.6))
7882 .pl_2()
7883 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7884 .cursor_default()
7885 .hoverable_tooltip(move |_window, cx| {
7886 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7887 })
7888 })
7889 .children(keybind)
7890 .child(
7891 Label::new(label)
7892 .size(LabelSize::Small)
7893 .when(!has_keybind, |el| {
7894 el.color(cx.theme().status().error.into()).strikethrough()
7895 }),
7896 )
7897 .when(!has_keybind, |el| {
7898 el.child(
7899 h_flex().ml_1().child(
7900 Icon::new(IconName::Info)
7901 .size(IconSize::Small)
7902 .color(cx.theme().status().error.into()),
7903 ),
7904 )
7905 })
7906 .when_some(icon, |element, icon| {
7907 element.child(
7908 div()
7909 .mt(px(1.5))
7910 .child(Icon::new(icon).size(IconSize::Small)),
7911 )
7912 });
7913
7914 Some(result)
7915 }
7916
7917 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7918 let accent_color = cx.theme().colors().text_accent;
7919 let editor_bg_color = cx.theme().colors().editor_background;
7920 editor_bg_color.blend(accent_color.opacity(0.1))
7921 }
7922
7923 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7924 let accent_color = cx.theme().colors().text_accent;
7925 let editor_bg_color = cx.theme().colors().editor_background;
7926 editor_bg_color.blend(accent_color.opacity(0.6))
7927 }
7928
7929 fn render_edit_prediction_cursor_popover(
7930 &self,
7931 min_width: Pixels,
7932 max_width: Pixels,
7933 cursor_point: Point,
7934 style: &EditorStyle,
7935 accept_keystroke: Option<&gpui::Keystroke>,
7936 _window: &Window,
7937 cx: &mut Context<Editor>,
7938 ) -> Option<AnyElement> {
7939 let provider = self.edit_prediction_provider.as_ref()?;
7940
7941 if provider.provider.needs_terms_acceptance(cx) {
7942 return Some(
7943 h_flex()
7944 .min_w(min_width)
7945 .flex_1()
7946 .px_2()
7947 .py_1()
7948 .gap_3()
7949 .elevation_2(cx)
7950 .hover(|style| style.bg(cx.theme().colors().element_hover))
7951 .id("accept-terms")
7952 .cursor_pointer()
7953 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7954 .on_click(cx.listener(|this, _event, window, cx| {
7955 cx.stop_propagation();
7956 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7957 window.dispatch_action(
7958 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7959 cx,
7960 );
7961 }))
7962 .child(
7963 h_flex()
7964 .flex_1()
7965 .gap_2()
7966 .child(Icon::new(IconName::ZedPredict))
7967 .child(Label::new("Accept Terms of Service"))
7968 .child(div().w_full())
7969 .child(
7970 Icon::new(IconName::ArrowUpRight)
7971 .color(Color::Muted)
7972 .size(IconSize::Small),
7973 )
7974 .into_any_element(),
7975 )
7976 .into_any(),
7977 );
7978 }
7979
7980 let is_refreshing = provider.provider.is_refreshing(cx);
7981
7982 fn pending_completion_container() -> Div {
7983 h_flex()
7984 .h_full()
7985 .flex_1()
7986 .gap_2()
7987 .child(Icon::new(IconName::ZedPredict))
7988 }
7989
7990 let completion = match &self.active_inline_completion {
7991 Some(prediction) => {
7992 if !self.has_visible_completions_menu() {
7993 const RADIUS: Pixels = px(6.);
7994 const BORDER_WIDTH: Pixels = px(1.);
7995
7996 return Some(
7997 h_flex()
7998 .elevation_2(cx)
7999 .border(BORDER_WIDTH)
8000 .border_color(cx.theme().colors().border)
8001 .when(accept_keystroke.is_none(), |el| {
8002 el.border_color(cx.theme().status().error)
8003 })
8004 .rounded(RADIUS)
8005 .rounded_tl(px(0.))
8006 .overflow_hidden()
8007 .child(div().px_1p5().child(match &prediction.completion {
8008 InlineCompletion::Move { target, snapshot } => {
8009 use text::ToPoint as _;
8010 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8011 {
8012 Icon::new(IconName::ZedPredictDown)
8013 } else {
8014 Icon::new(IconName::ZedPredictUp)
8015 }
8016 }
8017 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8018 }))
8019 .child(
8020 h_flex()
8021 .gap_1()
8022 .py_1()
8023 .px_2()
8024 .rounded_r(RADIUS - BORDER_WIDTH)
8025 .border_l_1()
8026 .border_color(cx.theme().colors().border)
8027 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8028 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8029 el.child(
8030 Label::new("Hold")
8031 .size(LabelSize::Small)
8032 .when(accept_keystroke.is_none(), |el| {
8033 el.strikethrough()
8034 })
8035 .line_height_style(LineHeightStyle::UiLabel),
8036 )
8037 })
8038 .id("edit_prediction_cursor_popover_keybind")
8039 .when(accept_keystroke.is_none(), |el| {
8040 let status_colors = cx.theme().status();
8041
8042 el.bg(status_colors.error_background)
8043 .border_color(status_colors.error.opacity(0.6))
8044 .child(Icon::new(IconName::Info).color(Color::Error))
8045 .cursor_default()
8046 .hoverable_tooltip(move |_window, cx| {
8047 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8048 .into()
8049 })
8050 })
8051 .when_some(
8052 accept_keystroke.as_ref(),
8053 |el, accept_keystroke| {
8054 el.child(h_flex().children(ui::render_modifiers(
8055 &accept_keystroke.modifiers,
8056 PlatformStyle::platform(),
8057 Some(Color::Default),
8058 Some(IconSize::XSmall.rems().into()),
8059 false,
8060 )))
8061 },
8062 ),
8063 )
8064 .into_any(),
8065 );
8066 }
8067
8068 self.render_edit_prediction_cursor_popover_preview(
8069 prediction,
8070 cursor_point,
8071 style,
8072 cx,
8073 )?
8074 }
8075
8076 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8077 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8078 stale_completion,
8079 cursor_point,
8080 style,
8081 cx,
8082 )?,
8083
8084 None => {
8085 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8086 }
8087 },
8088
8089 None => pending_completion_container().child(Label::new("No Prediction")),
8090 };
8091
8092 let completion = if is_refreshing {
8093 completion
8094 .with_animation(
8095 "loading-completion",
8096 Animation::new(Duration::from_secs(2))
8097 .repeat()
8098 .with_easing(pulsating_between(0.4, 0.8)),
8099 |label, delta| label.opacity(delta),
8100 )
8101 .into_any_element()
8102 } else {
8103 completion.into_any_element()
8104 };
8105
8106 let has_completion = self.active_inline_completion.is_some();
8107
8108 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8109 Some(
8110 h_flex()
8111 .min_w(min_width)
8112 .max_w(max_width)
8113 .flex_1()
8114 .elevation_2(cx)
8115 .border_color(cx.theme().colors().border)
8116 .child(
8117 div()
8118 .flex_1()
8119 .py_1()
8120 .px_2()
8121 .overflow_hidden()
8122 .child(completion),
8123 )
8124 .when_some(accept_keystroke, |el, accept_keystroke| {
8125 if !accept_keystroke.modifiers.modified() {
8126 return el;
8127 }
8128
8129 el.child(
8130 h_flex()
8131 .h_full()
8132 .border_l_1()
8133 .rounded_r_lg()
8134 .border_color(cx.theme().colors().border)
8135 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8136 .gap_1()
8137 .py_1()
8138 .px_2()
8139 .child(
8140 h_flex()
8141 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8142 .when(is_platform_style_mac, |parent| parent.gap_1())
8143 .child(h_flex().children(ui::render_modifiers(
8144 &accept_keystroke.modifiers,
8145 PlatformStyle::platform(),
8146 Some(if !has_completion {
8147 Color::Muted
8148 } else {
8149 Color::Default
8150 }),
8151 None,
8152 false,
8153 ))),
8154 )
8155 .child(Label::new("Preview").into_any_element())
8156 .opacity(if has_completion { 1.0 } else { 0.4 }),
8157 )
8158 })
8159 .into_any(),
8160 )
8161 }
8162
8163 fn render_edit_prediction_cursor_popover_preview(
8164 &self,
8165 completion: &InlineCompletionState,
8166 cursor_point: Point,
8167 style: &EditorStyle,
8168 cx: &mut Context<Editor>,
8169 ) -> Option<Div> {
8170 use text::ToPoint as _;
8171
8172 fn render_relative_row_jump(
8173 prefix: impl Into<String>,
8174 current_row: u32,
8175 target_row: u32,
8176 ) -> Div {
8177 let (row_diff, arrow) = if target_row < current_row {
8178 (current_row - target_row, IconName::ArrowUp)
8179 } else {
8180 (target_row - current_row, IconName::ArrowDown)
8181 };
8182
8183 h_flex()
8184 .child(
8185 Label::new(format!("{}{}", prefix.into(), row_diff))
8186 .color(Color::Muted)
8187 .size(LabelSize::Small),
8188 )
8189 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8190 }
8191
8192 match &completion.completion {
8193 InlineCompletion::Move {
8194 target, snapshot, ..
8195 } => Some(
8196 h_flex()
8197 .px_2()
8198 .gap_2()
8199 .flex_1()
8200 .child(
8201 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8202 Icon::new(IconName::ZedPredictDown)
8203 } else {
8204 Icon::new(IconName::ZedPredictUp)
8205 },
8206 )
8207 .child(Label::new("Jump to Edit")),
8208 ),
8209
8210 InlineCompletion::Edit {
8211 edits,
8212 edit_preview,
8213 snapshot,
8214 display_mode: _,
8215 } => {
8216 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8217
8218 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8219 &snapshot,
8220 &edits,
8221 edit_preview.as_ref()?,
8222 true,
8223 cx,
8224 )
8225 .first_line_preview();
8226
8227 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8228 .with_default_highlights(&style.text, highlighted_edits.highlights);
8229
8230 let preview = h_flex()
8231 .gap_1()
8232 .min_w_16()
8233 .child(styled_text)
8234 .when(has_more_lines, |parent| parent.child("…"));
8235
8236 let left = if first_edit_row != cursor_point.row {
8237 render_relative_row_jump("", cursor_point.row, first_edit_row)
8238 .into_any_element()
8239 } else {
8240 Icon::new(IconName::ZedPredict).into_any_element()
8241 };
8242
8243 Some(
8244 h_flex()
8245 .h_full()
8246 .flex_1()
8247 .gap_2()
8248 .pr_1()
8249 .overflow_x_hidden()
8250 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8251 .child(left)
8252 .child(preview),
8253 )
8254 }
8255 }
8256 }
8257
8258 fn render_context_menu(
8259 &self,
8260 style: &EditorStyle,
8261 max_height_in_lines: u32,
8262 window: &mut Window,
8263 cx: &mut Context<Editor>,
8264 ) -> Option<AnyElement> {
8265 let menu = self.context_menu.borrow();
8266 let menu = menu.as_ref()?;
8267 if !menu.visible() {
8268 return None;
8269 };
8270 Some(menu.render(style, max_height_in_lines, window, cx))
8271 }
8272
8273 fn render_context_menu_aside(
8274 &mut self,
8275 max_size: Size<Pixels>,
8276 window: &mut Window,
8277 cx: &mut Context<Editor>,
8278 ) -> Option<AnyElement> {
8279 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8280 if menu.visible() {
8281 menu.render_aside(self, max_size, window, cx)
8282 } else {
8283 None
8284 }
8285 })
8286 }
8287
8288 fn hide_context_menu(
8289 &mut self,
8290 window: &mut Window,
8291 cx: &mut Context<Self>,
8292 ) -> Option<CodeContextMenu> {
8293 cx.notify();
8294 self.completion_tasks.clear();
8295 let context_menu = self.context_menu.borrow_mut().take();
8296 self.stale_inline_completion_in_menu.take();
8297 self.update_visible_inline_completion(window, cx);
8298 context_menu
8299 }
8300
8301 fn show_snippet_choices(
8302 &mut self,
8303 choices: &Vec<String>,
8304 selection: Range<Anchor>,
8305 cx: &mut Context<Self>,
8306 ) {
8307 if selection.start.buffer_id.is_none() {
8308 return;
8309 }
8310 let buffer_id = selection.start.buffer_id.unwrap();
8311 let buffer = self.buffer().read(cx).buffer(buffer_id);
8312 let id = post_inc(&mut self.next_completion_id);
8313 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8314
8315 if let Some(buffer) = buffer {
8316 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8317 CompletionsMenu::new_snippet_choices(
8318 id,
8319 true,
8320 choices,
8321 selection,
8322 buffer,
8323 snippet_sort_order,
8324 ),
8325 ));
8326 }
8327 }
8328
8329 pub fn insert_snippet(
8330 &mut self,
8331 insertion_ranges: &[Range<usize>],
8332 snippet: Snippet,
8333 window: &mut Window,
8334 cx: &mut Context<Self>,
8335 ) -> Result<()> {
8336 struct Tabstop<T> {
8337 is_end_tabstop: bool,
8338 ranges: Vec<Range<T>>,
8339 choices: Option<Vec<String>>,
8340 }
8341
8342 let tabstops = self.buffer.update(cx, |buffer, cx| {
8343 let snippet_text: Arc<str> = snippet.text.clone().into();
8344 let edits = insertion_ranges
8345 .iter()
8346 .cloned()
8347 .map(|range| (range, snippet_text.clone()));
8348 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8349
8350 let snapshot = &*buffer.read(cx);
8351 let snippet = &snippet;
8352 snippet
8353 .tabstops
8354 .iter()
8355 .map(|tabstop| {
8356 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8357 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8358 });
8359 let mut tabstop_ranges = tabstop
8360 .ranges
8361 .iter()
8362 .flat_map(|tabstop_range| {
8363 let mut delta = 0_isize;
8364 insertion_ranges.iter().map(move |insertion_range| {
8365 let insertion_start = insertion_range.start as isize + delta;
8366 delta +=
8367 snippet.text.len() as isize - insertion_range.len() as isize;
8368
8369 let start = ((insertion_start + tabstop_range.start) as usize)
8370 .min(snapshot.len());
8371 let end = ((insertion_start + tabstop_range.end) as usize)
8372 .min(snapshot.len());
8373 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8374 })
8375 })
8376 .collect::<Vec<_>>();
8377 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8378
8379 Tabstop {
8380 is_end_tabstop,
8381 ranges: tabstop_ranges,
8382 choices: tabstop.choices.clone(),
8383 }
8384 })
8385 .collect::<Vec<_>>()
8386 });
8387 if let Some(tabstop) = tabstops.first() {
8388 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8389 s.select_ranges(tabstop.ranges.iter().cloned());
8390 });
8391
8392 if let Some(choices) = &tabstop.choices {
8393 if let Some(selection) = tabstop.ranges.first() {
8394 self.show_snippet_choices(choices, selection.clone(), cx)
8395 }
8396 }
8397
8398 // If we're already at the last tabstop and it's at the end of the snippet,
8399 // we're done, we don't need to keep the state around.
8400 if !tabstop.is_end_tabstop {
8401 let choices = tabstops
8402 .iter()
8403 .map(|tabstop| tabstop.choices.clone())
8404 .collect();
8405
8406 let ranges = tabstops
8407 .into_iter()
8408 .map(|tabstop| tabstop.ranges)
8409 .collect::<Vec<_>>();
8410
8411 self.snippet_stack.push(SnippetState {
8412 active_index: 0,
8413 ranges,
8414 choices,
8415 });
8416 }
8417
8418 // Check whether the just-entered snippet ends with an auto-closable bracket.
8419 if self.autoclose_regions.is_empty() {
8420 let snapshot = self.buffer.read(cx).snapshot(cx);
8421 for selection in &mut self.selections.all::<Point>(cx) {
8422 let selection_head = selection.head();
8423 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8424 continue;
8425 };
8426
8427 let mut bracket_pair = None;
8428 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8429 let prev_chars = snapshot
8430 .reversed_chars_at(selection_head)
8431 .collect::<String>();
8432 for (pair, enabled) in scope.brackets() {
8433 if enabled
8434 && pair.close
8435 && prev_chars.starts_with(pair.start.as_str())
8436 && next_chars.starts_with(pair.end.as_str())
8437 {
8438 bracket_pair = Some(pair.clone());
8439 break;
8440 }
8441 }
8442 if let Some(pair) = bracket_pair {
8443 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8444 let autoclose_enabled =
8445 self.use_autoclose && snapshot_settings.use_autoclose;
8446 if autoclose_enabled {
8447 let start = snapshot.anchor_after(selection_head);
8448 let end = snapshot.anchor_after(selection_head);
8449 self.autoclose_regions.push(AutocloseRegion {
8450 selection_id: selection.id,
8451 range: start..end,
8452 pair,
8453 });
8454 }
8455 }
8456 }
8457 }
8458 }
8459 Ok(())
8460 }
8461
8462 pub fn move_to_next_snippet_tabstop(
8463 &mut self,
8464 window: &mut Window,
8465 cx: &mut Context<Self>,
8466 ) -> bool {
8467 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8468 }
8469
8470 pub fn move_to_prev_snippet_tabstop(
8471 &mut self,
8472 window: &mut Window,
8473 cx: &mut Context<Self>,
8474 ) -> bool {
8475 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8476 }
8477
8478 pub fn move_to_snippet_tabstop(
8479 &mut self,
8480 bias: Bias,
8481 window: &mut Window,
8482 cx: &mut Context<Self>,
8483 ) -> bool {
8484 if let Some(mut snippet) = self.snippet_stack.pop() {
8485 match bias {
8486 Bias::Left => {
8487 if snippet.active_index > 0 {
8488 snippet.active_index -= 1;
8489 } else {
8490 self.snippet_stack.push(snippet);
8491 return false;
8492 }
8493 }
8494 Bias::Right => {
8495 if snippet.active_index + 1 < snippet.ranges.len() {
8496 snippet.active_index += 1;
8497 } else {
8498 self.snippet_stack.push(snippet);
8499 return false;
8500 }
8501 }
8502 }
8503 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8504 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8505 s.select_anchor_ranges(current_ranges.iter().cloned())
8506 });
8507
8508 if let Some(choices) = &snippet.choices[snippet.active_index] {
8509 if let Some(selection) = current_ranges.first() {
8510 self.show_snippet_choices(&choices, selection.clone(), cx);
8511 }
8512 }
8513
8514 // If snippet state is not at the last tabstop, push it back on the stack
8515 if snippet.active_index + 1 < snippet.ranges.len() {
8516 self.snippet_stack.push(snippet);
8517 }
8518 return true;
8519 }
8520 }
8521
8522 false
8523 }
8524
8525 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8526 self.transact(window, cx, |this, window, cx| {
8527 this.select_all(&SelectAll, window, cx);
8528 this.insert("", window, cx);
8529 });
8530 }
8531
8532 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8533 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8534 self.transact(window, cx, |this, window, cx| {
8535 this.select_autoclose_pair(window, cx);
8536 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8537 if !this.linked_edit_ranges.is_empty() {
8538 let selections = this.selections.all::<MultiBufferPoint>(cx);
8539 let snapshot = this.buffer.read(cx).snapshot(cx);
8540
8541 for selection in selections.iter() {
8542 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8543 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8544 if selection_start.buffer_id != selection_end.buffer_id {
8545 continue;
8546 }
8547 if let Some(ranges) =
8548 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8549 {
8550 for (buffer, entries) in ranges {
8551 linked_ranges.entry(buffer).or_default().extend(entries);
8552 }
8553 }
8554 }
8555 }
8556
8557 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8558 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8559 for selection in &mut selections {
8560 if selection.is_empty() {
8561 let old_head = selection.head();
8562 let mut new_head =
8563 movement::left(&display_map, old_head.to_display_point(&display_map))
8564 .to_point(&display_map);
8565 if let Some((buffer, line_buffer_range)) = display_map
8566 .buffer_snapshot
8567 .buffer_line_for_row(MultiBufferRow(old_head.row))
8568 {
8569 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8570 let indent_len = match indent_size.kind {
8571 IndentKind::Space => {
8572 buffer.settings_at(line_buffer_range.start, cx).tab_size
8573 }
8574 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8575 };
8576 if old_head.column <= indent_size.len && old_head.column > 0 {
8577 let indent_len = indent_len.get();
8578 new_head = cmp::min(
8579 new_head,
8580 MultiBufferPoint::new(
8581 old_head.row,
8582 ((old_head.column - 1) / indent_len) * indent_len,
8583 ),
8584 );
8585 }
8586 }
8587
8588 selection.set_head(new_head, SelectionGoal::None);
8589 }
8590 }
8591
8592 this.signature_help_state.set_backspace_pressed(true);
8593 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8594 s.select(selections)
8595 });
8596 this.insert("", window, cx);
8597 let empty_str: Arc<str> = Arc::from("");
8598 for (buffer, edits) in linked_ranges {
8599 let snapshot = buffer.read(cx).snapshot();
8600 use text::ToPoint as TP;
8601
8602 let edits = edits
8603 .into_iter()
8604 .map(|range| {
8605 let end_point = TP::to_point(&range.end, &snapshot);
8606 let mut start_point = TP::to_point(&range.start, &snapshot);
8607
8608 if end_point == start_point {
8609 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8610 .saturating_sub(1);
8611 start_point =
8612 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8613 };
8614
8615 (start_point..end_point, empty_str.clone())
8616 })
8617 .sorted_by_key(|(range, _)| range.start)
8618 .collect::<Vec<_>>();
8619 buffer.update(cx, |this, cx| {
8620 this.edit(edits, None, cx);
8621 })
8622 }
8623 this.refresh_inline_completion(true, false, window, cx);
8624 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8625 });
8626 }
8627
8628 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8629 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8630 self.transact(window, cx, |this, window, cx| {
8631 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8632 s.move_with(|map, selection| {
8633 if selection.is_empty() {
8634 let cursor = movement::right(map, selection.head());
8635 selection.end = cursor;
8636 selection.reversed = true;
8637 selection.goal = SelectionGoal::None;
8638 }
8639 })
8640 });
8641 this.insert("", window, cx);
8642 this.refresh_inline_completion(true, false, window, cx);
8643 });
8644 }
8645
8646 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8647 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8648 if self.move_to_prev_snippet_tabstop(window, cx) {
8649 return;
8650 }
8651 self.outdent(&Outdent, window, cx);
8652 }
8653
8654 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8655 if self.move_to_next_snippet_tabstop(window, cx) {
8656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8657 return;
8658 }
8659 if self.read_only(cx) {
8660 return;
8661 }
8662 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8663 let mut selections = self.selections.all_adjusted(cx);
8664 let buffer = self.buffer.read(cx);
8665 let snapshot = buffer.snapshot(cx);
8666 let rows_iter = selections.iter().map(|s| s.head().row);
8667 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8668
8669 let has_some_cursor_in_whitespace = selections
8670 .iter()
8671 .filter(|selection| selection.is_empty())
8672 .any(|selection| {
8673 let cursor = selection.head();
8674 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8675 cursor.column < current_indent.len
8676 });
8677
8678 let mut edits = Vec::new();
8679 let mut prev_edited_row = 0;
8680 let mut row_delta = 0;
8681 for selection in &mut selections {
8682 if selection.start.row != prev_edited_row {
8683 row_delta = 0;
8684 }
8685 prev_edited_row = selection.end.row;
8686
8687 // If the selection is non-empty, then increase the indentation of the selected lines.
8688 if !selection.is_empty() {
8689 row_delta =
8690 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8691 continue;
8692 }
8693
8694 // If the selection is empty and the cursor is in the leading whitespace before the
8695 // suggested indentation, then auto-indent the line.
8696 let cursor = selection.head();
8697 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8698 if let Some(suggested_indent) =
8699 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8700 {
8701 // If there exist any empty selection in the leading whitespace, then skip
8702 // indent for selections at the boundary.
8703 if has_some_cursor_in_whitespace
8704 && cursor.column == current_indent.len
8705 && current_indent.len == suggested_indent.len
8706 {
8707 continue;
8708 }
8709
8710 if cursor.column < suggested_indent.len
8711 && cursor.column <= current_indent.len
8712 && current_indent.len <= suggested_indent.len
8713 {
8714 selection.start = Point::new(cursor.row, suggested_indent.len);
8715 selection.end = selection.start;
8716 if row_delta == 0 {
8717 edits.extend(Buffer::edit_for_indent_size_adjustment(
8718 cursor.row,
8719 current_indent,
8720 suggested_indent,
8721 ));
8722 row_delta = suggested_indent.len - current_indent.len;
8723 }
8724 continue;
8725 }
8726 }
8727
8728 // Otherwise, insert a hard or soft tab.
8729 let settings = buffer.language_settings_at(cursor, cx);
8730 let tab_size = if settings.hard_tabs {
8731 IndentSize::tab()
8732 } else {
8733 let tab_size = settings.tab_size.get();
8734 let indent_remainder = snapshot
8735 .text_for_range(Point::new(cursor.row, 0)..cursor)
8736 .flat_map(str::chars)
8737 .fold(row_delta % tab_size, |counter: u32, c| {
8738 if c == '\t' {
8739 0
8740 } else {
8741 (counter + 1) % tab_size
8742 }
8743 });
8744
8745 let chars_to_next_tab_stop = tab_size - indent_remainder;
8746 IndentSize::spaces(chars_to_next_tab_stop)
8747 };
8748 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8749 selection.end = selection.start;
8750 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8751 row_delta += tab_size.len;
8752 }
8753
8754 self.transact(window, cx, |this, window, cx| {
8755 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8756 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8757 s.select(selections)
8758 });
8759 this.refresh_inline_completion(true, false, window, cx);
8760 });
8761 }
8762
8763 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8764 if self.read_only(cx) {
8765 return;
8766 }
8767 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8768 let mut selections = self.selections.all::<Point>(cx);
8769 let mut prev_edited_row = 0;
8770 let mut row_delta = 0;
8771 let mut edits = Vec::new();
8772 let buffer = self.buffer.read(cx);
8773 let snapshot = buffer.snapshot(cx);
8774 for selection in &mut selections {
8775 if selection.start.row != prev_edited_row {
8776 row_delta = 0;
8777 }
8778 prev_edited_row = selection.end.row;
8779
8780 row_delta =
8781 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8782 }
8783
8784 self.transact(window, cx, |this, window, cx| {
8785 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8786 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8787 s.select(selections)
8788 });
8789 });
8790 }
8791
8792 fn indent_selection(
8793 buffer: &MultiBuffer,
8794 snapshot: &MultiBufferSnapshot,
8795 selection: &mut Selection<Point>,
8796 edits: &mut Vec<(Range<Point>, String)>,
8797 delta_for_start_row: u32,
8798 cx: &App,
8799 ) -> u32 {
8800 let settings = buffer.language_settings_at(selection.start, cx);
8801 let tab_size = settings.tab_size.get();
8802 let indent_kind = if settings.hard_tabs {
8803 IndentKind::Tab
8804 } else {
8805 IndentKind::Space
8806 };
8807 let mut start_row = selection.start.row;
8808 let mut end_row = selection.end.row + 1;
8809
8810 // If a selection ends at the beginning of a line, don't indent
8811 // that last line.
8812 if selection.end.column == 0 && selection.end.row > selection.start.row {
8813 end_row -= 1;
8814 }
8815
8816 // Avoid re-indenting a row that has already been indented by a
8817 // previous selection, but still update this selection's column
8818 // to reflect that indentation.
8819 if delta_for_start_row > 0 {
8820 start_row += 1;
8821 selection.start.column += delta_for_start_row;
8822 if selection.end.row == selection.start.row {
8823 selection.end.column += delta_for_start_row;
8824 }
8825 }
8826
8827 let mut delta_for_end_row = 0;
8828 let has_multiple_rows = start_row + 1 != end_row;
8829 for row in start_row..end_row {
8830 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8831 let indent_delta = match (current_indent.kind, indent_kind) {
8832 (IndentKind::Space, IndentKind::Space) => {
8833 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8834 IndentSize::spaces(columns_to_next_tab_stop)
8835 }
8836 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8837 (_, IndentKind::Tab) => IndentSize::tab(),
8838 };
8839
8840 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8841 0
8842 } else {
8843 selection.start.column
8844 };
8845 let row_start = Point::new(row, start);
8846 edits.push((
8847 row_start..row_start,
8848 indent_delta.chars().collect::<String>(),
8849 ));
8850
8851 // Update this selection's endpoints to reflect the indentation.
8852 if row == selection.start.row {
8853 selection.start.column += indent_delta.len;
8854 }
8855 if row == selection.end.row {
8856 selection.end.column += indent_delta.len;
8857 delta_for_end_row = indent_delta.len;
8858 }
8859 }
8860
8861 if selection.start.row == selection.end.row {
8862 delta_for_start_row + delta_for_end_row
8863 } else {
8864 delta_for_end_row
8865 }
8866 }
8867
8868 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8869 if self.read_only(cx) {
8870 return;
8871 }
8872 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8873 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8874 let selections = self.selections.all::<Point>(cx);
8875 let mut deletion_ranges = Vec::new();
8876 let mut last_outdent = None;
8877 {
8878 let buffer = self.buffer.read(cx);
8879 let snapshot = buffer.snapshot(cx);
8880 for selection in &selections {
8881 let settings = buffer.language_settings_at(selection.start, cx);
8882 let tab_size = settings.tab_size.get();
8883 let mut rows = selection.spanned_rows(false, &display_map);
8884
8885 // Avoid re-outdenting a row that has already been outdented by a
8886 // previous selection.
8887 if let Some(last_row) = last_outdent {
8888 if last_row == rows.start {
8889 rows.start = rows.start.next_row();
8890 }
8891 }
8892 let has_multiple_rows = rows.len() > 1;
8893 for row in rows.iter_rows() {
8894 let indent_size = snapshot.indent_size_for_line(row);
8895 if indent_size.len > 0 {
8896 let deletion_len = match indent_size.kind {
8897 IndentKind::Space => {
8898 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8899 if columns_to_prev_tab_stop == 0 {
8900 tab_size
8901 } else {
8902 columns_to_prev_tab_stop
8903 }
8904 }
8905 IndentKind::Tab => 1,
8906 };
8907 let start = if has_multiple_rows
8908 || deletion_len > selection.start.column
8909 || indent_size.len < selection.start.column
8910 {
8911 0
8912 } else {
8913 selection.start.column - deletion_len
8914 };
8915 deletion_ranges.push(
8916 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8917 );
8918 last_outdent = Some(row);
8919 }
8920 }
8921 }
8922 }
8923
8924 self.transact(window, cx, |this, window, cx| {
8925 this.buffer.update(cx, |buffer, cx| {
8926 let empty_str: Arc<str> = Arc::default();
8927 buffer.edit(
8928 deletion_ranges
8929 .into_iter()
8930 .map(|range| (range, empty_str.clone())),
8931 None,
8932 cx,
8933 );
8934 });
8935 let selections = this.selections.all::<usize>(cx);
8936 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8937 s.select(selections)
8938 });
8939 });
8940 }
8941
8942 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8943 if self.read_only(cx) {
8944 return;
8945 }
8946 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8947 let selections = self
8948 .selections
8949 .all::<usize>(cx)
8950 .into_iter()
8951 .map(|s| s.range());
8952
8953 self.transact(window, cx, |this, window, cx| {
8954 this.buffer.update(cx, |buffer, cx| {
8955 buffer.autoindent_ranges(selections, cx);
8956 });
8957 let selections = this.selections.all::<usize>(cx);
8958 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8959 s.select(selections)
8960 });
8961 });
8962 }
8963
8964 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8965 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8967 let selections = self.selections.all::<Point>(cx);
8968
8969 let mut new_cursors = Vec::new();
8970 let mut edit_ranges = Vec::new();
8971 let mut selections = selections.iter().peekable();
8972 while let Some(selection) = selections.next() {
8973 let mut rows = selection.spanned_rows(false, &display_map);
8974 let goal_display_column = selection.head().to_display_point(&display_map).column();
8975
8976 // Accumulate contiguous regions of rows that we want to delete.
8977 while let Some(next_selection) = selections.peek() {
8978 let next_rows = next_selection.spanned_rows(false, &display_map);
8979 if next_rows.start <= rows.end {
8980 rows.end = next_rows.end;
8981 selections.next().unwrap();
8982 } else {
8983 break;
8984 }
8985 }
8986
8987 let buffer = &display_map.buffer_snapshot;
8988 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8989 let edit_end;
8990 let cursor_buffer_row;
8991 if buffer.max_point().row >= rows.end.0 {
8992 // If there's a line after the range, delete the \n from the end of the row range
8993 // and position the cursor on the next line.
8994 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
8995 cursor_buffer_row = rows.end;
8996 } else {
8997 // If there isn't a line after the range, delete the \n from the line before the
8998 // start of the row range and position the cursor there.
8999 edit_start = edit_start.saturating_sub(1);
9000 edit_end = buffer.len();
9001 cursor_buffer_row = rows.start.previous_row();
9002 }
9003
9004 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9005 *cursor.column_mut() =
9006 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9007
9008 new_cursors.push((
9009 selection.id,
9010 buffer.anchor_after(cursor.to_point(&display_map)),
9011 ));
9012 edit_ranges.push(edit_start..edit_end);
9013 }
9014
9015 self.transact(window, cx, |this, window, cx| {
9016 let buffer = this.buffer.update(cx, |buffer, cx| {
9017 let empty_str: Arc<str> = Arc::default();
9018 buffer.edit(
9019 edit_ranges
9020 .into_iter()
9021 .map(|range| (range, empty_str.clone())),
9022 None,
9023 cx,
9024 );
9025 buffer.snapshot(cx)
9026 });
9027 let new_selections = new_cursors
9028 .into_iter()
9029 .map(|(id, cursor)| {
9030 let cursor = cursor.to_point(&buffer);
9031 Selection {
9032 id,
9033 start: cursor,
9034 end: cursor,
9035 reversed: false,
9036 goal: SelectionGoal::None,
9037 }
9038 })
9039 .collect();
9040
9041 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9042 s.select(new_selections);
9043 });
9044 });
9045 }
9046
9047 pub fn join_lines_impl(
9048 &mut self,
9049 insert_whitespace: bool,
9050 window: &mut Window,
9051 cx: &mut Context<Self>,
9052 ) {
9053 if self.read_only(cx) {
9054 return;
9055 }
9056 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9057 for selection in self.selections.all::<Point>(cx) {
9058 let start = MultiBufferRow(selection.start.row);
9059 // Treat single line selections as if they include the next line. Otherwise this action
9060 // would do nothing for single line selections individual cursors.
9061 let end = if selection.start.row == selection.end.row {
9062 MultiBufferRow(selection.start.row + 1)
9063 } else {
9064 MultiBufferRow(selection.end.row)
9065 };
9066
9067 if let Some(last_row_range) = row_ranges.last_mut() {
9068 if start <= last_row_range.end {
9069 last_row_range.end = end;
9070 continue;
9071 }
9072 }
9073 row_ranges.push(start..end);
9074 }
9075
9076 let snapshot = self.buffer.read(cx).snapshot(cx);
9077 let mut cursor_positions = Vec::new();
9078 for row_range in &row_ranges {
9079 let anchor = snapshot.anchor_before(Point::new(
9080 row_range.end.previous_row().0,
9081 snapshot.line_len(row_range.end.previous_row()),
9082 ));
9083 cursor_positions.push(anchor..anchor);
9084 }
9085
9086 self.transact(window, cx, |this, window, cx| {
9087 for row_range in row_ranges.into_iter().rev() {
9088 for row in row_range.iter_rows().rev() {
9089 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9090 let next_line_row = row.next_row();
9091 let indent = snapshot.indent_size_for_line(next_line_row);
9092 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9093
9094 let replace =
9095 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9096 " "
9097 } else {
9098 ""
9099 };
9100
9101 this.buffer.update(cx, |buffer, cx| {
9102 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9103 });
9104 }
9105 }
9106
9107 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9108 s.select_anchor_ranges(cursor_positions)
9109 });
9110 });
9111 }
9112
9113 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9114 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9115 self.join_lines_impl(true, window, cx);
9116 }
9117
9118 pub fn sort_lines_case_sensitive(
9119 &mut self,
9120 _: &SortLinesCaseSensitive,
9121 window: &mut Window,
9122 cx: &mut Context<Self>,
9123 ) {
9124 self.manipulate_lines(window, cx, |lines| lines.sort())
9125 }
9126
9127 pub fn sort_lines_case_insensitive(
9128 &mut self,
9129 _: &SortLinesCaseInsensitive,
9130 window: &mut Window,
9131 cx: &mut Context<Self>,
9132 ) {
9133 self.manipulate_lines(window, cx, |lines| {
9134 lines.sort_by_key(|line| line.to_lowercase())
9135 })
9136 }
9137
9138 pub fn unique_lines_case_insensitive(
9139 &mut self,
9140 _: &UniqueLinesCaseInsensitive,
9141 window: &mut Window,
9142 cx: &mut Context<Self>,
9143 ) {
9144 self.manipulate_lines(window, cx, |lines| {
9145 let mut seen = HashSet::default();
9146 lines.retain(|line| seen.insert(line.to_lowercase()));
9147 })
9148 }
9149
9150 pub fn unique_lines_case_sensitive(
9151 &mut self,
9152 _: &UniqueLinesCaseSensitive,
9153 window: &mut Window,
9154 cx: &mut Context<Self>,
9155 ) {
9156 self.manipulate_lines(window, cx, |lines| {
9157 let mut seen = HashSet::default();
9158 lines.retain(|line| seen.insert(*line));
9159 })
9160 }
9161
9162 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9163 let Some(project) = self.project.clone() else {
9164 return;
9165 };
9166 self.reload(project, window, cx)
9167 .detach_and_notify_err(window, cx);
9168 }
9169
9170 pub fn restore_file(
9171 &mut self,
9172 _: &::git::RestoreFile,
9173 window: &mut Window,
9174 cx: &mut Context<Self>,
9175 ) {
9176 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9177 let mut buffer_ids = HashSet::default();
9178 let snapshot = self.buffer().read(cx).snapshot(cx);
9179 for selection in self.selections.all::<usize>(cx) {
9180 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9181 }
9182
9183 let buffer = self.buffer().read(cx);
9184 let ranges = buffer_ids
9185 .into_iter()
9186 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9187 .collect::<Vec<_>>();
9188
9189 self.restore_hunks_in_ranges(ranges, window, cx);
9190 }
9191
9192 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9193 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9194 let selections = self
9195 .selections
9196 .all(cx)
9197 .into_iter()
9198 .map(|s| s.range())
9199 .collect();
9200 self.restore_hunks_in_ranges(selections, window, cx);
9201 }
9202
9203 pub fn restore_hunks_in_ranges(
9204 &mut self,
9205 ranges: Vec<Range<Point>>,
9206 window: &mut Window,
9207 cx: &mut Context<Editor>,
9208 ) {
9209 let mut revert_changes = HashMap::default();
9210 let chunk_by = self
9211 .snapshot(window, cx)
9212 .hunks_for_ranges(ranges)
9213 .into_iter()
9214 .chunk_by(|hunk| hunk.buffer_id);
9215 for (buffer_id, hunks) in &chunk_by {
9216 let hunks = hunks.collect::<Vec<_>>();
9217 for hunk in &hunks {
9218 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9219 }
9220 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9221 }
9222 drop(chunk_by);
9223 if !revert_changes.is_empty() {
9224 self.transact(window, cx, |editor, window, cx| {
9225 editor.restore(revert_changes, window, cx);
9226 });
9227 }
9228 }
9229
9230 pub fn open_active_item_in_terminal(
9231 &mut self,
9232 _: &OpenInTerminal,
9233 window: &mut Window,
9234 cx: &mut Context<Self>,
9235 ) {
9236 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9237 let project_path = buffer.read(cx).project_path(cx)?;
9238 let project = self.project.as_ref()?.read(cx);
9239 let entry = project.entry_for_path(&project_path, cx)?;
9240 let parent = match &entry.canonical_path {
9241 Some(canonical_path) => canonical_path.to_path_buf(),
9242 None => project.absolute_path(&project_path, cx)?,
9243 }
9244 .parent()?
9245 .to_path_buf();
9246 Some(parent)
9247 }) {
9248 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9249 }
9250 }
9251
9252 fn set_breakpoint_context_menu(
9253 &mut self,
9254 display_row: DisplayRow,
9255 position: Option<Anchor>,
9256 clicked_point: gpui::Point<Pixels>,
9257 window: &mut Window,
9258 cx: &mut Context<Self>,
9259 ) {
9260 if !cx.has_flag::<DebuggerFeatureFlag>() {
9261 return;
9262 }
9263 let source = self
9264 .buffer
9265 .read(cx)
9266 .snapshot(cx)
9267 .anchor_before(Point::new(display_row.0, 0u32));
9268
9269 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9270
9271 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9272 self,
9273 source,
9274 clicked_point,
9275 context_menu,
9276 window,
9277 cx,
9278 );
9279 }
9280
9281 fn add_edit_breakpoint_block(
9282 &mut self,
9283 anchor: Anchor,
9284 breakpoint: &Breakpoint,
9285 edit_action: BreakpointPromptEditAction,
9286 window: &mut Window,
9287 cx: &mut Context<Self>,
9288 ) {
9289 let weak_editor = cx.weak_entity();
9290 let bp_prompt = cx.new(|cx| {
9291 BreakpointPromptEditor::new(
9292 weak_editor,
9293 anchor,
9294 breakpoint.clone(),
9295 edit_action,
9296 window,
9297 cx,
9298 )
9299 });
9300
9301 let height = bp_prompt.update(cx, |this, cx| {
9302 this.prompt
9303 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9304 });
9305 let cloned_prompt = bp_prompt.clone();
9306 let blocks = vec![BlockProperties {
9307 style: BlockStyle::Sticky,
9308 placement: BlockPlacement::Above(anchor),
9309 height: Some(height),
9310 render: Arc::new(move |cx| {
9311 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9312 cloned_prompt.clone().into_any_element()
9313 }),
9314 priority: 0,
9315 }];
9316
9317 let focus_handle = bp_prompt.focus_handle(cx);
9318 window.focus(&focus_handle);
9319
9320 let block_ids = self.insert_blocks(blocks, None, cx);
9321 bp_prompt.update(cx, |prompt, _| {
9322 prompt.add_block_ids(block_ids);
9323 });
9324 }
9325
9326 pub(crate) fn breakpoint_at_row(
9327 &self,
9328 row: u32,
9329 window: &mut Window,
9330 cx: &mut Context<Self>,
9331 ) -> Option<(Anchor, Breakpoint)> {
9332 let snapshot = self.snapshot(window, cx);
9333 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9334
9335 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9336 }
9337
9338 pub(crate) fn breakpoint_at_anchor(
9339 &self,
9340 breakpoint_position: Anchor,
9341 snapshot: &EditorSnapshot,
9342 cx: &mut Context<Self>,
9343 ) -> Option<(Anchor, Breakpoint)> {
9344 let project = self.project.clone()?;
9345
9346 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9347 snapshot
9348 .buffer_snapshot
9349 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9350 })?;
9351
9352 let enclosing_excerpt = breakpoint_position.excerpt_id;
9353 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9354 let buffer_snapshot = buffer.read(cx).snapshot();
9355
9356 let row = buffer_snapshot
9357 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9358 .row;
9359
9360 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9361 let anchor_end = snapshot
9362 .buffer_snapshot
9363 .anchor_after(Point::new(row, line_len));
9364
9365 let bp = self
9366 .breakpoint_store
9367 .as_ref()?
9368 .read_with(cx, |breakpoint_store, cx| {
9369 breakpoint_store
9370 .breakpoints(
9371 &buffer,
9372 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9373 &buffer_snapshot,
9374 cx,
9375 )
9376 .next()
9377 .and_then(|(anchor, bp)| {
9378 let breakpoint_row = buffer_snapshot
9379 .summary_for_anchor::<text::PointUtf16>(anchor)
9380 .row;
9381
9382 if breakpoint_row == row {
9383 snapshot
9384 .buffer_snapshot
9385 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9386 .map(|anchor| (anchor, bp.clone()))
9387 } else {
9388 None
9389 }
9390 })
9391 });
9392 bp
9393 }
9394
9395 pub fn edit_log_breakpoint(
9396 &mut self,
9397 _: &EditLogBreakpoint,
9398 window: &mut Window,
9399 cx: &mut Context<Self>,
9400 ) {
9401 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9402 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9403 message: None,
9404 state: BreakpointState::Enabled,
9405 condition: None,
9406 hit_condition: None,
9407 });
9408
9409 self.add_edit_breakpoint_block(
9410 anchor,
9411 &breakpoint,
9412 BreakpointPromptEditAction::Log,
9413 window,
9414 cx,
9415 );
9416 }
9417 }
9418
9419 fn breakpoints_at_cursors(
9420 &self,
9421 window: &mut Window,
9422 cx: &mut Context<Self>,
9423 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9424 let snapshot = self.snapshot(window, cx);
9425 let cursors = self
9426 .selections
9427 .disjoint_anchors()
9428 .into_iter()
9429 .map(|selection| {
9430 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9431
9432 let breakpoint_position = self
9433 .breakpoint_at_row(cursor_position.row, window, cx)
9434 .map(|bp| bp.0)
9435 .unwrap_or_else(|| {
9436 snapshot
9437 .display_snapshot
9438 .buffer_snapshot
9439 .anchor_after(Point::new(cursor_position.row, 0))
9440 });
9441
9442 let breakpoint = self
9443 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9444 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9445
9446 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9447 })
9448 // 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.
9449 .collect::<HashMap<Anchor, _>>();
9450
9451 cursors.into_iter().collect()
9452 }
9453
9454 pub fn enable_breakpoint(
9455 &mut self,
9456 _: &crate::actions::EnableBreakpoint,
9457 window: &mut Window,
9458 cx: &mut Context<Self>,
9459 ) {
9460 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9461 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9462 continue;
9463 };
9464 self.edit_breakpoint_at_anchor(
9465 anchor,
9466 breakpoint,
9467 BreakpointEditAction::InvertState,
9468 cx,
9469 );
9470 }
9471 }
9472
9473 pub fn disable_breakpoint(
9474 &mut self,
9475 _: &crate::actions::DisableBreakpoint,
9476 window: &mut Window,
9477 cx: &mut Context<Self>,
9478 ) {
9479 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9480 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9481 continue;
9482 };
9483 self.edit_breakpoint_at_anchor(
9484 anchor,
9485 breakpoint,
9486 BreakpointEditAction::InvertState,
9487 cx,
9488 );
9489 }
9490 }
9491
9492 pub fn toggle_breakpoint(
9493 &mut self,
9494 _: &crate::actions::ToggleBreakpoint,
9495 window: &mut Window,
9496 cx: &mut Context<Self>,
9497 ) {
9498 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9499 if let Some(breakpoint) = breakpoint {
9500 self.edit_breakpoint_at_anchor(
9501 anchor,
9502 breakpoint,
9503 BreakpointEditAction::Toggle,
9504 cx,
9505 );
9506 } else {
9507 self.edit_breakpoint_at_anchor(
9508 anchor,
9509 Breakpoint::new_standard(),
9510 BreakpointEditAction::Toggle,
9511 cx,
9512 );
9513 }
9514 }
9515 }
9516
9517 pub fn edit_breakpoint_at_anchor(
9518 &mut self,
9519 breakpoint_position: Anchor,
9520 breakpoint: Breakpoint,
9521 edit_action: BreakpointEditAction,
9522 cx: &mut Context<Self>,
9523 ) {
9524 let Some(breakpoint_store) = &self.breakpoint_store else {
9525 return;
9526 };
9527
9528 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9529 if breakpoint_position == Anchor::min() {
9530 self.buffer()
9531 .read(cx)
9532 .excerpt_buffer_ids()
9533 .into_iter()
9534 .next()
9535 } else {
9536 None
9537 }
9538 }) else {
9539 return;
9540 };
9541
9542 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9543 return;
9544 };
9545
9546 breakpoint_store.update(cx, |breakpoint_store, cx| {
9547 breakpoint_store.toggle_breakpoint(
9548 buffer,
9549 (breakpoint_position.text_anchor, breakpoint),
9550 edit_action,
9551 cx,
9552 );
9553 });
9554
9555 cx.notify();
9556 }
9557
9558 #[cfg(any(test, feature = "test-support"))]
9559 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9560 self.breakpoint_store.clone()
9561 }
9562
9563 pub fn prepare_restore_change(
9564 &self,
9565 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9566 hunk: &MultiBufferDiffHunk,
9567 cx: &mut App,
9568 ) -> Option<()> {
9569 if hunk.is_created_file() {
9570 return None;
9571 }
9572 let buffer = self.buffer.read(cx);
9573 let diff = buffer.diff_for(hunk.buffer_id)?;
9574 let buffer = buffer.buffer(hunk.buffer_id)?;
9575 let buffer = buffer.read(cx);
9576 let original_text = diff
9577 .read(cx)
9578 .base_text()
9579 .as_rope()
9580 .slice(hunk.diff_base_byte_range.clone());
9581 let buffer_snapshot = buffer.snapshot();
9582 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9583 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9584 probe
9585 .0
9586 .start
9587 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9588 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9589 }) {
9590 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9591 Some(())
9592 } else {
9593 None
9594 }
9595 }
9596
9597 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9598 self.manipulate_lines(window, cx, |lines| lines.reverse())
9599 }
9600
9601 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9602 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9603 }
9604
9605 fn manipulate_lines<Fn>(
9606 &mut self,
9607 window: &mut Window,
9608 cx: &mut Context<Self>,
9609 mut callback: Fn,
9610 ) where
9611 Fn: FnMut(&mut Vec<&str>),
9612 {
9613 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9614
9615 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9616 let buffer = self.buffer.read(cx).snapshot(cx);
9617
9618 let mut edits = Vec::new();
9619
9620 let selections = self.selections.all::<Point>(cx);
9621 let mut selections = selections.iter().peekable();
9622 let mut contiguous_row_selections = Vec::new();
9623 let mut new_selections = Vec::new();
9624 let mut added_lines = 0;
9625 let mut removed_lines = 0;
9626
9627 while let Some(selection) = selections.next() {
9628 let (start_row, end_row) = consume_contiguous_rows(
9629 &mut contiguous_row_selections,
9630 selection,
9631 &display_map,
9632 &mut selections,
9633 );
9634
9635 let start_point = Point::new(start_row.0, 0);
9636 let end_point = Point::new(
9637 end_row.previous_row().0,
9638 buffer.line_len(end_row.previous_row()),
9639 );
9640 let text = buffer
9641 .text_for_range(start_point..end_point)
9642 .collect::<String>();
9643
9644 let mut lines = text.split('\n').collect_vec();
9645
9646 let lines_before = lines.len();
9647 callback(&mut lines);
9648 let lines_after = lines.len();
9649
9650 edits.push((start_point..end_point, lines.join("\n")));
9651
9652 // Selections must change based on added and removed line count
9653 let start_row =
9654 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9655 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9656 new_selections.push(Selection {
9657 id: selection.id,
9658 start: start_row,
9659 end: end_row,
9660 goal: SelectionGoal::None,
9661 reversed: selection.reversed,
9662 });
9663
9664 if lines_after > lines_before {
9665 added_lines += lines_after - lines_before;
9666 } else if lines_before > lines_after {
9667 removed_lines += lines_before - lines_after;
9668 }
9669 }
9670
9671 self.transact(window, cx, |this, window, cx| {
9672 let buffer = this.buffer.update(cx, |buffer, cx| {
9673 buffer.edit(edits, None, cx);
9674 buffer.snapshot(cx)
9675 });
9676
9677 // Recalculate offsets on newly edited buffer
9678 let new_selections = new_selections
9679 .iter()
9680 .map(|s| {
9681 let start_point = Point::new(s.start.0, 0);
9682 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9683 Selection {
9684 id: s.id,
9685 start: buffer.point_to_offset(start_point),
9686 end: buffer.point_to_offset(end_point),
9687 goal: s.goal,
9688 reversed: s.reversed,
9689 }
9690 })
9691 .collect();
9692
9693 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9694 s.select(new_selections);
9695 });
9696
9697 this.request_autoscroll(Autoscroll::fit(), cx);
9698 });
9699 }
9700
9701 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9702 self.manipulate_text(window, cx, |text| {
9703 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9704 if has_upper_case_characters {
9705 text.to_lowercase()
9706 } else {
9707 text.to_uppercase()
9708 }
9709 })
9710 }
9711
9712 pub fn convert_to_upper_case(
9713 &mut self,
9714 _: &ConvertToUpperCase,
9715 window: &mut Window,
9716 cx: &mut Context<Self>,
9717 ) {
9718 self.manipulate_text(window, cx, |text| text.to_uppercase())
9719 }
9720
9721 pub fn convert_to_lower_case(
9722 &mut self,
9723 _: &ConvertToLowerCase,
9724 window: &mut Window,
9725 cx: &mut Context<Self>,
9726 ) {
9727 self.manipulate_text(window, cx, |text| text.to_lowercase())
9728 }
9729
9730 pub fn convert_to_title_case(
9731 &mut self,
9732 _: &ConvertToTitleCase,
9733 window: &mut Window,
9734 cx: &mut Context<Self>,
9735 ) {
9736 self.manipulate_text(window, cx, |text| {
9737 text.split('\n')
9738 .map(|line| line.to_case(Case::Title))
9739 .join("\n")
9740 })
9741 }
9742
9743 pub fn convert_to_snake_case(
9744 &mut self,
9745 _: &ConvertToSnakeCase,
9746 window: &mut Window,
9747 cx: &mut Context<Self>,
9748 ) {
9749 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9750 }
9751
9752 pub fn convert_to_kebab_case(
9753 &mut self,
9754 _: &ConvertToKebabCase,
9755 window: &mut Window,
9756 cx: &mut Context<Self>,
9757 ) {
9758 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9759 }
9760
9761 pub fn convert_to_upper_camel_case(
9762 &mut self,
9763 _: &ConvertToUpperCamelCase,
9764 window: &mut Window,
9765 cx: &mut Context<Self>,
9766 ) {
9767 self.manipulate_text(window, cx, |text| {
9768 text.split('\n')
9769 .map(|line| line.to_case(Case::UpperCamel))
9770 .join("\n")
9771 })
9772 }
9773
9774 pub fn convert_to_lower_camel_case(
9775 &mut self,
9776 _: &ConvertToLowerCamelCase,
9777 window: &mut Window,
9778 cx: &mut Context<Self>,
9779 ) {
9780 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9781 }
9782
9783 pub fn convert_to_opposite_case(
9784 &mut self,
9785 _: &ConvertToOppositeCase,
9786 window: &mut Window,
9787 cx: &mut Context<Self>,
9788 ) {
9789 self.manipulate_text(window, cx, |text| {
9790 text.chars()
9791 .fold(String::with_capacity(text.len()), |mut t, c| {
9792 if c.is_uppercase() {
9793 t.extend(c.to_lowercase());
9794 } else {
9795 t.extend(c.to_uppercase());
9796 }
9797 t
9798 })
9799 })
9800 }
9801
9802 pub fn convert_to_rot13(
9803 &mut self,
9804 _: &ConvertToRot13,
9805 window: &mut Window,
9806 cx: &mut Context<Self>,
9807 ) {
9808 self.manipulate_text(window, cx, |text| {
9809 text.chars()
9810 .map(|c| match c {
9811 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9812 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9813 _ => c,
9814 })
9815 .collect()
9816 })
9817 }
9818
9819 pub fn convert_to_rot47(
9820 &mut self,
9821 _: &ConvertToRot47,
9822 window: &mut Window,
9823 cx: &mut Context<Self>,
9824 ) {
9825 self.manipulate_text(window, cx, |text| {
9826 text.chars()
9827 .map(|c| {
9828 let code_point = c as u32;
9829 if code_point >= 33 && code_point <= 126 {
9830 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9831 }
9832 c
9833 })
9834 .collect()
9835 })
9836 }
9837
9838 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9839 where
9840 Fn: FnMut(&str) -> String,
9841 {
9842 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9843 let buffer = self.buffer.read(cx).snapshot(cx);
9844
9845 let mut new_selections = Vec::new();
9846 let mut edits = Vec::new();
9847 let mut selection_adjustment = 0i32;
9848
9849 for selection in self.selections.all::<usize>(cx) {
9850 let selection_is_empty = selection.is_empty();
9851
9852 let (start, end) = if selection_is_empty {
9853 let word_range = movement::surrounding_word(
9854 &display_map,
9855 selection.start.to_display_point(&display_map),
9856 );
9857 let start = word_range.start.to_offset(&display_map, Bias::Left);
9858 let end = word_range.end.to_offset(&display_map, Bias::Left);
9859 (start, end)
9860 } else {
9861 (selection.start, selection.end)
9862 };
9863
9864 let text = buffer.text_for_range(start..end).collect::<String>();
9865 let old_length = text.len() as i32;
9866 let text = callback(&text);
9867
9868 new_selections.push(Selection {
9869 start: (start as i32 - selection_adjustment) as usize,
9870 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9871 goal: SelectionGoal::None,
9872 ..selection
9873 });
9874
9875 selection_adjustment += old_length - text.len() as i32;
9876
9877 edits.push((start..end, text));
9878 }
9879
9880 self.transact(window, cx, |this, window, cx| {
9881 this.buffer.update(cx, |buffer, cx| {
9882 buffer.edit(edits, None, cx);
9883 });
9884
9885 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9886 s.select(new_selections);
9887 });
9888
9889 this.request_autoscroll(Autoscroll::fit(), cx);
9890 });
9891 }
9892
9893 pub fn duplicate(
9894 &mut self,
9895 upwards: bool,
9896 whole_lines: bool,
9897 window: &mut Window,
9898 cx: &mut Context<Self>,
9899 ) {
9900 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9901
9902 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9903 let buffer = &display_map.buffer_snapshot;
9904 let selections = self.selections.all::<Point>(cx);
9905
9906 let mut edits = Vec::new();
9907 let mut selections_iter = selections.iter().peekable();
9908 while let Some(selection) = selections_iter.next() {
9909 let mut rows = selection.spanned_rows(false, &display_map);
9910 // duplicate line-wise
9911 if whole_lines || selection.start == selection.end {
9912 // Avoid duplicating the same lines twice.
9913 while let Some(next_selection) = selections_iter.peek() {
9914 let next_rows = next_selection.spanned_rows(false, &display_map);
9915 if next_rows.start < rows.end {
9916 rows.end = next_rows.end;
9917 selections_iter.next().unwrap();
9918 } else {
9919 break;
9920 }
9921 }
9922
9923 // Copy the text from the selected row region and splice it either at the start
9924 // or end of the region.
9925 let start = Point::new(rows.start.0, 0);
9926 let end = Point::new(
9927 rows.end.previous_row().0,
9928 buffer.line_len(rows.end.previous_row()),
9929 );
9930 let text = buffer
9931 .text_for_range(start..end)
9932 .chain(Some("\n"))
9933 .collect::<String>();
9934 let insert_location = if upwards {
9935 Point::new(rows.end.0, 0)
9936 } else {
9937 start
9938 };
9939 edits.push((insert_location..insert_location, text));
9940 } else {
9941 // duplicate character-wise
9942 let start = selection.start;
9943 let end = selection.end;
9944 let text = buffer.text_for_range(start..end).collect::<String>();
9945 edits.push((selection.end..selection.end, text));
9946 }
9947 }
9948
9949 self.transact(window, cx, |this, _, cx| {
9950 this.buffer.update(cx, |buffer, cx| {
9951 buffer.edit(edits, None, cx);
9952 });
9953
9954 this.request_autoscroll(Autoscroll::fit(), cx);
9955 });
9956 }
9957
9958 pub fn duplicate_line_up(
9959 &mut self,
9960 _: &DuplicateLineUp,
9961 window: &mut Window,
9962 cx: &mut Context<Self>,
9963 ) {
9964 self.duplicate(true, true, window, cx);
9965 }
9966
9967 pub fn duplicate_line_down(
9968 &mut self,
9969 _: &DuplicateLineDown,
9970 window: &mut Window,
9971 cx: &mut Context<Self>,
9972 ) {
9973 self.duplicate(false, true, window, cx);
9974 }
9975
9976 pub fn duplicate_selection(
9977 &mut self,
9978 _: &DuplicateSelection,
9979 window: &mut Window,
9980 cx: &mut Context<Self>,
9981 ) {
9982 self.duplicate(false, false, window, cx);
9983 }
9984
9985 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9986 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9987
9988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9989 let buffer = self.buffer.read(cx).snapshot(cx);
9990
9991 let mut edits = Vec::new();
9992 let mut unfold_ranges = Vec::new();
9993 let mut refold_creases = Vec::new();
9994
9995 let selections = self.selections.all::<Point>(cx);
9996 let mut selections = selections.iter().peekable();
9997 let mut contiguous_row_selections = Vec::new();
9998 let mut new_selections = Vec::new();
9999
10000 while let Some(selection) = selections.next() {
10001 // Find all the selections that span a contiguous row range
10002 let (start_row, end_row) = consume_contiguous_rows(
10003 &mut contiguous_row_selections,
10004 selection,
10005 &display_map,
10006 &mut selections,
10007 );
10008
10009 // Move the text spanned by the row range to be before the line preceding the row range
10010 if start_row.0 > 0 {
10011 let range_to_move = Point::new(
10012 start_row.previous_row().0,
10013 buffer.line_len(start_row.previous_row()),
10014 )
10015 ..Point::new(
10016 end_row.previous_row().0,
10017 buffer.line_len(end_row.previous_row()),
10018 );
10019 let insertion_point = display_map
10020 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10021 .0;
10022
10023 // Don't move lines across excerpts
10024 if buffer
10025 .excerpt_containing(insertion_point..range_to_move.end)
10026 .is_some()
10027 {
10028 let text = buffer
10029 .text_for_range(range_to_move.clone())
10030 .flat_map(|s| s.chars())
10031 .skip(1)
10032 .chain(['\n'])
10033 .collect::<String>();
10034
10035 edits.push((
10036 buffer.anchor_after(range_to_move.start)
10037 ..buffer.anchor_before(range_to_move.end),
10038 String::new(),
10039 ));
10040 let insertion_anchor = buffer.anchor_after(insertion_point);
10041 edits.push((insertion_anchor..insertion_anchor, text));
10042
10043 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10044
10045 // Move selections up
10046 new_selections.extend(contiguous_row_selections.drain(..).map(
10047 |mut selection| {
10048 selection.start.row -= row_delta;
10049 selection.end.row -= row_delta;
10050 selection
10051 },
10052 ));
10053
10054 // Move folds up
10055 unfold_ranges.push(range_to_move.clone());
10056 for fold in display_map.folds_in_range(
10057 buffer.anchor_before(range_to_move.start)
10058 ..buffer.anchor_after(range_to_move.end),
10059 ) {
10060 let mut start = fold.range.start.to_point(&buffer);
10061 let mut end = fold.range.end.to_point(&buffer);
10062 start.row -= row_delta;
10063 end.row -= row_delta;
10064 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10065 }
10066 }
10067 }
10068
10069 // If we didn't move line(s), preserve the existing selections
10070 new_selections.append(&mut contiguous_row_selections);
10071 }
10072
10073 self.transact(window, cx, |this, window, cx| {
10074 this.unfold_ranges(&unfold_ranges, true, true, cx);
10075 this.buffer.update(cx, |buffer, cx| {
10076 for (range, text) in edits {
10077 buffer.edit([(range, text)], None, cx);
10078 }
10079 });
10080 this.fold_creases(refold_creases, true, window, cx);
10081 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10082 s.select(new_selections);
10083 })
10084 });
10085 }
10086
10087 pub fn move_line_down(
10088 &mut self,
10089 _: &MoveLineDown,
10090 window: &mut Window,
10091 cx: &mut Context<Self>,
10092 ) {
10093 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10094
10095 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10096 let buffer = self.buffer.read(cx).snapshot(cx);
10097
10098 let mut edits = Vec::new();
10099 let mut unfold_ranges = Vec::new();
10100 let mut refold_creases = Vec::new();
10101
10102 let selections = self.selections.all::<Point>(cx);
10103 let mut selections = selections.iter().peekable();
10104 let mut contiguous_row_selections = Vec::new();
10105 let mut new_selections = Vec::new();
10106
10107 while let Some(selection) = selections.next() {
10108 // Find all the selections that span a contiguous row range
10109 let (start_row, end_row) = consume_contiguous_rows(
10110 &mut contiguous_row_selections,
10111 selection,
10112 &display_map,
10113 &mut selections,
10114 );
10115
10116 // Move the text spanned by the row range to be after the last line of the row range
10117 if end_row.0 <= buffer.max_point().row {
10118 let range_to_move =
10119 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10120 let insertion_point = display_map
10121 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10122 .0;
10123
10124 // Don't move lines across excerpt boundaries
10125 if buffer
10126 .excerpt_containing(range_to_move.start..insertion_point)
10127 .is_some()
10128 {
10129 let mut text = String::from("\n");
10130 text.extend(buffer.text_for_range(range_to_move.clone()));
10131 text.pop(); // Drop trailing newline
10132 edits.push((
10133 buffer.anchor_after(range_to_move.start)
10134 ..buffer.anchor_before(range_to_move.end),
10135 String::new(),
10136 ));
10137 let insertion_anchor = buffer.anchor_after(insertion_point);
10138 edits.push((insertion_anchor..insertion_anchor, text));
10139
10140 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10141
10142 // Move selections down
10143 new_selections.extend(contiguous_row_selections.drain(..).map(
10144 |mut selection| {
10145 selection.start.row += row_delta;
10146 selection.end.row += row_delta;
10147 selection
10148 },
10149 ));
10150
10151 // Move folds down
10152 unfold_ranges.push(range_to_move.clone());
10153 for fold in display_map.folds_in_range(
10154 buffer.anchor_before(range_to_move.start)
10155 ..buffer.anchor_after(range_to_move.end),
10156 ) {
10157 let mut start = fold.range.start.to_point(&buffer);
10158 let mut end = fold.range.end.to_point(&buffer);
10159 start.row += row_delta;
10160 end.row += row_delta;
10161 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10162 }
10163 }
10164 }
10165
10166 // If we didn't move line(s), preserve the existing selections
10167 new_selections.append(&mut contiguous_row_selections);
10168 }
10169
10170 self.transact(window, cx, |this, window, cx| {
10171 this.unfold_ranges(&unfold_ranges, true, true, cx);
10172 this.buffer.update(cx, |buffer, cx| {
10173 for (range, text) in edits {
10174 buffer.edit([(range, text)], None, cx);
10175 }
10176 });
10177 this.fold_creases(refold_creases, true, window, cx);
10178 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10179 s.select(new_selections)
10180 });
10181 });
10182 }
10183
10184 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10185 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10186 let text_layout_details = &self.text_layout_details(window);
10187 self.transact(window, cx, |this, window, cx| {
10188 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10189 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10190 s.move_with(|display_map, selection| {
10191 if !selection.is_empty() {
10192 return;
10193 }
10194
10195 let mut head = selection.head();
10196 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10197 if head.column() == display_map.line_len(head.row()) {
10198 transpose_offset = display_map
10199 .buffer_snapshot
10200 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10201 }
10202
10203 if transpose_offset == 0 {
10204 return;
10205 }
10206
10207 *head.column_mut() += 1;
10208 head = display_map.clip_point(head, Bias::Right);
10209 let goal = SelectionGoal::HorizontalPosition(
10210 display_map
10211 .x_for_display_point(head, text_layout_details)
10212 .into(),
10213 );
10214 selection.collapse_to(head, goal);
10215
10216 let transpose_start = display_map
10217 .buffer_snapshot
10218 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10219 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10220 let transpose_end = display_map
10221 .buffer_snapshot
10222 .clip_offset(transpose_offset + 1, Bias::Right);
10223 if let Some(ch) =
10224 display_map.buffer_snapshot.chars_at(transpose_start).next()
10225 {
10226 edits.push((transpose_start..transpose_offset, String::new()));
10227 edits.push((transpose_end..transpose_end, ch.to_string()));
10228 }
10229 }
10230 });
10231 edits
10232 });
10233 this.buffer
10234 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10235 let selections = this.selections.all::<usize>(cx);
10236 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10237 s.select(selections);
10238 });
10239 });
10240 }
10241
10242 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10243 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10244 self.rewrap_impl(RewrapOptions::default(), cx)
10245 }
10246
10247 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10248 let buffer = self.buffer.read(cx).snapshot(cx);
10249 let selections = self.selections.all::<Point>(cx);
10250 let mut selections = selections.iter().peekable();
10251
10252 let mut edits = Vec::new();
10253 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10254
10255 while let Some(selection) = selections.next() {
10256 let mut start_row = selection.start.row;
10257 let mut end_row = selection.end.row;
10258
10259 // Skip selections that overlap with a range that has already been rewrapped.
10260 let selection_range = start_row..end_row;
10261 if rewrapped_row_ranges
10262 .iter()
10263 .any(|range| range.overlaps(&selection_range))
10264 {
10265 continue;
10266 }
10267
10268 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10269
10270 // Since not all lines in the selection may be at the same indent
10271 // level, choose the indent size that is the most common between all
10272 // of the lines.
10273 //
10274 // If there is a tie, we use the deepest indent.
10275 let (indent_size, indent_end) = {
10276 let mut indent_size_occurrences = HashMap::default();
10277 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10278
10279 for row in start_row..=end_row {
10280 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10281 rows_by_indent_size.entry(indent).or_default().push(row);
10282 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10283 }
10284
10285 let indent_size = indent_size_occurrences
10286 .into_iter()
10287 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10288 .map(|(indent, _)| indent)
10289 .unwrap_or_default();
10290 let row = rows_by_indent_size[&indent_size][0];
10291 let indent_end = Point::new(row, indent_size.len);
10292
10293 (indent_size, indent_end)
10294 };
10295
10296 let mut line_prefix = indent_size.chars().collect::<String>();
10297
10298 let mut inside_comment = false;
10299 if let Some(comment_prefix) =
10300 buffer
10301 .language_scope_at(selection.head())
10302 .and_then(|language| {
10303 language
10304 .line_comment_prefixes()
10305 .iter()
10306 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10307 .cloned()
10308 })
10309 {
10310 line_prefix.push_str(&comment_prefix);
10311 inside_comment = true;
10312 }
10313
10314 let language_settings = buffer.language_settings_at(selection.head(), cx);
10315 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10316 RewrapBehavior::InComments => inside_comment,
10317 RewrapBehavior::InSelections => !selection.is_empty(),
10318 RewrapBehavior::Anywhere => true,
10319 };
10320
10321 let should_rewrap = options.override_language_settings
10322 || allow_rewrap_based_on_language
10323 || self.hard_wrap.is_some();
10324 if !should_rewrap {
10325 continue;
10326 }
10327
10328 if selection.is_empty() {
10329 'expand_upwards: while start_row > 0 {
10330 let prev_row = start_row - 1;
10331 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10332 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10333 {
10334 start_row = prev_row;
10335 } else {
10336 break 'expand_upwards;
10337 }
10338 }
10339
10340 'expand_downwards: while end_row < buffer.max_point().row {
10341 let next_row = end_row + 1;
10342 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10343 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10344 {
10345 end_row = next_row;
10346 } else {
10347 break 'expand_downwards;
10348 }
10349 }
10350 }
10351
10352 let start = Point::new(start_row, 0);
10353 let start_offset = start.to_offset(&buffer);
10354 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10355 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10356 let Some(lines_without_prefixes) = selection_text
10357 .lines()
10358 .map(|line| {
10359 line.strip_prefix(&line_prefix)
10360 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10361 .ok_or_else(|| {
10362 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10363 })
10364 })
10365 .collect::<Result<Vec<_>, _>>()
10366 .log_err()
10367 else {
10368 continue;
10369 };
10370
10371 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10372 buffer
10373 .language_settings_at(Point::new(start_row, 0), cx)
10374 .preferred_line_length as usize
10375 });
10376 let wrapped_text = wrap_with_prefix(
10377 line_prefix,
10378 lines_without_prefixes.join("\n"),
10379 wrap_column,
10380 tab_size,
10381 options.preserve_existing_whitespace,
10382 );
10383
10384 // TODO: should always use char-based diff while still supporting cursor behavior that
10385 // matches vim.
10386 let mut diff_options = DiffOptions::default();
10387 if options.override_language_settings {
10388 diff_options.max_word_diff_len = 0;
10389 diff_options.max_word_diff_line_count = 0;
10390 } else {
10391 diff_options.max_word_diff_len = usize::MAX;
10392 diff_options.max_word_diff_line_count = usize::MAX;
10393 }
10394
10395 for (old_range, new_text) in
10396 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10397 {
10398 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10399 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10400 edits.push((edit_start..edit_end, new_text));
10401 }
10402
10403 rewrapped_row_ranges.push(start_row..=end_row);
10404 }
10405
10406 self.buffer
10407 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10408 }
10409
10410 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10411 let mut text = String::new();
10412 let buffer = self.buffer.read(cx).snapshot(cx);
10413 let mut selections = self.selections.all::<Point>(cx);
10414 let mut clipboard_selections = Vec::with_capacity(selections.len());
10415 {
10416 let max_point = buffer.max_point();
10417 let mut is_first = true;
10418 for selection in &mut selections {
10419 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10420 if is_entire_line {
10421 selection.start = Point::new(selection.start.row, 0);
10422 if !selection.is_empty() && selection.end.column == 0 {
10423 selection.end = cmp::min(max_point, selection.end);
10424 } else {
10425 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10426 }
10427 selection.goal = SelectionGoal::None;
10428 }
10429 if is_first {
10430 is_first = false;
10431 } else {
10432 text += "\n";
10433 }
10434 let mut len = 0;
10435 for chunk in buffer.text_for_range(selection.start..selection.end) {
10436 text.push_str(chunk);
10437 len += chunk.len();
10438 }
10439 clipboard_selections.push(ClipboardSelection {
10440 len,
10441 is_entire_line,
10442 first_line_indent: buffer
10443 .indent_size_for_line(MultiBufferRow(selection.start.row))
10444 .len,
10445 });
10446 }
10447 }
10448
10449 self.transact(window, cx, |this, window, cx| {
10450 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10451 s.select(selections);
10452 });
10453 this.insert("", window, cx);
10454 });
10455 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10456 }
10457
10458 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10459 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10460 let item = self.cut_common(window, cx);
10461 cx.write_to_clipboard(item);
10462 }
10463
10464 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10465 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10466 self.change_selections(None, window, cx, |s| {
10467 s.move_with(|snapshot, sel| {
10468 if sel.is_empty() {
10469 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10470 }
10471 });
10472 });
10473 let item = self.cut_common(window, cx);
10474 cx.set_global(KillRing(item))
10475 }
10476
10477 pub fn kill_ring_yank(
10478 &mut self,
10479 _: &KillRingYank,
10480 window: &mut Window,
10481 cx: &mut Context<Self>,
10482 ) {
10483 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10484 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10485 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10486 (kill_ring.text().to_string(), kill_ring.metadata_json())
10487 } else {
10488 return;
10489 }
10490 } else {
10491 return;
10492 };
10493 self.do_paste(&text, metadata, false, window, cx);
10494 }
10495
10496 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10497 self.do_copy(true, cx);
10498 }
10499
10500 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10501 self.do_copy(false, cx);
10502 }
10503
10504 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10505 let selections = self.selections.all::<Point>(cx);
10506 let buffer = self.buffer.read(cx).read(cx);
10507 let mut text = String::new();
10508
10509 let mut clipboard_selections = Vec::with_capacity(selections.len());
10510 {
10511 let max_point = buffer.max_point();
10512 let mut is_first = true;
10513 for selection in &selections {
10514 let mut start = selection.start;
10515 let mut end = selection.end;
10516 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10517 if is_entire_line {
10518 start = Point::new(start.row, 0);
10519 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10520 }
10521
10522 let mut trimmed_selections = Vec::new();
10523 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10524 let row = MultiBufferRow(start.row);
10525 let first_indent = buffer.indent_size_for_line(row);
10526 if first_indent.len == 0 || start.column > first_indent.len {
10527 trimmed_selections.push(start..end);
10528 } else {
10529 trimmed_selections.push(
10530 Point::new(row.0, first_indent.len)
10531 ..Point::new(row.0, buffer.line_len(row)),
10532 );
10533 for row in start.row + 1..=end.row {
10534 let mut line_len = buffer.line_len(MultiBufferRow(row));
10535 if row == end.row {
10536 line_len = end.column;
10537 }
10538 if line_len == 0 {
10539 trimmed_selections
10540 .push(Point::new(row, 0)..Point::new(row, line_len));
10541 continue;
10542 }
10543 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10544 if row_indent_size.len >= first_indent.len {
10545 trimmed_selections.push(
10546 Point::new(row, first_indent.len)..Point::new(row, line_len),
10547 );
10548 } else {
10549 trimmed_selections.clear();
10550 trimmed_selections.push(start..end);
10551 break;
10552 }
10553 }
10554 }
10555 } else {
10556 trimmed_selections.push(start..end);
10557 }
10558
10559 for trimmed_range in trimmed_selections {
10560 if is_first {
10561 is_first = false;
10562 } else {
10563 text += "\n";
10564 }
10565 let mut len = 0;
10566 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10567 text.push_str(chunk);
10568 len += chunk.len();
10569 }
10570 clipboard_selections.push(ClipboardSelection {
10571 len,
10572 is_entire_line,
10573 first_line_indent: buffer
10574 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10575 .len,
10576 });
10577 }
10578 }
10579 }
10580
10581 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10582 text,
10583 clipboard_selections,
10584 ));
10585 }
10586
10587 pub fn do_paste(
10588 &mut self,
10589 text: &String,
10590 clipboard_selections: Option<Vec<ClipboardSelection>>,
10591 handle_entire_lines: bool,
10592 window: &mut Window,
10593 cx: &mut Context<Self>,
10594 ) {
10595 if self.read_only(cx) {
10596 return;
10597 }
10598
10599 let clipboard_text = Cow::Borrowed(text);
10600
10601 self.transact(window, cx, |this, window, cx| {
10602 if let Some(mut clipboard_selections) = clipboard_selections {
10603 let old_selections = this.selections.all::<usize>(cx);
10604 let all_selections_were_entire_line =
10605 clipboard_selections.iter().all(|s| s.is_entire_line);
10606 let first_selection_indent_column =
10607 clipboard_selections.first().map(|s| s.first_line_indent);
10608 if clipboard_selections.len() != old_selections.len() {
10609 clipboard_selections.drain(..);
10610 }
10611 let cursor_offset = this.selections.last::<usize>(cx).head();
10612 let mut auto_indent_on_paste = true;
10613
10614 this.buffer.update(cx, |buffer, cx| {
10615 let snapshot = buffer.read(cx);
10616 auto_indent_on_paste = snapshot
10617 .language_settings_at(cursor_offset, cx)
10618 .auto_indent_on_paste;
10619
10620 let mut start_offset = 0;
10621 let mut edits = Vec::new();
10622 let mut original_indent_columns = Vec::new();
10623 for (ix, selection) in old_selections.iter().enumerate() {
10624 let to_insert;
10625 let entire_line;
10626 let original_indent_column;
10627 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10628 let end_offset = start_offset + clipboard_selection.len;
10629 to_insert = &clipboard_text[start_offset..end_offset];
10630 entire_line = clipboard_selection.is_entire_line;
10631 start_offset = end_offset + 1;
10632 original_indent_column = Some(clipboard_selection.first_line_indent);
10633 } else {
10634 to_insert = clipboard_text.as_str();
10635 entire_line = all_selections_were_entire_line;
10636 original_indent_column = first_selection_indent_column
10637 }
10638
10639 // If the corresponding selection was empty when this slice of the
10640 // clipboard text was written, then the entire line containing the
10641 // selection was copied. If this selection is also currently empty,
10642 // then paste the line before the current line of the buffer.
10643 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10644 let column = selection.start.to_point(&snapshot).column as usize;
10645 let line_start = selection.start - column;
10646 line_start..line_start
10647 } else {
10648 selection.range()
10649 };
10650
10651 edits.push((range, to_insert));
10652 original_indent_columns.push(original_indent_column);
10653 }
10654 drop(snapshot);
10655
10656 buffer.edit(
10657 edits,
10658 if auto_indent_on_paste {
10659 Some(AutoindentMode::Block {
10660 original_indent_columns,
10661 })
10662 } else {
10663 None
10664 },
10665 cx,
10666 );
10667 });
10668
10669 let selections = this.selections.all::<usize>(cx);
10670 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10671 s.select(selections)
10672 });
10673 } else {
10674 this.insert(&clipboard_text, window, cx);
10675 }
10676 });
10677 }
10678
10679 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10680 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10681 if let Some(item) = cx.read_from_clipboard() {
10682 let entries = item.entries();
10683
10684 match entries.first() {
10685 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10686 // of all the pasted entries.
10687 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10688 .do_paste(
10689 clipboard_string.text(),
10690 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10691 true,
10692 window,
10693 cx,
10694 ),
10695 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10696 }
10697 }
10698 }
10699
10700 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10701 if self.read_only(cx) {
10702 return;
10703 }
10704
10705 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10706
10707 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10708 if let Some((selections, _)) =
10709 self.selection_history.transaction(transaction_id).cloned()
10710 {
10711 self.change_selections(None, window, cx, |s| {
10712 s.select_anchors(selections.to_vec());
10713 });
10714 } else {
10715 log::error!(
10716 "No entry in selection_history found for undo. \
10717 This may correspond to a bug where undo does not update the selection. \
10718 If this is occurring, please add details to \
10719 https://github.com/zed-industries/zed/issues/22692"
10720 );
10721 }
10722 self.request_autoscroll(Autoscroll::fit(), cx);
10723 self.unmark_text(window, cx);
10724 self.refresh_inline_completion(true, false, window, cx);
10725 cx.emit(EditorEvent::Edited { transaction_id });
10726 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10727 }
10728 }
10729
10730 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10731 if self.read_only(cx) {
10732 return;
10733 }
10734
10735 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10736
10737 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10738 if let Some((_, Some(selections))) =
10739 self.selection_history.transaction(transaction_id).cloned()
10740 {
10741 self.change_selections(None, window, cx, |s| {
10742 s.select_anchors(selections.to_vec());
10743 });
10744 } else {
10745 log::error!(
10746 "No entry in selection_history found for redo. \
10747 This may correspond to a bug where undo does not update the selection. \
10748 If this is occurring, please add details to \
10749 https://github.com/zed-industries/zed/issues/22692"
10750 );
10751 }
10752 self.request_autoscroll(Autoscroll::fit(), cx);
10753 self.unmark_text(window, cx);
10754 self.refresh_inline_completion(true, false, window, cx);
10755 cx.emit(EditorEvent::Edited { transaction_id });
10756 }
10757 }
10758
10759 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10760 self.buffer
10761 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10762 }
10763
10764 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10765 self.buffer
10766 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10767 }
10768
10769 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10770 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10771 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10772 s.move_with(|map, selection| {
10773 let cursor = if selection.is_empty() {
10774 movement::left(map, selection.start)
10775 } else {
10776 selection.start
10777 };
10778 selection.collapse_to(cursor, SelectionGoal::None);
10779 });
10780 })
10781 }
10782
10783 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10784 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10785 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10786 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10787 })
10788 }
10789
10790 pub fn move_right(&mut self, _: &MoveRight, 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_with(|map, selection| {
10794 let cursor = if selection.is_empty() {
10795 movement::right(map, selection.end)
10796 } else {
10797 selection.end
10798 };
10799 selection.collapse_to(cursor, SelectionGoal::None)
10800 });
10801 })
10802 }
10803
10804 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10805 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10806 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10807 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10808 })
10809 }
10810
10811 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10812 if self.take_rename(true, window, cx).is_some() {
10813 return;
10814 }
10815
10816 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10817 cx.propagate();
10818 return;
10819 }
10820
10821 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10822
10823 let text_layout_details = &self.text_layout_details(window);
10824 let selection_count = self.selections.count();
10825 let first_selection = self.selections.first_anchor();
10826
10827 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10828 s.move_with(|map, selection| {
10829 if !selection.is_empty() {
10830 selection.goal = SelectionGoal::None;
10831 }
10832 let (cursor, goal) = movement::up(
10833 map,
10834 selection.start,
10835 selection.goal,
10836 false,
10837 text_layout_details,
10838 );
10839 selection.collapse_to(cursor, goal);
10840 });
10841 });
10842
10843 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10844 {
10845 cx.propagate();
10846 }
10847 }
10848
10849 pub fn move_up_by_lines(
10850 &mut self,
10851 action: &MoveUpByLines,
10852 window: &mut Window,
10853 cx: &mut Context<Self>,
10854 ) {
10855 if self.take_rename(true, window, cx).is_some() {
10856 return;
10857 }
10858
10859 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10860 cx.propagate();
10861 return;
10862 }
10863
10864 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10865
10866 let text_layout_details = &self.text_layout_details(window);
10867
10868 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10869 s.move_with(|map, selection| {
10870 if !selection.is_empty() {
10871 selection.goal = SelectionGoal::None;
10872 }
10873 let (cursor, goal) = movement::up_by_rows(
10874 map,
10875 selection.start,
10876 action.lines,
10877 selection.goal,
10878 false,
10879 text_layout_details,
10880 );
10881 selection.collapse_to(cursor, goal);
10882 });
10883 })
10884 }
10885
10886 pub fn move_down_by_lines(
10887 &mut self,
10888 action: &MoveDownByLines,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 if self.take_rename(true, window, cx).is_some() {
10893 return;
10894 }
10895
10896 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10897 cx.propagate();
10898 return;
10899 }
10900
10901 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10902
10903 let text_layout_details = &self.text_layout_details(window);
10904
10905 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10906 s.move_with(|map, selection| {
10907 if !selection.is_empty() {
10908 selection.goal = SelectionGoal::None;
10909 }
10910 let (cursor, goal) = movement::down_by_rows(
10911 map,
10912 selection.start,
10913 action.lines,
10914 selection.goal,
10915 false,
10916 text_layout_details,
10917 );
10918 selection.collapse_to(cursor, goal);
10919 });
10920 })
10921 }
10922
10923 pub fn select_down_by_lines(
10924 &mut self,
10925 action: &SelectDownByLines,
10926 window: &mut Window,
10927 cx: &mut Context<Self>,
10928 ) {
10929 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10930 let text_layout_details = &self.text_layout_details(window);
10931 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10932 s.move_heads_with(|map, head, goal| {
10933 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10934 })
10935 })
10936 }
10937
10938 pub fn select_up_by_lines(
10939 &mut self,
10940 action: &SelectUpByLines,
10941 window: &mut Window,
10942 cx: &mut Context<Self>,
10943 ) {
10944 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10945 let text_layout_details = &self.text_layout_details(window);
10946 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10947 s.move_heads_with(|map, head, goal| {
10948 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10949 })
10950 })
10951 }
10952
10953 pub fn select_page_up(
10954 &mut self,
10955 _: &SelectPageUp,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) {
10959 let Some(row_count) = self.visible_row_count() else {
10960 return;
10961 };
10962
10963 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10964
10965 let text_layout_details = &self.text_layout_details(window);
10966
10967 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10968 s.move_heads_with(|map, head, goal| {
10969 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10970 })
10971 })
10972 }
10973
10974 pub fn move_page_up(
10975 &mut self,
10976 action: &MovePageUp,
10977 window: &mut Window,
10978 cx: &mut Context<Self>,
10979 ) {
10980 if self.take_rename(true, window, cx).is_some() {
10981 return;
10982 }
10983
10984 if self
10985 .context_menu
10986 .borrow_mut()
10987 .as_mut()
10988 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10989 .unwrap_or(false)
10990 {
10991 return;
10992 }
10993
10994 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10995 cx.propagate();
10996 return;
10997 }
10998
10999 let Some(row_count) = self.visible_row_count() else {
11000 return;
11001 };
11002
11003 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11004
11005 let autoscroll = if action.center_cursor {
11006 Autoscroll::center()
11007 } else {
11008 Autoscroll::fit()
11009 };
11010
11011 let text_layout_details = &self.text_layout_details(window);
11012
11013 self.change_selections(Some(autoscroll), window, cx, |s| {
11014 s.move_with(|map, selection| {
11015 if !selection.is_empty() {
11016 selection.goal = SelectionGoal::None;
11017 }
11018 let (cursor, goal) = movement::up_by_rows(
11019 map,
11020 selection.end,
11021 row_count,
11022 selection.goal,
11023 false,
11024 text_layout_details,
11025 );
11026 selection.collapse_to(cursor, goal);
11027 });
11028 });
11029 }
11030
11031 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11032 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11033 let text_layout_details = &self.text_layout_details(window);
11034 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11035 s.move_heads_with(|map, head, goal| {
11036 movement::up(map, head, goal, false, text_layout_details)
11037 })
11038 })
11039 }
11040
11041 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11042 self.take_rename(true, window, cx);
11043
11044 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11045 cx.propagate();
11046 return;
11047 }
11048
11049 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11050
11051 let text_layout_details = &self.text_layout_details(window);
11052 let selection_count = self.selections.count();
11053 let first_selection = self.selections.first_anchor();
11054
11055 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11056 s.move_with(|map, selection| {
11057 if !selection.is_empty() {
11058 selection.goal = SelectionGoal::None;
11059 }
11060 let (cursor, goal) = movement::down(
11061 map,
11062 selection.end,
11063 selection.goal,
11064 false,
11065 text_layout_details,
11066 );
11067 selection.collapse_to(cursor, goal);
11068 });
11069 });
11070
11071 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11072 {
11073 cx.propagate();
11074 }
11075 }
11076
11077 pub fn select_page_down(
11078 &mut self,
11079 _: &SelectPageDown,
11080 window: &mut Window,
11081 cx: &mut Context<Self>,
11082 ) {
11083 let Some(row_count) = self.visible_row_count() else {
11084 return;
11085 };
11086
11087 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11088
11089 let text_layout_details = &self.text_layout_details(window);
11090
11091 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11092 s.move_heads_with(|map, head, goal| {
11093 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11094 })
11095 })
11096 }
11097
11098 pub fn move_page_down(
11099 &mut self,
11100 action: &MovePageDown,
11101 window: &mut Window,
11102 cx: &mut Context<Self>,
11103 ) {
11104 if self.take_rename(true, window, cx).is_some() {
11105 return;
11106 }
11107
11108 if self
11109 .context_menu
11110 .borrow_mut()
11111 .as_mut()
11112 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11113 .unwrap_or(false)
11114 {
11115 return;
11116 }
11117
11118 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11119 cx.propagate();
11120 return;
11121 }
11122
11123 let Some(row_count) = self.visible_row_count() else {
11124 return;
11125 };
11126
11127 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11128
11129 let autoscroll = if action.center_cursor {
11130 Autoscroll::center()
11131 } else {
11132 Autoscroll::fit()
11133 };
11134
11135 let text_layout_details = &self.text_layout_details(window);
11136 self.change_selections(Some(autoscroll), window, cx, |s| {
11137 s.move_with(|map, selection| {
11138 if !selection.is_empty() {
11139 selection.goal = SelectionGoal::None;
11140 }
11141 let (cursor, goal) = movement::down_by_rows(
11142 map,
11143 selection.end,
11144 row_count,
11145 selection.goal,
11146 false,
11147 text_layout_details,
11148 );
11149 selection.collapse_to(cursor, goal);
11150 });
11151 });
11152 }
11153
11154 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11155 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11156 let text_layout_details = &self.text_layout_details(window);
11157 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11158 s.move_heads_with(|map, head, goal| {
11159 movement::down(map, head, goal, false, text_layout_details)
11160 })
11161 });
11162 }
11163
11164 pub fn context_menu_first(
11165 &mut self,
11166 _: &ContextMenuFirst,
11167 _window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11171 context_menu.select_first(self.completion_provider.as_deref(), cx);
11172 }
11173 }
11174
11175 pub fn context_menu_prev(
11176 &mut self,
11177 _: &ContextMenuPrevious,
11178 _window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11182 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11183 }
11184 }
11185
11186 pub fn context_menu_next(
11187 &mut self,
11188 _: &ContextMenuNext,
11189 _window: &mut Window,
11190 cx: &mut Context<Self>,
11191 ) {
11192 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11193 context_menu.select_next(self.completion_provider.as_deref(), cx);
11194 }
11195 }
11196
11197 pub fn context_menu_last(
11198 &mut self,
11199 _: &ContextMenuLast,
11200 _window: &mut Window,
11201 cx: &mut Context<Self>,
11202 ) {
11203 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11204 context_menu.select_last(self.completion_provider.as_deref(), cx);
11205 }
11206 }
11207
11208 pub fn move_to_previous_word_start(
11209 &mut self,
11210 _: &MoveToPreviousWordStart,
11211 window: &mut Window,
11212 cx: &mut Context<Self>,
11213 ) {
11214 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11215 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11216 s.move_cursors_with(|map, head, _| {
11217 (
11218 movement::previous_word_start(map, head),
11219 SelectionGoal::None,
11220 )
11221 });
11222 })
11223 }
11224
11225 pub fn move_to_previous_subword_start(
11226 &mut self,
11227 _: &MoveToPreviousSubwordStart,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11232 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11233 s.move_cursors_with(|map, head, _| {
11234 (
11235 movement::previous_subword_start(map, head),
11236 SelectionGoal::None,
11237 )
11238 });
11239 })
11240 }
11241
11242 pub fn select_to_previous_word_start(
11243 &mut self,
11244 _: &SelectToPreviousWordStart,
11245 window: &mut Window,
11246 cx: &mut Context<Self>,
11247 ) {
11248 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11250 s.move_heads_with(|map, head, _| {
11251 (
11252 movement::previous_word_start(map, head),
11253 SelectionGoal::None,
11254 )
11255 });
11256 })
11257 }
11258
11259 pub fn select_to_previous_subword_start(
11260 &mut self,
11261 _: &SelectToPreviousSubwordStart,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11266 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11267 s.move_heads_with(|map, head, _| {
11268 (
11269 movement::previous_subword_start(map, head),
11270 SelectionGoal::None,
11271 )
11272 });
11273 })
11274 }
11275
11276 pub fn delete_to_previous_word_start(
11277 &mut self,
11278 action: &DeleteToPreviousWordStart,
11279 window: &mut Window,
11280 cx: &mut Context<Self>,
11281 ) {
11282 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11283 self.transact(window, cx, |this, window, cx| {
11284 this.select_autoclose_pair(window, cx);
11285 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11286 s.move_with(|map, selection| {
11287 if selection.is_empty() {
11288 let cursor = if action.ignore_newlines {
11289 movement::previous_word_start(map, selection.head())
11290 } else {
11291 movement::previous_word_start_or_newline(map, selection.head())
11292 };
11293 selection.set_head(cursor, SelectionGoal::None);
11294 }
11295 });
11296 });
11297 this.insert("", window, cx);
11298 });
11299 }
11300
11301 pub fn delete_to_previous_subword_start(
11302 &mut self,
11303 _: &DeleteToPreviousSubwordStart,
11304 window: &mut Window,
11305 cx: &mut Context<Self>,
11306 ) {
11307 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11308 self.transact(window, cx, |this, window, cx| {
11309 this.select_autoclose_pair(window, cx);
11310 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11311 s.move_with(|map, selection| {
11312 if selection.is_empty() {
11313 let cursor = movement::previous_subword_start(map, selection.head());
11314 selection.set_head(cursor, SelectionGoal::None);
11315 }
11316 });
11317 });
11318 this.insert("", window, cx);
11319 });
11320 }
11321
11322 pub fn move_to_next_word_end(
11323 &mut self,
11324 _: &MoveToNextWordEnd,
11325 window: &mut Window,
11326 cx: &mut Context<Self>,
11327 ) {
11328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11330 s.move_cursors_with(|map, head, _| {
11331 (movement::next_word_end(map, head), SelectionGoal::None)
11332 });
11333 })
11334 }
11335
11336 pub fn move_to_next_subword_end(
11337 &mut self,
11338 _: &MoveToNextSubwordEnd,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 ) {
11342 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11343 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11344 s.move_cursors_with(|map, head, _| {
11345 (movement::next_subword_end(map, head), SelectionGoal::None)
11346 });
11347 })
11348 }
11349
11350 pub fn select_to_next_word_end(
11351 &mut self,
11352 _: &SelectToNextWordEnd,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11357 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11358 s.move_heads_with(|map, head, _| {
11359 (movement::next_word_end(map, head), SelectionGoal::None)
11360 });
11361 })
11362 }
11363
11364 pub fn select_to_next_subword_end(
11365 &mut self,
11366 _: &SelectToNextSubwordEnd,
11367 window: &mut Window,
11368 cx: &mut Context<Self>,
11369 ) {
11370 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11372 s.move_heads_with(|map, head, _| {
11373 (movement::next_subword_end(map, head), SelectionGoal::None)
11374 });
11375 })
11376 }
11377
11378 pub fn delete_to_next_word_end(
11379 &mut self,
11380 action: &DeleteToNextWordEnd,
11381 window: &mut Window,
11382 cx: &mut Context<Self>,
11383 ) {
11384 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11385 self.transact(window, cx, |this, window, cx| {
11386 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11387 s.move_with(|map, selection| {
11388 if selection.is_empty() {
11389 let cursor = if action.ignore_newlines {
11390 movement::next_word_end(map, selection.head())
11391 } else {
11392 movement::next_word_end_or_newline(map, selection.head())
11393 };
11394 selection.set_head(cursor, SelectionGoal::None);
11395 }
11396 });
11397 });
11398 this.insert("", window, cx);
11399 });
11400 }
11401
11402 pub fn delete_to_next_subword_end(
11403 &mut self,
11404 _: &DeleteToNextSubwordEnd,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11409 self.transact(window, cx, |this, window, cx| {
11410 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11411 s.move_with(|map, selection| {
11412 if selection.is_empty() {
11413 let cursor = movement::next_subword_end(map, selection.head());
11414 selection.set_head(cursor, SelectionGoal::None);
11415 }
11416 });
11417 });
11418 this.insert("", window, cx);
11419 });
11420 }
11421
11422 pub fn move_to_beginning_of_line(
11423 &mut self,
11424 action: &MoveToBeginningOfLine,
11425 window: &mut Window,
11426 cx: &mut Context<Self>,
11427 ) {
11428 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11429 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11430 s.move_cursors_with(|map, head, _| {
11431 (
11432 movement::indented_line_beginning(
11433 map,
11434 head,
11435 action.stop_at_soft_wraps,
11436 action.stop_at_indent,
11437 ),
11438 SelectionGoal::None,
11439 )
11440 });
11441 })
11442 }
11443
11444 pub fn select_to_beginning_of_line(
11445 &mut self,
11446 action: &SelectToBeginningOfLine,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11451 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11452 s.move_heads_with(|map, head, _| {
11453 (
11454 movement::indented_line_beginning(
11455 map,
11456 head,
11457 action.stop_at_soft_wraps,
11458 action.stop_at_indent,
11459 ),
11460 SelectionGoal::None,
11461 )
11462 });
11463 });
11464 }
11465
11466 pub fn delete_to_beginning_of_line(
11467 &mut self,
11468 action: &DeleteToBeginningOfLine,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11473 self.transact(window, cx, |this, window, cx| {
11474 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11475 s.move_with(|_, selection| {
11476 selection.reversed = true;
11477 });
11478 });
11479
11480 this.select_to_beginning_of_line(
11481 &SelectToBeginningOfLine {
11482 stop_at_soft_wraps: false,
11483 stop_at_indent: action.stop_at_indent,
11484 },
11485 window,
11486 cx,
11487 );
11488 this.backspace(&Backspace, window, cx);
11489 });
11490 }
11491
11492 pub fn move_to_end_of_line(
11493 &mut self,
11494 action: &MoveToEndOfLine,
11495 window: &mut Window,
11496 cx: &mut Context<Self>,
11497 ) {
11498 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11499 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11500 s.move_cursors_with(|map, head, _| {
11501 (
11502 movement::line_end(map, head, action.stop_at_soft_wraps),
11503 SelectionGoal::None,
11504 )
11505 });
11506 })
11507 }
11508
11509 pub fn select_to_end_of_line(
11510 &mut self,
11511 action: &SelectToEndOfLine,
11512 window: &mut Window,
11513 cx: &mut Context<Self>,
11514 ) {
11515 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11517 s.move_heads_with(|map, head, _| {
11518 (
11519 movement::line_end(map, head, action.stop_at_soft_wraps),
11520 SelectionGoal::None,
11521 )
11522 });
11523 })
11524 }
11525
11526 pub fn delete_to_end_of_line(
11527 &mut self,
11528 _: &DeleteToEndOfLine,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11533 self.transact(window, cx, |this, window, cx| {
11534 this.select_to_end_of_line(
11535 &SelectToEndOfLine {
11536 stop_at_soft_wraps: false,
11537 },
11538 window,
11539 cx,
11540 );
11541 this.delete(&Delete, window, cx);
11542 });
11543 }
11544
11545 pub fn cut_to_end_of_line(
11546 &mut self,
11547 _: &CutToEndOfLine,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11552 self.transact(window, cx, |this, window, cx| {
11553 this.select_to_end_of_line(
11554 &SelectToEndOfLine {
11555 stop_at_soft_wraps: false,
11556 },
11557 window,
11558 cx,
11559 );
11560 this.cut(&Cut, window, cx);
11561 });
11562 }
11563
11564 pub fn move_to_start_of_paragraph(
11565 &mut self,
11566 _: &MoveToStartOfParagraph,
11567 window: &mut Window,
11568 cx: &mut Context<Self>,
11569 ) {
11570 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11571 cx.propagate();
11572 return;
11573 }
11574 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11575 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11576 s.move_with(|map, selection| {
11577 selection.collapse_to(
11578 movement::start_of_paragraph(map, selection.head(), 1),
11579 SelectionGoal::None,
11580 )
11581 });
11582 })
11583 }
11584
11585 pub fn move_to_end_of_paragraph(
11586 &mut self,
11587 _: &MoveToEndOfParagraph,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11592 cx.propagate();
11593 return;
11594 }
11595 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11596 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11597 s.move_with(|map, selection| {
11598 selection.collapse_to(
11599 movement::end_of_paragraph(map, selection.head(), 1),
11600 SelectionGoal::None,
11601 )
11602 });
11603 })
11604 }
11605
11606 pub fn select_to_start_of_paragraph(
11607 &mut self,
11608 _: &SelectToStartOfParagraph,
11609 window: &mut Window,
11610 cx: &mut Context<Self>,
11611 ) {
11612 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11613 cx.propagate();
11614 return;
11615 }
11616 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11618 s.move_heads_with(|map, head, _| {
11619 (
11620 movement::start_of_paragraph(map, head, 1),
11621 SelectionGoal::None,
11622 )
11623 });
11624 })
11625 }
11626
11627 pub fn select_to_end_of_paragraph(
11628 &mut self,
11629 _: &SelectToEndOfParagraph,
11630 window: &mut Window,
11631 cx: &mut Context<Self>,
11632 ) {
11633 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11634 cx.propagate();
11635 return;
11636 }
11637 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11638 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11639 s.move_heads_with(|map, head, _| {
11640 (
11641 movement::end_of_paragraph(map, head, 1),
11642 SelectionGoal::None,
11643 )
11644 });
11645 })
11646 }
11647
11648 pub fn move_to_start_of_excerpt(
11649 &mut self,
11650 _: &MoveToStartOfExcerpt,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11655 cx.propagate();
11656 return;
11657 }
11658 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11659 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11660 s.move_with(|map, selection| {
11661 selection.collapse_to(
11662 movement::start_of_excerpt(
11663 map,
11664 selection.head(),
11665 workspace::searchable::Direction::Prev,
11666 ),
11667 SelectionGoal::None,
11668 )
11669 });
11670 })
11671 }
11672
11673 pub fn move_to_start_of_next_excerpt(
11674 &mut self,
11675 _: &MoveToStartOfNextExcerpt,
11676 window: &mut Window,
11677 cx: &mut Context<Self>,
11678 ) {
11679 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11680 cx.propagate();
11681 return;
11682 }
11683
11684 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11685 s.move_with(|map, selection| {
11686 selection.collapse_to(
11687 movement::start_of_excerpt(
11688 map,
11689 selection.head(),
11690 workspace::searchable::Direction::Next,
11691 ),
11692 SelectionGoal::None,
11693 )
11694 });
11695 })
11696 }
11697
11698 pub fn move_to_end_of_excerpt(
11699 &mut self,
11700 _: &MoveToEndOfExcerpt,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11705 cx.propagate();
11706 return;
11707 }
11708 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11709 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11710 s.move_with(|map, selection| {
11711 selection.collapse_to(
11712 movement::end_of_excerpt(
11713 map,
11714 selection.head(),
11715 workspace::searchable::Direction::Next,
11716 ),
11717 SelectionGoal::None,
11718 )
11719 });
11720 })
11721 }
11722
11723 pub fn move_to_end_of_previous_excerpt(
11724 &mut self,
11725 _: &MoveToEndOfPreviousExcerpt,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11730 cx.propagate();
11731 return;
11732 }
11733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11735 s.move_with(|map, selection| {
11736 selection.collapse_to(
11737 movement::end_of_excerpt(
11738 map,
11739 selection.head(),
11740 workspace::searchable::Direction::Prev,
11741 ),
11742 SelectionGoal::None,
11743 )
11744 });
11745 })
11746 }
11747
11748 pub fn select_to_start_of_excerpt(
11749 &mut self,
11750 _: &SelectToStartOfExcerpt,
11751 window: &mut Window,
11752 cx: &mut Context<Self>,
11753 ) {
11754 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11755 cx.propagate();
11756 return;
11757 }
11758 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11759 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11760 s.move_heads_with(|map, head, _| {
11761 (
11762 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11763 SelectionGoal::None,
11764 )
11765 });
11766 })
11767 }
11768
11769 pub fn select_to_start_of_next_excerpt(
11770 &mut self,
11771 _: &SelectToStartOfNextExcerpt,
11772 window: &mut Window,
11773 cx: &mut Context<Self>,
11774 ) {
11775 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11776 cx.propagate();
11777 return;
11778 }
11779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11780 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11781 s.move_heads_with(|map, head, _| {
11782 (
11783 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11784 SelectionGoal::None,
11785 )
11786 });
11787 })
11788 }
11789
11790 pub fn select_to_end_of_excerpt(
11791 &mut self,
11792 _: &SelectToEndOfExcerpt,
11793 window: &mut Window,
11794 cx: &mut Context<Self>,
11795 ) {
11796 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11797 cx.propagate();
11798 return;
11799 }
11800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11802 s.move_heads_with(|map, head, _| {
11803 (
11804 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11805 SelectionGoal::None,
11806 )
11807 });
11808 })
11809 }
11810
11811 pub fn select_to_end_of_previous_excerpt(
11812 &mut self,
11813 _: &SelectToEndOfPreviousExcerpt,
11814 window: &mut Window,
11815 cx: &mut Context<Self>,
11816 ) {
11817 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11818 cx.propagate();
11819 return;
11820 }
11821 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11822 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11823 s.move_heads_with(|map, head, _| {
11824 (
11825 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11826 SelectionGoal::None,
11827 )
11828 });
11829 })
11830 }
11831
11832 pub fn move_to_beginning(
11833 &mut self,
11834 _: &MoveToBeginning,
11835 window: &mut Window,
11836 cx: &mut Context<Self>,
11837 ) {
11838 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11839 cx.propagate();
11840 return;
11841 }
11842 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11843 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11844 s.select_ranges(vec![0..0]);
11845 });
11846 }
11847
11848 pub fn select_to_beginning(
11849 &mut self,
11850 _: &SelectToBeginning,
11851 window: &mut Window,
11852 cx: &mut Context<Self>,
11853 ) {
11854 let mut selection = self.selections.last::<Point>(cx);
11855 selection.set_head(Point::zero(), SelectionGoal::None);
11856 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11857 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11858 s.select(vec![selection]);
11859 });
11860 }
11861
11862 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11863 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11864 cx.propagate();
11865 return;
11866 }
11867 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11868 let cursor = self.buffer.read(cx).read(cx).len();
11869 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11870 s.select_ranges(vec![cursor..cursor])
11871 });
11872 }
11873
11874 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11875 self.nav_history = nav_history;
11876 }
11877
11878 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11879 self.nav_history.as_ref()
11880 }
11881
11882 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11883 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11884 }
11885
11886 fn push_to_nav_history(
11887 &mut self,
11888 cursor_anchor: Anchor,
11889 new_position: Option<Point>,
11890 is_deactivate: bool,
11891 cx: &mut Context<Self>,
11892 ) {
11893 if let Some(nav_history) = self.nav_history.as_mut() {
11894 let buffer = self.buffer.read(cx).read(cx);
11895 let cursor_position = cursor_anchor.to_point(&buffer);
11896 let scroll_state = self.scroll_manager.anchor();
11897 let scroll_top_row = scroll_state.top_row(&buffer);
11898 drop(buffer);
11899
11900 if let Some(new_position) = new_position {
11901 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11902 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11903 return;
11904 }
11905 }
11906
11907 nav_history.push(
11908 Some(NavigationData {
11909 cursor_anchor,
11910 cursor_position,
11911 scroll_anchor: scroll_state,
11912 scroll_top_row,
11913 }),
11914 cx,
11915 );
11916 cx.emit(EditorEvent::PushedToNavHistory {
11917 anchor: cursor_anchor,
11918 is_deactivate,
11919 })
11920 }
11921 }
11922
11923 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11924 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11925 let buffer = self.buffer.read(cx).snapshot(cx);
11926 let mut selection = self.selections.first::<usize>(cx);
11927 selection.set_head(buffer.len(), SelectionGoal::None);
11928 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11929 s.select(vec![selection]);
11930 });
11931 }
11932
11933 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11934 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11935 let end = self.buffer.read(cx).read(cx).len();
11936 self.change_selections(None, window, cx, |s| {
11937 s.select_ranges(vec![0..end]);
11938 });
11939 }
11940
11941 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11942 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11944 let mut selections = self.selections.all::<Point>(cx);
11945 let max_point = display_map.buffer_snapshot.max_point();
11946 for selection in &mut selections {
11947 let rows = selection.spanned_rows(true, &display_map);
11948 selection.start = Point::new(rows.start.0, 0);
11949 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11950 selection.reversed = false;
11951 }
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.select(selections);
11954 });
11955 }
11956
11957 pub fn split_selection_into_lines(
11958 &mut self,
11959 _: &SplitSelectionIntoLines,
11960 window: &mut Window,
11961 cx: &mut Context<Self>,
11962 ) {
11963 let selections = self
11964 .selections
11965 .all::<Point>(cx)
11966 .into_iter()
11967 .map(|selection| selection.start..selection.end)
11968 .collect::<Vec<_>>();
11969 self.unfold_ranges(&selections, true, true, cx);
11970
11971 let mut new_selection_ranges = Vec::new();
11972 {
11973 let buffer = self.buffer.read(cx).read(cx);
11974 for selection in selections {
11975 for row in selection.start.row..selection.end.row {
11976 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11977 new_selection_ranges.push(cursor..cursor);
11978 }
11979
11980 let is_multiline_selection = selection.start.row != selection.end.row;
11981 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11982 // so this action feels more ergonomic when paired with other selection operations
11983 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11984 if !should_skip_last {
11985 new_selection_ranges.push(selection.end..selection.end);
11986 }
11987 }
11988 }
11989 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11990 s.select_ranges(new_selection_ranges);
11991 });
11992 }
11993
11994 pub fn add_selection_above(
11995 &mut self,
11996 _: &AddSelectionAbove,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 self.add_selection(true, window, cx);
12001 }
12002
12003 pub fn add_selection_below(
12004 &mut self,
12005 _: &AddSelectionBelow,
12006 window: &mut Window,
12007 cx: &mut Context<Self>,
12008 ) {
12009 self.add_selection(false, window, cx);
12010 }
12011
12012 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12013 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12014
12015 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12016 let mut selections = self.selections.all::<Point>(cx);
12017 let text_layout_details = self.text_layout_details(window);
12018 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12019 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12020 let range = oldest_selection.display_range(&display_map).sorted();
12021
12022 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12023 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12024 let positions = start_x.min(end_x)..start_x.max(end_x);
12025
12026 selections.clear();
12027 let mut stack = Vec::new();
12028 for row in range.start.row().0..=range.end.row().0 {
12029 if let Some(selection) = self.selections.build_columnar_selection(
12030 &display_map,
12031 DisplayRow(row),
12032 &positions,
12033 oldest_selection.reversed,
12034 &text_layout_details,
12035 ) {
12036 stack.push(selection.id);
12037 selections.push(selection);
12038 }
12039 }
12040
12041 if above {
12042 stack.reverse();
12043 }
12044
12045 AddSelectionsState { above, stack }
12046 });
12047
12048 let last_added_selection = *state.stack.last().unwrap();
12049 let mut new_selections = Vec::new();
12050 if above == state.above {
12051 let end_row = if above {
12052 DisplayRow(0)
12053 } else {
12054 display_map.max_point().row()
12055 };
12056
12057 'outer: for selection in selections {
12058 if selection.id == last_added_selection {
12059 let range = selection.display_range(&display_map).sorted();
12060 debug_assert_eq!(range.start.row(), range.end.row());
12061 let mut row = range.start.row();
12062 let positions =
12063 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12064 px(start)..px(end)
12065 } else {
12066 let start_x =
12067 display_map.x_for_display_point(range.start, &text_layout_details);
12068 let end_x =
12069 display_map.x_for_display_point(range.end, &text_layout_details);
12070 start_x.min(end_x)..start_x.max(end_x)
12071 };
12072
12073 while row != end_row {
12074 if above {
12075 row.0 -= 1;
12076 } else {
12077 row.0 += 1;
12078 }
12079
12080 if let Some(new_selection) = self.selections.build_columnar_selection(
12081 &display_map,
12082 row,
12083 &positions,
12084 selection.reversed,
12085 &text_layout_details,
12086 ) {
12087 state.stack.push(new_selection.id);
12088 if above {
12089 new_selections.push(new_selection);
12090 new_selections.push(selection);
12091 } else {
12092 new_selections.push(selection);
12093 new_selections.push(new_selection);
12094 }
12095
12096 continue 'outer;
12097 }
12098 }
12099 }
12100
12101 new_selections.push(selection);
12102 }
12103 } else {
12104 new_selections = selections;
12105 new_selections.retain(|s| s.id != last_added_selection);
12106 state.stack.pop();
12107 }
12108
12109 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12110 s.select(new_selections);
12111 });
12112 if state.stack.len() > 1 {
12113 self.add_selections_state = Some(state);
12114 }
12115 }
12116
12117 pub fn select_next_match_internal(
12118 &mut self,
12119 display_map: &DisplaySnapshot,
12120 replace_newest: bool,
12121 autoscroll: Option<Autoscroll>,
12122 window: &mut Window,
12123 cx: &mut Context<Self>,
12124 ) -> Result<()> {
12125 fn select_next_match_ranges(
12126 this: &mut Editor,
12127 range: Range<usize>,
12128 reversed: bool,
12129 replace_newest: bool,
12130 auto_scroll: Option<Autoscroll>,
12131 window: &mut Window,
12132 cx: &mut Context<Editor>,
12133 ) {
12134 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12135 this.change_selections(auto_scroll, window, cx, |s| {
12136 if replace_newest {
12137 s.delete(s.newest_anchor().id);
12138 }
12139 if reversed {
12140 s.insert_range(range.end..range.start);
12141 } else {
12142 s.insert_range(range);
12143 }
12144 });
12145 }
12146
12147 let buffer = &display_map.buffer_snapshot;
12148 let mut selections = self.selections.all::<usize>(cx);
12149 if let Some(mut select_next_state) = self.select_next_state.take() {
12150 let query = &select_next_state.query;
12151 if !select_next_state.done {
12152 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12153 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12154 let mut next_selected_range = None;
12155
12156 let bytes_after_last_selection =
12157 buffer.bytes_in_range(last_selection.end..buffer.len());
12158 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12159 let query_matches = query
12160 .stream_find_iter(bytes_after_last_selection)
12161 .map(|result| (last_selection.end, result))
12162 .chain(
12163 query
12164 .stream_find_iter(bytes_before_first_selection)
12165 .map(|result| (0, result)),
12166 );
12167
12168 for (start_offset, query_match) in query_matches {
12169 let query_match = query_match.unwrap(); // can only fail due to I/O
12170 let offset_range =
12171 start_offset + query_match.start()..start_offset + query_match.end();
12172 let display_range = offset_range.start.to_display_point(display_map)
12173 ..offset_range.end.to_display_point(display_map);
12174
12175 if !select_next_state.wordwise
12176 || (!movement::is_inside_word(display_map, display_range.start)
12177 && !movement::is_inside_word(display_map, display_range.end))
12178 {
12179 // TODO: This is n^2, because we might check all the selections
12180 if !selections
12181 .iter()
12182 .any(|selection| selection.range().overlaps(&offset_range))
12183 {
12184 next_selected_range = Some(offset_range);
12185 break;
12186 }
12187 }
12188 }
12189
12190 if let Some(next_selected_range) = next_selected_range {
12191 select_next_match_ranges(
12192 self,
12193 next_selected_range,
12194 last_selection.reversed,
12195 replace_newest,
12196 autoscroll,
12197 window,
12198 cx,
12199 );
12200 } else {
12201 select_next_state.done = true;
12202 }
12203 }
12204
12205 self.select_next_state = Some(select_next_state);
12206 } else {
12207 let mut only_carets = true;
12208 let mut same_text_selected = true;
12209 let mut selected_text = None;
12210
12211 let mut selections_iter = selections.iter().peekable();
12212 while let Some(selection) = selections_iter.next() {
12213 if selection.start != selection.end {
12214 only_carets = false;
12215 }
12216
12217 if same_text_selected {
12218 if selected_text.is_none() {
12219 selected_text =
12220 Some(buffer.text_for_range(selection.range()).collect::<String>());
12221 }
12222
12223 if let Some(next_selection) = selections_iter.peek() {
12224 if next_selection.range().len() == selection.range().len() {
12225 let next_selected_text = buffer
12226 .text_for_range(next_selection.range())
12227 .collect::<String>();
12228 if Some(next_selected_text) != selected_text {
12229 same_text_selected = false;
12230 selected_text = None;
12231 }
12232 } else {
12233 same_text_selected = false;
12234 selected_text = None;
12235 }
12236 }
12237 }
12238 }
12239
12240 if only_carets {
12241 for selection in &mut selections {
12242 let word_range = movement::surrounding_word(
12243 display_map,
12244 selection.start.to_display_point(display_map),
12245 );
12246 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12247 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12248 selection.goal = SelectionGoal::None;
12249 selection.reversed = false;
12250 select_next_match_ranges(
12251 self,
12252 selection.start..selection.end,
12253 selection.reversed,
12254 replace_newest,
12255 autoscroll,
12256 window,
12257 cx,
12258 );
12259 }
12260
12261 if selections.len() == 1 {
12262 let selection = selections
12263 .last()
12264 .expect("ensured that there's only one selection");
12265 let query = buffer
12266 .text_for_range(selection.start..selection.end)
12267 .collect::<String>();
12268 let is_empty = query.is_empty();
12269 let select_state = SelectNextState {
12270 query: AhoCorasick::new(&[query])?,
12271 wordwise: true,
12272 done: is_empty,
12273 };
12274 self.select_next_state = Some(select_state);
12275 } else {
12276 self.select_next_state = None;
12277 }
12278 } else if let Some(selected_text) = selected_text {
12279 self.select_next_state = Some(SelectNextState {
12280 query: AhoCorasick::new(&[selected_text])?,
12281 wordwise: false,
12282 done: false,
12283 });
12284 self.select_next_match_internal(
12285 display_map,
12286 replace_newest,
12287 autoscroll,
12288 window,
12289 cx,
12290 )?;
12291 }
12292 }
12293 Ok(())
12294 }
12295
12296 pub fn select_all_matches(
12297 &mut self,
12298 _action: &SelectAllMatches,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) -> Result<()> {
12302 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12303
12304 self.push_to_selection_history();
12305 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12306
12307 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12308 let Some(select_next_state) = self.select_next_state.as_mut() else {
12309 return Ok(());
12310 };
12311 if select_next_state.done {
12312 return Ok(());
12313 }
12314
12315 let mut new_selections = Vec::new();
12316
12317 let reversed = self.selections.oldest::<usize>(cx).reversed;
12318 let buffer = &display_map.buffer_snapshot;
12319 let query_matches = select_next_state
12320 .query
12321 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12322
12323 for query_match in query_matches.into_iter() {
12324 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12325 let offset_range = if reversed {
12326 query_match.end()..query_match.start()
12327 } else {
12328 query_match.start()..query_match.end()
12329 };
12330 let display_range = offset_range.start.to_display_point(&display_map)
12331 ..offset_range.end.to_display_point(&display_map);
12332
12333 if !select_next_state.wordwise
12334 || (!movement::is_inside_word(&display_map, display_range.start)
12335 && !movement::is_inside_word(&display_map, display_range.end))
12336 {
12337 new_selections.push(offset_range.start..offset_range.end);
12338 }
12339 }
12340
12341 select_next_state.done = true;
12342 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12343 self.change_selections(None, window, cx, |selections| {
12344 selections.select_ranges(new_selections)
12345 });
12346
12347 Ok(())
12348 }
12349
12350 pub fn select_next(
12351 &mut self,
12352 action: &SelectNext,
12353 window: &mut Window,
12354 cx: &mut Context<Self>,
12355 ) -> Result<()> {
12356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12357 self.push_to_selection_history();
12358 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12359 self.select_next_match_internal(
12360 &display_map,
12361 action.replace_newest,
12362 Some(Autoscroll::newest()),
12363 window,
12364 cx,
12365 )?;
12366 Ok(())
12367 }
12368
12369 pub fn select_previous(
12370 &mut self,
12371 action: &SelectPrevious,
12372 window: &mut Window,
12373 cx: &mut Context<Self>,
12374 ) -> Result<()> {
12375 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12376 self.push_to_selection_history();
12377 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12378 let buffer = &display_map.buffer_snapshot;
12379 let mut selections = self.selections.all::<usize>(cx);
12380 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12381 let query = &select_prev_state.query;
12382 if !select_prev_state.done {
12383 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12384 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12385 let mut next_selected_range = None;
12386 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12387 let bytes_before_last_selection =
12388 buffer.reversed_bytes_in_range(0..last_selection.start);
12389 let bytes_after_first_selection =
12390 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12391 let query_matches = query
12392 .stream_find_iter(bytes_before_last_selection)
12393 .map(|result| (last_selection.start, result))
12394 .chain(
12395 query
12396 .stream_find_iter(bytes_after_first_selection)
12397 .map(|result| (buffer.len(), result)),
12398 );
12399 for (end_offset, query_match) in query_matches {
12400 let query_match = query_match.unwrap(); // can only fail due to I/O
12401 let offset_range =
12402 end_offset - query_match.end()..end_offset - query_match.start();
12403 let display_range = offset_range.start.to_display_point(&display_map)
12404 ..offset_range.end.to_display_point(&display_map);
12405
12406 if !select_prev_state.wordwise
12407 || (!movement::is_inside_word(&display_map, display_range.start)
12408 && !movement::is_inside_word(&display_map, display_range.end))
12409 {
12410 next_selected_range = Some(offset_range);
12411 break;
12412 }
12413 }
12414
12415 if let Some(next_selected_range) = next_selected_range {
12416 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12417 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12418 if action.replace_newest {
12419 s.delete(s.newest_anchor().id);
12420 }
12421 if last_selection.reversed {
12422 s.insert_range(next_selected_range.end..next_selected_range.start);
12423 } else {
12424 s.insert_range(next_selected_range);
12425 }
12426 });
12427 } else {
12428 select_prev_state.done = true;
12429 }
12430 }
12431
12432 self.select_prev_state = Some(select_prev_state);
12433 } else {
12434 let mut only_carets = true;
12435 let mut same_text_selected = true;
12436 let mut selected_text = None;
12437
12438 let mut selections_iter = selections.iter().peekable();
12439 while let Some(selection) = selections_iter.next() {
12440 if selection.start != selection.end {
12441 only_carets = false;
12442 }
12443
12444 if same_text_selected {
12445 if selected_text.is_none() {
12446 selected_text =
12447 Some(buffer.text_for_range(selection.range()).collect::<String>());
12448 }
12449
12450 if let Some(next_selection) = selections_iter.peek() {
12451 if next_selection.range().len() == selection.range().len() {
12452 let next_selected_text = buffer
12453 .text_for_range(next_selection.range())
12454 .collect::<String>();
12455 if Some(next_selected_text) != selected_text {
12456 same_text_selected = false;
12457 selected_text = None;
12458 }
12459 } else {
12460 same_text_selected = false;
12461 selected_text = None;
12462 }
12463 }
12464 }
12465 }
12466
12467 if only_carets {
12468 for selection in &mut selections {
12469 let word_range = movement::surrounding_word(
12470 &display_map,
12471 selection.start.to_display_point(&display_map),
12472 );
12473 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12474 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12475 selection.goal = SelectionGoal::None;
12476 selection.reversed = false;
12477 }
12478 if selections.len() == 1 {
12479 let selection = selections
12480 .last()
12481 .expect("ensured that there's only one selection");
12482 let query = buffer
12483 .text_for_range(selection.start..selection.end)
12484 .collect::<String>();
12485 let is_empty = query.is_empty();
12486 let select_state = SelectNextState {
12487 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12488 wordwise: true,
12489 done: is_empty,
12490 };
12491 self.select_prev_state = Some(select_state);
12492 } else {
12493 self.select_prev_state = None;
12494 }
12495
12496 self.unfold_ranges(
12497 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12498 false,
12499 true,
12500 cx,
12501 );
12502 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12503 s.select(selections);
12504 });
12505 } else if let Some(selected_text) = selected_text {
12506 self.select_prev_state = Some(SelectNextState {
12507 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12508 wordwise: false,
12509 done: false,
12510 });
12511 self.select_previous(action, window, cx)?;
12512 }
12513 }
12514 Ok(())
12515 }
12516
12517 pub fn find_next_match(
12518 &mut self,
12519 _: &FindNextMatch,
12520 window: &mut Window,
12521 cx: &mut Context<Self>,
12522 ) -> Result<()> {
12523 let selections = self.selections.disjoint_anchors();
12524 match selections.first() {
12525 Some(first) if selections.len() >= 2 => {
12526 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12527 s.select_ranges([first.range()]);
12528 });
12529 }
12530 _ => self.select_next(
12531 &SelectNext {
12532 replace_newest: true,
12533 },
12534 window,
12535 cx,
12536 )?,
12537 }
12538 Ok(())
12539 }
12540
12541 pub fn find_previous_match(
12542 &mut self,
12543 _: &FindPreviousMatch,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 ) -> Result<()> {
12547 let selections = self.selections.disjoint_anchors();
12548 match selections.last() {
12549 Some(last) if selections.len() >= 2 => {
12550 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12551 s.select_ranges([last.range()]);
12552 });
12553 }
12554 _ => self.select_previous(
12555 &SelectPrevious {
12556 replace_newest: true,
12557 },
12558 window,
12559 cx,
12560 )?,
12561 }
12562 Ok(())
12563 }
12564
12565 pub fn toggle_comments(
12566 &mut self,
12567 action: &ToggleComments,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 ) {
12571 if self.read_only(cx) {
12572 return;
12573 }
12574 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12575 let text_layout_details = &self.text_layout_details(window);
12576 self.transact(window, cx, |this, window, cx| {
12577 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12578 let mut edits = Vec::new();
12579 let mut selection_edit_ranges = Vec::new();
12580 let mut last_toggled_row = None;
12581 let snapshot = this.buffer.read(cx).read(cx);
12582 let empty_str: Arc<str> = Arc::default();
12583 let mut suffixes_inserted = Vec::new();
12584 let ignore_indent = action.ignore_indent;
12585
12586 fn comment_prefix_range(
12587 snapshot: &MultiBufferSnapshot,
12588 row: MultiBufferRow,
12589 comment_prefix: &str,
12590 comment_prefix_whitespace: &str,
12591 ignore_indent: bool,
12592 ) -> Range<Point> {
12593 let indent_size = if ignore_indent {
12594 0
12595 } else {
12596 snapshot.indent_size_for_line(row).len
12597 };
12598
12599 let start = Point::new(row.0, indent_size);
12600
12601 let mut line_bytes = snapshot
12602 .bytes_in_range(start..snapshot.max_point())
12603 .flatten()
12604 .copied();
12605
12606 // If this line currently begins with the line comment prefix, then record
12607 // the range containing the prefix.
12608 if line_bytes
12609 .by_ref()
12610 .take(comment_prefix.len())
12611 .eq(comment_prefix.bytes())
12612 {
12613 // Include any whitespace that matches the comment prefix.
12614 let matching_whitespace_len = line_bytes
12615 .zip(comment_prefix_whitespace.bytes())
12616 .take_while(|(a, b)| a == b)
12617 .count() as u32;
12618 let end = Point::new(
12619 start.row,
12620 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12621 );
12622 start..end
12623 } else {
12624 start..start
12625 }
12626 }
12627
12628 fn comment_suffix_range(
12629 snapshot: &MultiBufferSnapshot,
12630 row: MultiBufferRow,
12631 comment_suffix: &str,
12632 comment_suffix_has_leading_space: bool,
12633 ) -> Range<Point> {
12634 let end = Point::new(row.0, snapshot.line_len(row));
12635 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12636
12637 let mut line_end_bytes = snapshot
12638 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12639 .flatten()
12640 .copied();
12641
12642 let leading_space_len = if suffix_start_column > 0
12643 && line_end_bytes.next() == Some(b' ')
12644 && comment_suffix_has_leading_space
12645 {
12646 1
12647 } else {
12648 0
12649 };
12650
12651 // If this line currently begins with the line comment prefix, then record
12652 // the range containing the prefix.
12653 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12654 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12655 start..end
12656 } else {
12657 end..end
12658 }
12659 }
12660
12661 // TODO: Handle selections that cross excerpts
12662 for selection in &mut selections {
12663 let start_column = snapshot
12664 .indent_size_for_line(MultiBufferRow(selection.start.row))
12665 .len;
12666 let language = if let Some(language) =
12667 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12668 {
12669 language
12670 } else {
12671 continue;
12672 };
12673
12674 selection_edit_ranges.clear();
12675
12676 // If multiple selections contain a given row, avoid processing that
12677 // row more than once.
12678 let mut start_row = MultiBufferRow(selection.start.row);
12679 if last_toggled_row == Some(start_row) {
12680 start_row = start_row.next_row();
12681 }
12682 let end_row =
12683 if selection.end.row > selection.start.row && selection.end.column == 0 {
12684 MultiBufferRow(selection.end.row - 1)
12685 } else {
12686 MultiBufferRow(selection.end.row)
12687 };
12688 last_toggled_row = Some(end_row);
12689
12690 if start_row > end_row {
12691 continue;
12692 }
12693
12694 // If the language has line comments, toggle those.
12695 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12696
12697 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12698 if ignore_indent {
12699 full_comment_prefixes = full_comment_prefixes
12700 .into_iter()
12701 .map(|s| Arc::from(s.trim_end()))
12702 .collect();
12703 }
12704
12705 if !full_comment_prefixes.is_empty() {
12706 let first_prefix = full_comment_prefixes
12707 .first()
12708 .expect("prefixes is non-empty");
12709 let prefix_trimmed_lengths = full_comment_prefixes
12710 .iter()
12711 .map(|p| p.trim_end_matches(' ').len())
12712 .collect::<SmallVec<[usize; 4]>>();
12713
12714 let mut all_selection_lines_are_comments = true;
12715
12716 for row in start_row.0..=end_row.0 {
12717 let row = MultiBufferRow(row);
12718 if start_row < end_row && snapshot.is_line_blank(row) {
12719 continue;
12720 }
12721
12722 let prefix_range = full_comment_prefixes
12723 .iter()
12724 .zip(prefix_trimmed_lengths.iter().copied())
12725 .map(|(prefix, trimmed_prefix_len)| {
12726 comment_prefix_range(
12727 snapshot.deref(),
12728 row,
12729 &prefix[..trimmed_prefix_len],
12730 &prefix[trimmed_prefix_len..],
12731 ignore_indent,
12732 )
12733 })
12734 .max_by_key(|range| range.end.column - range.start.column)
12735 .expect("prefixes is non-empty");
12736
12737 if prefix_range.is_empty() {
12738 all_selection_lines_are_comments = false;
12739 }
12740
12741 selection_edit_ranges.push(prefix_range);
12742 }
12743
12744 if all_selection_lines_are_comments {
12745 edits.extend(
12746 selection_edit_ranges
12747 .iter()
12748 .cloned()
12749 .map(|range| (range, empty_str.clone())),
12750 );
12751 } else {
12752 let min_column = selection_edit_ranges
12753 .iter()
12754 .map(|range| range.start.column)
12755 .min()
12756 .unwrap_or(0);
12757 edits.extend(selection_edit_ranges.iter().map(|range| {
12758 let position = Point::new(range.start.row, min_column);
12759 (position..position, first_prefix.clone())
12760 }));
12761 }
12762 } else if let Some((full_comment_prefix, comment_suffix)) =
12763 language.block_comment_delimiters()
12764 {
12765 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12766 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12767 let prefix_range = comment_prefix_range(
12768 snapshot.deref(),
12769 start_row,
12770 comment_prefix,
12771 comment_prefix_whitespace,
12772 ignore_indent,
12773 );
12774 let suffix_range = comment_suffix_range(
12775 snapshot.deref(),
12776 end_row,
12777 comment_suffix.trim_start_matches(' '),
12778 comment_suffix.starts_with(' '),
12779 );
12780
12781 if prefix_range.is_empty() || suffix_range.is_empty() {
12782 edits.push((
12783 prefix_range.start..prefix_range.start,
12784 full_comment_prefix.clone(),
12785 ));
12786 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12787 suffixes_inserted.push((end_row, comment_suffix.len()));
12788 } else {
12789 edits.push((prefix_range, empty_str.clone()));
12790 edits.push((suffix_range, empty_str.clone()));
12791 }
12792 } else {
12793 continue;
12794 }
12795 }
12796
12797 drop(snapshot);
12798 this.buffer.update(cx, |buffer, cx| {
12799 buffer.edit(edits, None, cx);
12800 });
12801
12802 // Adjust selections so that they end before any comment suffixes that
12803 // were inserted.
12804 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12805 let mut selections = this.selections.all::<Point>(cx);
12806 let snapshot = this.buffer.read(cx).read(cx);
12807 for selection in &mut selections {
12808 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12809 match row.cmp(&MultiBufferRow(selection.end.row)) {
12810 Ordering::Less => {
12811 suffixes_inserted.next();
12812 continue;
12813 }
12814 Ordering::Greater => break,
12815 Ordering::Equal => {
12816 if selection.end.column == snapshot.line_len(row) {
12817 if selection.is_empty() {
12818 selection.start.column -= suffix_len as u32;
12819 }
12820 selection.end.column -= suffix_len as u32;
12821 }
12822 break;
12823 }
12824 }
12825 }
12826 }
12827
12828 drop(snapshot);
12829 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12830 s.select(selections)
12831 });
12832
12833 let selections = this.selections.all::<Point>(cx);
12834 let selections_on_single_row = selections.windows(2).all(|selections| {
12835 selections[0].start.row == selections[1].start.row
12836 && selections[0].end.row == selections[1].end.row
12837 && selections[0].start.row == selections[0].end.row
12838 });
12839 let selections_selecting = selections
12840 .iter()
12841 .any(|selection| selection.start != selection.end);
12842 let advance_downwards = action.advance_downwards
12843 && selections_on_single_row
12844 && !selections_selecting
12845 && !matches!(this.mode, EditorMode::SingleLine { .. });
12846
12847 if advance_downwards {
12848 let snapshot = this.buffer.read(cx).snapshot(cx);
12849
12850 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12851 s.move_cursors_with(|display_snapshot, display_point, _| {
12852 let mut point = display_point.to_point(display_snapshot);
12853 point.row += 1;
12854 point = snapshot.clip_point(point, Bias::Left);
12855 let display_point = point.to_display_point(display_snapshot);
12856 let goal = SelectionGoal::HorizontalPosition(
12857 display_snapshot
12858 .x_for_display_point(display_point, text_layout_details)
12859 .into(),
12860 );
12861 (display_point, goal)
12862 })
12863 });
12864 }
12865 });
12866 }
12867
12868 pub fn select_enclosing_symbol(
12869 &mut self,
12870 _: &SelectEnclosingSymbol,
12871 window: &mut Window,
12872 cx: &mut Context<Self>,
12873 ) {
12874 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12875
12876 let buffer = self.buffer.read(cx).snapshot(cx);
12877 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12878
12879 fn update_selection(
12880 selection: &Selection<usize>,
12881 buffer_snap: &MultiBufferSnapshot,
12882 ) -> Option<Selection<usize>> {
12883 let cursor = selection.head();
12884 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12885 for symbol in symbols.iter().rev() {
12886 let start = symbol.range.start.to_offset(buffer_snap);
12887 let end = symbol.range.end.to_offset(buffer_snap);
12888 let new_range = start..end;
12889 if start < selection.start || end > selection.end {
12890 return Some(Selection {
12891 id: selection.id,
12892 start: new_range.start,
12893 end: new_range.end,
12894 goal: SelectionGoal::None,
12895 reversed: selection.reversed,
12896 });
12897 }
12898 }
12899 None
12900 }
12901
12902 let mut selected_larger_symbol = false;
12903 let new_selections = old_selections
12904 .iter()
12905 .map(|selection| match update_selection(selection, &buffer) {
12906 Some(new_selection) => {
12907 if new_selection.range() != selection.range() {
12908 selected_larger_symbol = true;
12909 }
12910 new_selection
12911 }
12912 None => selection.clone(),
12913 })
12914 .collect::<Vec<_>>();
12915
12916 if selected_larger_symbol {
12917 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12918 s.select(new_selections);
12919 });
12920 }
12921 }
12922
12923 pub fn select_larger_syntax_node(
12924 &mut self,
12925 _: &SelectLargerSyntaxNode,
12926 window: &mut Window,
12927 cx: &mut Context<Self>,
12928 ) {
12929 let Some(visible_row_count) = self.visible_row_count() else {
12930 return;
12931 };
12932 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12933 if old_selections.is_empty() {
12934 return;
12935 }
12936
12937 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12938
12939 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12940 let buffer = self.buffer.read(cx).snapshot(cx);
12941
12942 let mut selected_larger_node = false;
12943 let mut new_selections = old_selections
12944 .iter()
12945 .map(|selection| {
12946 let old_range = selection.start..selection.end;
12947
12948 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12949 // manually select word at selection
12950 if ["string_content", "inline"].contains(&node.kind()) {
12951 let word_range = {
12952 let display_point = buffer
12953 .offset_to_point(old_range.start)
12954 .to_display_point(&display_map);
12955 let Range { start, end } =
12956 movement::surrounding_word(&display_map, display_point);
12957 start.to_point(&display_map).to_offset(&buffer)
12958 ..end.to_point(&display_map).to_offset(&buffer)
12959 };
12960 // ignore if word is already selected
12961 if !word_range.is_empty() && old_range != word_range {
12962 let last_word_range = {
12963 let display_point = buffer
12964 .offset_to_point(old_range.end)
12965 .to_display_point(&display_map);
12966 let Range { start, end } =
12967 movement::surrounding_word(&display_map, display_point);
12968 start.to_point(&display_map).to_offset(&buffer)
12969 ..end.to_point(&display_map).to_offset(&buffer)
12970 };
12971 // only select word if start and end point belongs to same word
12972 if word_range == last_word_range {
12973 selected_larger_node = true;
12974 return Selection {
12975 id: selection.id,
12976 start: word_range.start,
12977 end: word_range.end,
12978 goal: SelectionGoal::None,
12979 reversed: selection.reversed,
12980 };
12981 }
12982 }
12983 }
12984 }
12985
12986 let mut new_range = old_range.clone();
12987 let mut new_node = None;
12988 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12989 {
12990 new_node = Some(node);
12991 new_range = match containing_range {
12992 MultiOrSingleBufferOffsetRange::Single(_) => break,
12993 MultiOrSingleBufferOffsetRange::Multi(range) => range,
12994 };
12995 if !display_map.intersects_fold(new_range.start)
12996 && !display_map.intersects_fold(new_range.end)
12997 {
12998 break;
12999 }
13000 }
13001
13002 if let Some(node) = new_node {
13003 // Log the ancestor, to support using this action as a way to explore TreeSitter
13004 // nodes. Parent and grandparent are also logged because this operation will not
13005 // visit nodes that have the same range as their parent.
13006 log::info!("Node: {node:?}");
13007 let parent = node.parent();
13008 log::info!("Parent: {parent:?}");
13009 let grandparent = parent.and_then(|x| x.parent());
13010 log::info!("Grandparent: {grandparent:?}");
13011 }
13012
13013 selected_larger_node |= new_range != old_range;
13014 Selection {
13015 id: selection.id,
13016 start: new_range.start,
13017 end: new_range.end,
13018 goal: SelectionGoal::None,
13019 reversed: selection.reversed,
13020 }
13021 })
13022 .collect::<Vec<_>>();
13023
13024 if !selected_larger_node {
13025 return; // don't put this call in the history
13026 }
13027
13028 // scroll based on transformation done to the last selection created by the user
13029 let (last_old, last_new) = old_selections
13030 .last()
13031 .zip(new_selections.last().cloned())
13032 .expect("old_selections isn't empty");
13033
13034 // revert selection
13035 let is_selection_reversed = {
13036 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13037 new_selections.last_mut().expect("checked above").reversed =
13038 should_newest_selection_be_reversed;
13039 should_newest_selection_be_reversed
13040 };
13041
13042 if selected_larger_node {
13043 self.select_syntax_node_history.disable_clearing = true;
13044 self.change_selections(None, window, cx, |s| {
13045 s.select(new_selections.clone());
13046 });
13047 self.select_syntax_node_history.disable_clearing = false;
13048 }
13049
13050 let start_row = last_new.start.to_display_point(&display_map).row().0;
13051 let end_row = last_new.end.to_display_point(&display_map).row().0;
13052 let selection_height = end_row - start_row + 1;
13053 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13054
13055 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13056 let scroll_behavior = if fits_on_the_screen {
13057 self.request_autoscroll(Autoscroll::fit(), cx);
13058 SelectSyntaxNodeScrollBehavior::FitSelection
13059 } else if is_selection_reversed {
13060 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13061 SelectSyntaxNodeScrollBehavior::CursorTop
13062 } else {
13063 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13064 SelectSyntaxNodeScrollBehavior::CursorBottom
13065 };
13066
13067 self.select_syntax_node_history.push((
13068 old_selections,
13069 scroll_behavior,
13070 is_selection_reversed,
13071 ));
13072 }
13073
13074 pub fn select_smaller_syntax_node(
13075 &mut self,
13076 _: &SelectSmallerSyntaxNode,
13077 window: &mut Window,
13078 cx: &mut Context<Self>,
13079 ) {
13080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13081
13082 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13083 self.select_syntax_node_history.pop()
13084 {
13085 if let Some(selection) = selections.last_mut() {
13086 selection.reversed = is_selection_reversed;
13087 }
13088
13089 self.select_syntax_node_history.disable_clearing = true;
13090 self.change_selections(None, window, cx, |s| {
13091 s.select(selections.to_vec());
13092 });
13093 self.select_syntax_node_history.disable_clearing = false;
13094
13095 match scroll_behavior {
13096 SelectSyntaxNodeScrollBehavior::CursorTop => {
13097 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13098 }
13099 SelectSyntaxNodeScrollBehavior::FitSelection => {
13100 self.request_autoscroll(Autoscroll::fit(), cx);
13101 }
13102 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13103 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13104 }
13105 }
13106 }
13107 }
13108
13109 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13110 if !EditorSettings::get_global(cx).gutter.runnables {
13111 self.clear_tasks();
13112 return Task::ready(());
13113 }
13114 let project = self.project.as_ref().map(Entity::downgrade);
13115 let task_sources = self.lsp_task_sources(cx);
13116 cx.spawn_in(window, async move |editor, cx| {
13117 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13118 let Some(project) = project.and_then(|p| p.upgrade()) else {
13119 return;
13120 };
13121 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13122 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13123 }) else {
13124 return;
13125 };
13126
13127 let hide_runnables = project
13128 .update(cx, |project, cx| {
13129 // Do not display any test indicators in non-dev server remote projects.
13130 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13131 })
13132 .unwrap_or(true);
13133 if hide_runnables {
13134 return;
13135 }
13136 let new_rows =
13137 cx.background_spawn({
13138 let snapshot = display_snapshot.clone();
13139 async move {
13140 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13141 }
13142 })
13143 .await;
13144 let Ok(lsp_tasks) =
13145 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13146 else {
13147 return;
13148 };
13149 let lsp_tasks = lsp_tasks.await;
13150
13151 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13152 lsp_tasks
13153 .into_iter()
13154 .flat_map(|(kind, tasks)| {
13155 tasks.into_iter().filter_map(move |(location, task)| {
13156 Some((kind.clone(), location?, task))
13157 })
13158 })
13159 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13160 let buffer = location.target.buffer;
13161 let buffer_snapshot = buffer.read(cx).snapshot();
13162 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13163 |(excerpt_id, snapshot, _)| {
13164 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13165 display_snapshot
13166 .buffer_snapshot
13167 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13168 } else {
13169 None
13170 }
13171 },
13172 );
13173 if let Some(offset) = offset {
13174 let task_buffer_range =
13175 location.target.range.to_point(&buffer_snapshot);
13176 let context_buffer_range =
13177 task_buffer_range.to_offset(&buffer_snapshot);
13178 let context_range = BufferOffset(context_buffer_range.start)
13179 ..BufferOffset(context_buffer_range.end);
13180
13181 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13182 .or_insert_with(|| RunnableTasks {
13183 templates: Vec::new(),
13184 offset,
13185 column: task_buffer_range.start.column,
13186 extra_variables: HashMap::default(),
13187 context_range,
13188 })
13189 .templates
13190 .push((kind, task.original_task().clone()));
13191 }
13192
13193 acc
13194 })
13195 }) else {
13196 return;
13197 };
13198
13199 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13200 editor
13201 .update(cx, |editor, _| {
13202 editor.clear_tasks();
13203 for (key, mut value) in rows {
13204 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13205 value.templates.extend(lsp_tasks.templates);
13206 }
13207
13208 editor.insert_tasks(key, value);
13209 }
13210 for (key, value) in lsp_tasks_by_rows {
13211 editor.insert_tasks(key, value);
13212 }
13213 })
13214 .ok();
13215 })
13216 }
13217 fn fetch_runnable_ranges(
13218 snapshot: &DisplaySnapshot,
13219 range: Range<Anchor>,
13220 ) -> Vec<language::RunnableRange> {
13221 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13222 }
13223
13224 fn runnable_rows(
13225 project: Entity<Project>,
13226 snapshot: DisplaySnapshot,
13227 runnable_ranges: Vec<RunnableRange>,
13228 mut cx: AsyncWindowContext,
13229 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13230 runnable_ranges
13231 .into_iter()
13232 .filter_map(|mut runnable| {
13233 let tasks = cx
13234 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13235 .ok()?;
13236 if tasks.is_empty() {
13237 return None;
13238 }
13239
13240 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13241
13242 let row = snapshot
13243 .buffer_snapshot
13244 .buffer_line_for_row(MultiBufferRow(point.row))?
13245 .1
13246 .start
13247 .row;
13248
13249 let context_range =
13250 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13251 Some((
13252 (runnable.buffer_id, row),
13253 RunnableTasks {
13254 templates: tasks,
13255 offset: snapshot
13256 .buffer_snapshot
13257 .anchor_before(runnable.run_range.start),
13258 context_range,
13259 column: point.column,
13260 extra_variables: runnable.extra_captures,
13261 },
13262 ))
13263 })
13264 .collect()
13265 }
13266
13267 fn templates_with_tags(
13268 project: &Entity<Project>,
13269 runnable: &mut Runnable,
13270 cx: &mut App,
13271 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13272 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13273 let (worktree_id, file) = project
13274 .buffer_for_id(runnable.buffer, cx)
13275 .and_then(|buffer| buffer.read(cx).file())
13276 .map(|file| (file.worktree_id(cx), file.clone()))
13277 .unzip();
13278
13279 (
13280 project.task_store().read(cx).task_inventory().cloned(),
13281 worktree_id,
13282 file,
13283 )
13284 });
13285
13286 let mut templates_with_tags = mem::take(&mut runnable.tags)
13287 .into_iter()
13288 .flat_map(|RunnableTag(tag)| {
13289 inventory
13290 .as_ref()
13291 .into_iter()
13292 .flat_map(|inventory| {
13293 inventory.read(cx).list_tasks(
13294 file.clone(),
13295 Some(runnable.language.clone()),
13296 worktree_id,
13297 cx,
13298 )
13299 })
13300 .filter(move |(_, template)| {
13301 template.tags.iter().any(|source_tag| source_tag == &tag)
13302 })
13303 })
13304 .sorted_by_key(|(kind, _)| kind.to_owned())
13305 .collect::<Vec<_>>();
13306 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13307 // Strongest source wins; if we have worktree tag binding, prefer that to
13308 // global and language bindings;
13309 // if we have a global binding, prefer that to language binding.
13310 let first_mismatch = templates_with_tags
13311 .iter()
13312 .position(|(tag_source, _)| tag_source != leading_tag_source);
13313 if let Some(index) = first_mismatch {
13314 templates_with_tags.truncate(index);
13315 }
13316 }
13317
13318 templates_with_tags
13319 }
13320
13321 pub fn move_to_enclosing_bracket(
13322 &mut self,
13323 _: &MoveToEnclosingBracket,
13324 window: &mut Window,
13325 cx: &mut Context<Self>,
13326 ) {
13327 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13328 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13329 s.move_offsets_with(|snapshot, selection| {
13330 let Some(enclosing_bracket_ranges) =
13331 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13332 else {
13333 return;
13334 };
13335
13336 let mut best_length = usize::MAX;
13337 let mut best_inside = false;
13338 let mut best_in_bracket_range = false;
13339 let mut best_destination = None;
13340 for (open, close) in enclosing_bracket_ranges {
13341 let close = close.to_inclusive();
13342 let length = close.end() - open.start;
13343 let inside = selection.start >= open.end && selection.end <= *close.start();
13344 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13345 || close.contains(&selection.head());
13346
13347 // If best is next to a bracket and current isn't, skip
13348 if !in_bracket_range && best_in_bracket_range {
13349 continue;
13350 }
13351
13352 // Prefer smaller lengths unless best is inside and current isn't
13353 if length > best_length && (best_inside || !inside) {
13354 continue;
13355 }
13356
13357 best_length = length;
13358 best_inside = inside;
13359 best_in_bracket_range = in_bracket_range;
13360 best_destination = Some(
13361 if close.contains(&selection.start) && close.contains(&selection.end) {
13362 if inside { open.end } else { open.start }
13363 } else if inside {
13364 *close.start()
13365 } else {
13366 *close.end()
13367 },
13368 );
13369 }
13370
13371 if let Some(destination) = best_destination {
13372 selection.collapse_to(destination, SelectionGoal::None);
13373 }
13374 })
13375 });
13376 }
13377
13378 pub fn undo_selection(
13379 &mut self,
13380 _: &UndoSelection,
13381 window: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) {
13384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13385 self.end_selection(window, cx);
13386 self.selection_history.mode = SelectionHistoryMode::Undoing;
13387 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13388 self.change_selections(None, window, cx, |s| {
13389 s.select_anchors(entry.selections.to_vec())
13390 });
13391 self.select_next_state = entry.select_next_state;
13392 self.select_prev_state = entry.select_prev_state;
13393 self.add_selections_state = entry.add_selections_state;
13394 self.request_autoscroll(Autoscroll::newest(), cx);
13395 }
13396 self.selection_history.mode = SelectionHistoryMode::Normal;
13397 }
13398
13399 pub fn redo_selection(
13400 &mut self,
13401 _: &RedoSelection,
13402 window: &mut Window,
13403 cx: &mut Context<Self>,
13404 ) {
13405 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13406 self.end_selection(window, cx);
13407 self.selection_history.mode = SelectionHistoryMode::Redoing;
13408 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13409 self.change_selections(None, window, cx, |s| {
13410 s.select_anchors(entry.selections.to_vec())
13411 });
13412 self.select_next_state = entry.select_next_state;
13413 self.select_prev_state = entry.select_prev_state;
13414 self.add_selections_state = entry.add_selections_state;
13415 self.request_autoscroll(Autoscroll::newest(), cx);
13416 }
13417 self.selection_history.mode = SelectionHistoryMode::Normal;
13418 }
13419
13420 pub fn expand_excerpts(
13421 &mut self,
13422 action: &ExpandExcerpts,
13423 _: &mut Window,
13424 cx: &mut Context<Self>,
13425 ) {
13426 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13427 }
13428
13429 pub fn expand_excerpts_down(
13430 &mut self,
13431 action: &ExpandExcerptsDown,
13432 _: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13436 }
13437
13438 pub fn expand_excerpts_up(
13439 &mut self,
13440 action: &ExpandExcerptsUp,
13441 _: &mut Window,
13442 cx: &mut Context<Self>,
13443 ) {
13444 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13445 }
13446
13447 pub fn expand_excerpts_for_direction(
13448 &mut self,
13449 lines: u32,
13450 direction: ExpandExcerptDirection,
13451
13452 cx: &mut Context<Self>,
13453 ) {
13454 let selections = self.selections.disjoint_anchors();
13455
13456 let lines = if lines == 0 {
13457 EditorSettings::get_global(cx).expand_excerpt_lines
13458 } else {
13459 lines
13460 };
13461
13462 self.buffer.update(cx, |buffer, cx| {
13463 let snapshot = buffer.snapshot(cx);
13464 let mut excerpt_ids = selections
13465 .iter()
13466 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13467 .collect::<Vec<_>>();
13468 excerpt_ids.sort();
13469 excerpt_ids.dedup();
13470 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13471 })
13472 }
13473
13474 pub fn expand_excerpt(
13475 &mut self,
13476 excerpt: ExcerptId,
13477 direction: ExpandExcerptDirection,
13478 window: &mut Window,
13479 cx: &mut Context<Self>,
13480 ) {
13481 let current_scroll_position = self.scroll_position(cx);
13482 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13483 let mut should_scroll_up = false;
13484
13485 if direction == ExpandExcerptDirection::Down {
13486 let multi_buffer = self.buffer.read(cx);
13487 let snapshot = multi_buffer.snapshot(cx);
13488 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13489 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13490 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13491 let buffer_snapshot = buffer.read(cx).snapshot();
13492 let excerpt_end_row =
13493 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13494 let last_row = buffer_snapshot.max_point().row;
13495 let lines_below = last_row.saturating_sub(excerpt_end_row);
13496 should_scroll_up = lines_below >= lines_to_expand;
13497 }
13498 }
13499 }
13500 }
13501
13502 self.buffer.update(cx, |buffer, cx| {
13503 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13504 });
13505
13506 if should_scroll_up {
13507 let new_scroll_position =
13508 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13509 self.set_scroll_position(new_scroll_position, window, cx);
13510 }
13511 }
13512
13513 pub fn go_to_singleton_buffer_point(
13514 &mut self,
13515 point: Point,
13516 window: &mut Window,
13517 cx: &mut Context<Self>,
13518 ) {
13519 self.go_to_singleton_buffer_range(point..point, window, cx);
13520 }
13521
13522 pub fn go_to_singleton_buffer_range(
13523 &mut self,
13524 range: Range<Point>,
13525 window: &mut Window,
13526 cx: &mut Context<Self>,
13527 ) {
13528 let multibuffer = self.buffer().read(cx);
13529 let Some(buffer) = multibuffer.as_singleton() else {
13530 return;
13531 };
13532 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13533 return;
13534 };
13535 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13536 return;
13537 };
13538 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13539 s.select_anchor_ranges([start..end])
13540 });
13541 }
13542
13543 pub fn go_to_diagnostic(
13544 &mut self,
13545 _: &GoToDiagnostic,
13546 window: &mut Window,
13547 cx: &mut Context<Self>,
13548 ) {
13549 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13550 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13551 }
13552
13553 pub fn go_to_prev_diagnostic(
13554 &mut self,
13555 _: &GoToPreviousDiagnostic,
13556 window: &mut Window,
13557 cx: &mut Context<Self>,
13558 ) {
13559 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13560 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13561 }
13562
13563 pub fn go_to_diagnostic_impl(
13564 &mut self,
13565 direction: Direction,
13566 window: &mut Window,
13567 cx: &mut Context<Self>,
13568 ) {
13569 let buffer = self.buffer.read(cx).snapshot(cx);
13570 let selection = self.selections.newest::<usize>(cx);
13571
13572 let mut active_group_id = None;
13573 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13574 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13575 active_group_id = Some(active_group.group_id);
13576 }
13577 }
13578
13579 fn filtered(
13580 snapshot: EditorSnapshot,
13581 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13582 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13583 diagnostics
13584 .filter(|entry| entry.range.start != entry.range.end)
13585 .filter(|entry| !entry.diagnostic.is_unnecessary)
13586 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13587 }
13588
13589 let snapshot = self.snapshot(window, cx);
13590 let before = filtered(
13591 snapshot.clone(),
13592 buffer
13593 .diagnostics_in_range(0..selection.start)
13594 .filter(|entry| entry.range.start <= selection.start),
13595 );
13596 let after = filtered(
13597 snapshot,
13598 buffer
13599 .diagnostics_in_range(selection.start..buffer.len())
13600 .filter(|entry| entry.range.start >= selection.start),
13601 );
13602
13603 let mut found: Option<DiagnosticEntry<usize>> = None;
13604 if direction == Direction::Prev {
13605 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13606 {
13607 for diagnostic in prev_diagnostics.into_iter().rev() {
13608 if diagnostic.range.start != selection.start
13609 || active_group_id
13610 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13611 {
13612 found = Some(diagnostic);
13613 break 'outer;
13614 }
13615 }
13616 }
13617 } else {
13618 for diagnostic in after.chain(before) {
13619 if diagnostic.range.start != selection.start
13620 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13621 {
13622 found = Some(diagnostic);
13623 break;
13624 }
13625 }
13626 }
13627 let Some(next_diagnostic) = found else {
13628 return;
13629 };
13630
13631 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13632 return;
13633 };
13634 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13635 s.select_ranges(vec![
13636 next_diagnostic.range.start..next_diagnostic.range.start,
13637 ])
13638 });
13639 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13640 self.refresh_inline_completion(false, true, window, cx);
13641 }
13642
13643 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13644 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13645 let snapshot = self.snapshot(window, cx);
13646 let selection = self.selections.newest::<Point>(cx);
13647 self.go_to_hunk_before_or_after_position(
13648 &snapshot,
13649 selection.head(),
13650 Direction::Next,
13651 window,
13652 cx,
13653 );
13654 }
13655
13656 pub fn go_to_hunk_before_or_after_position(
13657 &mut self,
13658 snapshot: &EditorSnapshot,
13659 position: Point,
13660 direction: Direction,
13661 window: &mut Window,
13662 cx: &mut Context<Editor>,
13663 ) {
13664 let row = if direction == Direction::Next {
13665 self.hunk_after_position(snapshot, position)
13666 .map(|hunk| hunk.row_range.start)
13667 } else {
13668 self.hunk_before_position(snapshot, position)
13669 };
13670
13671 if let Some(row) = row {
13672 let destination = Point::new(row.0, 0);
13673 let autoscroll = Autoscroll::center();
13674
13675 self.unfold_ranges(&[destination..destination], false, false, cx);
13676 self.change_selections(Some(autoscroll), window, cx, |s| {
13677 s.select_ranges([destination..destination]);
13678 });
13679 }
13680 }
13681
13682 fn hunk_after_position(
13683 &mut self,
13684 snapshot: &EditorSnapshot,
13685 position: Point,
13686 ) -> Option<MultiBufferDiffHunk> {
13687 snapshot
13688 .buffer_snapshot
13689 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13690 .find(|hunk| hunk.row_range.start.0 > position.row)
13691 .or_else(|| {
13692 snapshot
13693 .buffer_snapshot
13694 .diff_hunks_in_range(Point::zero()..position)
13695 .find(|hunk| hunk.row_range.end.0 < position.row)
13696 })
13697 }
13698
13699 fn go_to_prev_hunk(
13700 &mut self,
13701 _: &GoToPreviousHunk,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13706 let snapshot = self.snapshot(window, cx);
13707 let selection = self.selections.newest::<Point>(cx);
13708 self.go_to_hunk_before_or_after_position(
13709 &snapshot,
13710 selection.head(),
13711 Direction::Prev,
13712 window,
13713 cx,
13714 );
13715 }
13716
13717 fn hunk_before_position(
13718 &mut self,
13719 snapshot: &EditorSnapshot,
13720 position: Point,
13721 ) -> Option<MultiBufferRow> {
13722 snapshot
13723 .buffer_snapshot
13724 .diff_hunk_before(position)
13725 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13726 }
13727
13728 fn go_to_next_change(
13729 &mut self,
13730 _: &GoToNextChange,
13731 window: &mut Window,
13732 cx: &mut Context<Self>,
13733 ) {
13734 if let Some(selections) = self
13735 .change_list
13736 .next_change(1, Direction::Next)
13737 .map(|s| s.to_vec())
13738 {
13739 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13740 let map = s.display_map();
13741 s.select_display_ranges(selections.iter().map(|a| {
13742 let point = a.to_display_point(&map);
13743 point..point
13744 }))
13745 })
13746 }
13747 }
13748
13749 fn go_to_previous_change(
13750 &mut self,
13751 _: &GoToPreviousChange,
13752 window: &mut Window,
13753 cx: &mut Context<Self>,
13754 ) {
13755 if let Some(selections) = self
13756 .change_list
13757 .next_change(1, Direction::Prev)
13758 .map(|s| s.to_vec())
13759 {
13760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13761 let map = s.display_map();
13762 s.select_display_ranges(selections.iter().map(|a| {
13763 let point = a.to_display_point(&map);
13764 point..point
13765 }))
13766 })
13767 }
13768 }
13769
13770 fn go_to_line<T: 'static>(
13771 &mut self,
13772 position: Anchor,
13773 highlight_color: Option<Hsla>,
13774 window: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 let snapshot = self.snapshot(window, cx).display_snapshot;
13778 let position = position.to_point(&snapshot.buffer_snapshot);
13779 let start = snapshot
13780 .buffer_snapshot
13781 .clip_point(Point::new(position.row, 0), Bias::Left);
13782 let end = start + Point::new(1, 0);
13783 let start = snapshot.buffer_snapshot.anchor_before(start);
13784 let end = snapshot.buffer_snapshot.anchor_before(end);
13785
13786 self.highlight_rows::<T>(
13787 start..end,
13788 highlight_color
13789 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13790 Default::default(),
13791 cx,
13792 );
13793 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13794 }
13795
13796 pub fn go_to_definition(
13797 &mut self,
13798 _: &GoToDefinition,
13799 window: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) -> Task<Result<Navigated>> {
13802 let definition =
13803 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13804 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13805 cx.spawn_in(window, async move |editor, cx| {
13806 if definition.await? == Navigated::Yes {
13807 return Ok(Navigated::Yes);
13808 }
13809 match fallback_strategy {
13810 GoToDefinitionFallback::None => Ok(Navigated::No),
13811 GoToDefinitionFallback::FindAllReferences => {
13812 match editor.update_in(cx, |editor, window, cx| {
13813 editor.find_all_references(&FindAllReferences, window, cx)
13814 })? {
13815 Some(references) => references.await,
13816 None => Ok(Navigated::No),
13817 }
13818 }
13819 }
13820 })
13821 }
13822
13823 pub fn go_to_declaration(
13824 &mut self,
13825 _: &GoToDeclaration,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) -> Task<Result<Navigated>> {
13829 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
13830 }
13831
13832 pub fn go_to_declaration_split(
13833 &mut self,
13834 _: &GoToDeclaration,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) -> Task<Result<Navigated>> {
13838 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13839 }
13840
13841 pub fn go_to_implementation(
13842 &mut self,
13843 _: &GoToImplementation,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) -> Task<Result<Navigated>> {
13847 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13848 }
13849
13850 pub fn go_to_implementation_split(
13851 &mut self,
13852 _: &GoToImplementationSplit,
13853 window: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) -> Task<Result<Navigated>> {
13856 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13857 }
13858
13859 pub fn go_to_type_definition(
13860 &mut self,
13861 _: &GoToTypeDefinition,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) -> Task<Result<Navigated>> {
13865 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13866 }
13867
13868 pub fn go_to_definition_split(
13869 &mut self,
13870 _: &GoToDefinitionSplit,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) -> Task<Result<Navigated>> {
13874 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13875 }
13876
13877 pub fn go_to_type_definition_split(
13878 &mut self,
13879 _: &GoToTypeDefinitionSplit,
13880 window: &mut Window,
13881 cx: &mut Context<Self>,
13882 ) -> Task<Result<Navigated>> {
13883 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13884 }
13885
13886 fn go_to_definition_of_kind(
13887 &mut self,
13888 kind: GotoDefinitionKind,
13889 split: bool,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) -> Task<Result<Navigated>> {
13893 let Some(provider) = self.semantics_provider.clone() else {
13894 return Task::ready(Ok(Navigated::No));
13895 };
13896 let head = self.selections.newest::<usize>(cx).head();
13897 let buffer = self.buffer.read(cx);
13898 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13899 text_anchor
13900 } else {
13901 return Task::ready(Ok(Navigated::No));
13902 };
13903
13904 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13905 return Task::ready(Ok(Navigated::No));
13906 };
13907
13908 cx.spawn_in(window, async move |editor, cx| {
13909 let definitions = definitions.await?;
13910 let navigated = editor
13911 .update_in(cx, |editor, window, cx| {
13912 editor.navigate_to_hover_links(
13913 Some(kind),
13914 definitions
13915 .into_iter()
13916 .filter(|location| {
13917 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13918 })
13919 .map(HoverLink::Text)
13920 .collect::<Vec<_>>(),
13921 split,
13922 window,
13923 cx,
13924 )
13925 })?
13926 .await?;
13927 anyhow::Ok(navigated)
13928 })
13929 }
13930
13931 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13932 let selection = self.selections.newest_anchor();
13933 let head = selection.head();
13934 let tail = selection.tail();
13935
13936 let Some((buffer, start_position)) =
13937 self.buffer.read(cx).text_anchor_for_position(head, cx)
13938 else {
13939 return;
13940 };
13941
13942 let end_position = if head != tail {
13943 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13944 return;
13945 };
13946 Some(pos)
13947 } else {
13948 None
13949 };
13950
13951 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13952 let url = if let Some(end_pos) = end_position {
13953 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13954 } else {
13955 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13956 };
13957
13958 if let Some(url) = url {
13959 editor.update(cx, |_, cx| {
13960 cx.open_url(&url);
13961 })
13962 } else {
13963 Ok(())
13964 }
13965 });
13966
13967 url_finder.detach();
13968 }
13969
13970 pub fn open_selected_filename(
13971 &mut self,
13972 _: &OpenSelectedFilename,
13973 window: &mut Window,
13974 cx: &mut Context<Self>,
13975 ) {
13976 let Some(workspace) = self.workspace() else {
13977 return;
13978 };
13979
13980 let position = self.selections.newest_anchor().head();
13981
13982 let Some((buffer, buffer_position)) =
13983 self.buffer.read(cx).text_anchor_for_position(position, cx)
13984 else {
13985 return;
13986 };
13987
13988 let project = self.project.clone();
13989
13990 cx.spawn_in(window, async move |_, cx| {
13991 let result = find_file(&buffer, project, buffer_position, cx).await;
13992
13993 if let Some((_, path)) = result {
13994 workspace
13995 .update_in(cx, |workspace, window, cx| {
13996 workspace.open_resolved_path(path, window, cx)
13997 })?
13998 .await?;
13999 }
14000 anyhow::Ok(())
14001 })
14002 .detach();
14003 }
14004
14005 pub(crate) fn navigate_to_hover_links(
14006 &mut self,
14007 kind: Option<GotoDefinitionKind>,
14008 mut definitions: Vec<HoverLink>,
14009 split: bool,
14010 window: &mut Window,
14011 cx: &mut Context<Editor>,
14012 ) -> Task<Result<Navigated>> {
14013 // If there is one definition, just open it directly
14014 if definitions.len() == 1 {
14015 let definition = definitions.pop().unwrap();
14016
14017 enum TargetTaskResult {
14018 Location(Option<Location>),
14019 AlreadyNavigated,
14020 }
14021
14022 let target_task = match definition {
14023 HoverLink::Text(link) => {
14024 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14025 }
14026 HoverLink::InlayHint(lsp_location, server_id) => {
14027 let computation =
14028 self.compute_target_location(lsp_location, server_id, window, cx);
14029 cx.background_spawn(async move {
14030 let location = computation.await?;
14031 Ok(TargetTaskResult::Location(location))
14032 })
14033 }
14034 HoverLink::Url(url) => {
14035 cx.open_url(&url);
14036 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14037 }
14038 HoverLink::File(path) => {
14039 if let Some(workspace) = self.workspace() {
14040 cx.spawn_in(window, async move |_, cx| {
14041 workspace
14042 .update_in(cx, |workspace, window, cx| {
14043 workspace.open_resolved_path(path, window, cx)
14044 })?
14045 .await
14046 .map(|_| TargetTaskResult::AlreadyNavigated)
14047 })
14048 } else {
14049 Task::ready(Ok(TargetTaskResult::Location(None)))
14050 }
14051 }
14052 };
14053 cx.spawn_in(window, async move |editor, cx| {
14054 let target = match target_task.await.context("target resolution task")? {
14055 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14056 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14057 TargetTaskResult::Location(Some(target)) => target,
14058 };
14059
14060 editor.update_in(cx, |editor, window, cx| {
14061 let Some(workspace) = editor.workspace() else {
14062 return Navigated::No;
14063 };
14064 let pane = workspace.read(cx).active_pane().clone();
14065
14066 let range = target.range.to_point(target.buffer.read(cx));
14067 let range = editor.range_for_match(&range);
14068 let range = collapse_multiline_range(range);
14069
14070 if !split
14071 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14072 {
14073 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14074 } else {
14075 window.defer(cx, move |window, cx| {
14076 let target_editor: Entity<Self> =
14077 workspace.update(cx, |workspace, cx| {
14078 let pane = if split {
14079 workspace.adjacent_pane(window, cx)
14080 } else {
14081 workspace.active_pane().clone()
14082 };
14083
14084 workspace.open_project_item(
14085 pane,
14086 target.buffer.clone(),
14087 true,
14088 true,
14089 window,
14090 cx,
14091 )
14092 });
14093 target_editor.update(cx, |target_editor, cx| {
14094 // When selecting a definition in a different buffer, disable the nav history
14095 // to avoid creating a history entry at the previous cursor location.
14096 pane.update(cx, |pane, _| pane.disable_history());
14097 target_editor.go_to_singleton_buffer_range(range, window, cx);
14098 pane.update(cx, |pane, _| pane.enable_history());
14099 });
14100 });
14101 }
14102 Navigated::Yes
14103 })
14104 })
14105 } else if !definitions.is_empty() {
14106 cx.spawn_in(window, async move |editor, cx| {
14107 let (title, location_tasks, workspace) = editor
14108 .update_in(cx, |editor, window, cx| {
14109 let tab_kind = match kind {
14110 Some(GotoDefinitionKind::Implementation) => "Implementations",
14111 _ => "Definitions",
14112 };
14113 let title = definitions
14114 .iter()
14115 .find_map(|definition| match definition {
14116 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14117 let buffer = origin.buffer.read(cx);
14118 format!(
14119 "{} for {}",
14120 tab_kind,
14121 buffer
14122 .text_for_range(origin.range.clone())
14123 .collect::<String>()
14124 )
14125 }),
14126 HoverLink::InlayHint(_, _) => None,
14127 HoverLink::Url(_) => None,
14128 HoverLink::File(_) => None,
14129 })
14130 .unwrap_or(tab_kind.to_string());
14131 let location_tasks = definitions
14132 .into_iter()
14133 .map(|definition| match definition {
14134 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14135 HoverLink::InlayHint(lsp_location, server_id) => editor
14136 .compute_target_location(lsp_location, server_id, window, cx),
14137 HoverLink::Url(_) => Task::ready(Ok(None)),
14138 HoverLink::File(_) => Task::ready(Ok(None)),
14139 })
14140 .collect::<Vec<_>>();
14141 (title, location_tasks, editor.workspace().clone())
14142 })
14143 .context("location tasks preparation")?;
14144
14145 let locations = future::join_all(location_tasks)
14146 .await
14147 .into_iter()
14148 .filter_map(|location| location.transpose())
14149 .collect::<Result<_>>()
14150 .context("location tasks")?;
14151
14152 let Some(workspace) = workspace else {
14153 return Ok(Navigated::No);
14154 };
14155 let opened = workspace
14156 .update_in(cx, |workspace, window, cx| {
14157 Self::open_locations_in_multibuffer(
14158 workspace,
14159 locations,
14160 title,
14161 split,
14162 MultibufferSelectionMode::First,
14163 window,
14164 cx,
14165 )
14166 })
14167 .ok();
14168
14169 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14170 })
14171 } else {
14172 Task::ready(Ok(Navigated::No))
14173 }
14174 }
14175
14176 fn compute_target_location(
14177 &self,
14178 lsp_location: lsp::Location,
14179 server_id: LanguageServerId,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) -> Task<anyhow::Result<Option<Location>>> {
14183 let Some(project) = self.project.clone() else {
14184 return Task::ready(Ok(None));
14185 };
14186
14187 cx.spawn_in(window, async move |editor, cx| {
14188 let location_task = editor.update(cx, |_, cx| {
14189 project.update(cx, |project, cx| {
14190 let language_server_name = project
14191 .language_server_statuses(cx)
14192 .find(|(id, _)| server_id == *id)
14193 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14194 language_server_name.map(|language_server_name| {
14195 project.open_local_buffer_via_lsp(
14196 lsp_location.uri.clone(),
14197 server_id,
14198 language_server_name,
14199 cx,
14200 )
14201 })
14202 })
14203 })?;
14204 let location = match location_task {
14205 Some(task) => Some({
14206 let target_buffer_handle = task.await.context("open local buffer")?;
14207 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14208 let target_start = target_buffer
14209 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14210 let target_end = target_buffer
14211 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14212 target_buffer.anchor_after(target_start)
14213 ..target_buffer.anchor_before(target_end)
14214 })?;
14215 Location {
14216 buffer: target_buffer_handle,
14217 range,
14218 }
14219 }),
14220 None => None,
14221 };
14222 Ok(location)
14223 })
14224 }
14225
14226 pub fn find_all_references(
14227 &mut self,
14228 _: &FindAllReferences,
14229 window: &mut Window,
14230 cx: &mut Context<Self>,
14231 ) -> Option<Task<Result<Navigated>>> {
14232 let selection = self.selections.newest::<usize>(cx);
14233 let multi_buffer = self.buffer.read(cx);
14234 let head = selection.head();
14235
14236 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14237 let head_anchor = multi_buffer_snapshot.anchor_at(
14238 head,
14239 if head < selection.tail() {
14240 Bias::Right
14241 } else {
14242 Bias::Left
14243 },
14244 );
14245
14246 match self
14247 .find_all_references_task_sources
14248 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14249 {
14250 Ok(_) => {
14251 log::info!(
14252 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14253 );
14254 return None;
14255 }
14256 Err(i) => {
14257 self.find_all_references_task_sources.insert(i, head_anchor);
14258 }
14259 }
14260
14261 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14262 let workspace = self.workspace()?;
14263 let project = workspace.read(cx).project().clone();
14264 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14265 Some(cx.spawn_in(window, async move |editor, cx| {
14266 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14267 if let Ok(i) = editor
14268 .find_all_references_task_sources
14269 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14270 {
14271 editor.find_all_references_task_sources.remove(i);
14272 }
14273 });
14274
14275 let locations = references.await?;
14276 if locations.is_empty() {
14277 return anyhow::Ok(Navigated::No);
14278 }
14279
14280 workspace.update_in(cx, |workspace, window, cx| {
14281 let title = locations
14282 .first()
14283 .as_ref()
14284 .map(|location| {
14285 let buffer = location.buffer.read(cx);
14286 format!(
14287 "References to `{}`",
14288 buffer
14289 .text_for_range(location.range.clone())
14290 .collect::<String>()
14291 )
14292 })
14293 .unwrap();
14294 Self::open_locations_in_multibuffer(
14295 workspace,
14296 locations,
14297 title,
14298 false,
14299 MultibufferSelectionMode::First,
14300 window,
14301 cx,
14302 );
14303 Navigated::Yes
14304 })
14305 }))
14306 }
14307
14308 /// Opens a multibuffer with the given project locations in it
14309 pub fn open_locations_in_multibuffer(
14310 workspace: &mut Workspace,
14311 mut locations: Vec<Location>,
14312 title: String,
14313 split: bool,
14314 multibuffer_selection_mode: MultibufferSelectionMode,
14315 window: &mut Window,
14316 cx: &mut Context<Workspace>,
14317 ) {
14318 // If there are multiple definitions, open them in a multibuffer
14319 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14320 let mut locations = locations.into_iter().peekable();
14321 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14322 let capability = workspace.project().read(cx).capability();
14323
14324 let excerpt_buffer = cx.new(|cx| {
14325 let mut multibuffer = MultiBuffer::new(capability);
14326 while let Some(location) = locations.next() {
14327 let buffer = location.buffer.read(cx);
14328 let mut ranges_for_buffer = Vec::new();
14329 let range = location.range.to_point(buffer);
14330 ranges_for_buffer.push(range.clone());
14331
14332 while let Some(next_location) = locations.peek() {
14333 if next_location.buffer == location.buffer {
14334 ranges_for_buffer.push(next_location.range.to_point(buffer));
14335 locations.next();
14336 } else {
14337 break;
14338 }
14339 }
14340
14341 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14342 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14343 PathKey::for_buffer(&location.buffer, cx),
14344 location.buffer.clone(),
14345 ranges_for_buffer,
14346 DEFAULT_MULTIBUFFER_CONTEXT,
14347 cx,
14348 );
14349 ranges.extend(new_ranges)
14350 }
14351
14352 multibuffer.with_title(title)
14353 });
14354
14355 let editor = cx.new(|cx| {
14356 Editor::for_multibuffer(
14357 excerpt_buffer,
14358 Some(workspace.project().clone()),
14359 window,
14360 cx,
14361 )
14362 });
14363 editor.update(cx, |editor, cx| {
14364 match multibuffer_selection_mode {
14365 MultibufferSelectionMode::First => {
14366 if let Some(first_range) = ranges.first() {
14367 editor.change_selections(None, window, cx, |selections| {
14368 selections.clear_disjoint();
14369 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14370 });
14371 }
14372 editor.highlight_background::<Self>(
14373 &ranges,
14374 |theme| theme.editor_highlighted_line_background,
14375 cx,
14376 );
14377 }
14378 MultibufferSelectionMode::All => {
14379 editor.change_selections(None, window, cx, |selections| {
14380 selections.clear_disjoint();
14381 selections.select_anchor_ranges(ranges);
14382 });
14383 }
14384 }
14385 editor.register_buffers_with_language_servers(cx);
14386 });
14387
14388 let item = Box::new(editor);
14389 let item_id = item.item_id();
14390
14391 if split {
14392 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14393 } else {
14394 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14395 let (preview_item_id, preview_item_idx) =
14396 workspace.active_pane().update(cx, |pane, _| {
14397 (pane.preview_item_id(), pane.preview_item_idx())
14398 });
14399
14400 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14401
14402 if let Some(preview_item_id) = preview_item_id {
14403 workspace.active_pane().update(cx, |pane, cx| {
14404 pane.remove_item(preview_item_id, false, false, window, cx);
14405 });
14406 }
14407 } else {
14408 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14409 }
14410 }
14411 workspace.active_pane().update(cx, |pane, cx| {
14412 pane.set_preview_item_id(Some(item_id), cx);
14413 });
14414 }
14415
14416 pub fn rename(
14417 &mut self,
14418 _: &Rename,
14419 window: &mut Window,
14420 cx: &mut Context<Self>,
14421 ) -> Option<Task<Result<()>>> {
14422 use language::ToOffset as _;
14423
14424 let provider = self.semantics_provider.clone()?;
14425 let selection = self.selections.newest_anchor().clone();
14426 let (cursor_buffer, cursor_buffer_position) = self
14427 .buffer
14428 .read(cx)
14429 .text_anchor_for_position(selection.head(), cx)?;
14430 let (tail_buffer, cursor_buffer_position_end) = self
14431 .buffer
14432 .read(cx)
14433 .text_anchor_for_position(selection.tail(), cx)?;
14434 if tail_buffer != cursor_buffer {
14435 return None;
14436 }
14437
14438 let snapshot = cursor_buffer.read(cx).snapshot();
14439 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14440 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14441 let prepare_rename = provider
14442 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14443 .unwrap_or_else(|| Task::ready(Ok(None)));
14444 drop(snapshot);
14445
14446 Some(cx.spawn_in(window, async move |this, cx| {
14447 let rename_range = if let Some(range) = prepare_rename.await? {
14448 Some(range)
14449 } else {
14450 this.update(cx, |this, cx| {
14451 let buffer = this.buffer.read(cx).snapshot(cx);
14452 let mut buffer_highlights = this
14453 .document_highlights_for_position(selection.head(), &buffer)
14454 .filter(|highlight| {
14455 highlight.start.excerpt_id == selection.head().excerpt_id
14456 && highlight.end.excerpt_id == selection.head().excerpt_id
14457 });
14458 buffer_highlights
14459 .next()
14460 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14461 })?
14462 };
14463 if let Some(rename_range) = rename_range {
14464 this.update_in(cx, |this, window, cx| {
14465 let snapshot = cursor_buffer.read(cx).snapshot();
14466 let rename_buffer_range = rename_range.to_offset(&snapshot);
14467 let cursor_offset_in_rename_range =
14468 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14469 let cursor_offset_in_rename_range_end =
14470 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14471
14472 this.take_rename(false, window, cx);
14473 let buffer = this.buffer.read(cx).read(cx);
14474 let cursor_offset = selection.head().to_offset(&buffer);
14475 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14476 let rename_end = rename_start + rename_buffer_range.len();
14477 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14478 let mut old_highlight_id = None;
14479 let old_name: Arc<str> = buffer
14480 .chunks(rename_start..rename_end, true)
14481 .map(|chunk| {
14482 if old_highlight_id.is_none() {
14483 old_highlight_id = chunk.syntax_highlight_id;
14484 }
14485 chunk.text
14486 })
14487 .collect::<String>()
14488 .into();
14489
14490 drop(buffer);
14491
14492 // Position the selection in the rename editor so that it matches the current selection.
14493 this.show_local_selections = false;
14494 let rename_editor = cx.new(|cx| {
14495 let mut editor = Editor::single_line(window, cx);
14496 editor.buffer.update(cx, |buffer, cx| {
14497 buffer.edit([(0..0, old_name.clone())], None, cx)
14498 });
14499 let rename_selection_range = match cursor_offset_in_rename_range
14500 .cmp(&cursor_offset_in_rename_range_end)
14501 {
14502 Ordering::Equal => {
14503 editor.select_all(&SelectAll, window, cx);
14504 return editor;
14505 }
14506 Ordering::Less => {
14507 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14508 }
14509 Ordering::Greater => {
14510 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14511 }
14512 };
14513 if rename_selection_range.end > old_name.len() {
14514 editor.select_all(&SelectAll, window, cx);
14515 } else {
14516 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14517 s.select_ranges([rename_selection_range]);
14518 });
14519 }
14520 editor
14521 });
14522 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14523 if e == &EditorEvent::Focused {
14524 cx.emit(EditorEvent::FocusedIn)
14525 }
14526 })
14527 .detach();
14528
14529 let write_highlights =
14530 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14531 let read_highlights =
14532 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14533 let ranges = write_highlights
14534 .iter()
14535 .flat_map(|(_, ranges)| ranges.iter())
14536 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14537 .cloned()
14538 .collect();
14539
14540 this.highlight_text::<Rename>(
14541 ranges,
14542 HighlightStyle {
14543 fade_out: Some(0.6),
14544 ..Default::default()
14545 },
14546 cx,
14547 );
14548 let rename_focus_handle = rename_editor.focus_handle(cx);
14549 window.focus(&rename_focus_handle);
14550 let block_id = this.insert_blocks(
14551 [BlockProperties {
14552 style: BlockStyle::Flex,
14553 placement: BlockPlacement::Below(range.start),
14554 height: Some(1),
14555 render: Arc::new({
14556 let rename_editor = rename_editor.clone();
14557 move |cx: &mut BlockContext| {
14558 let mut text_style = cx.editor_style.text.clone();
14559 if let Some(highlight_style) = old_highlight_id
14560 .and_then(|h| h.style(&cx.editor_style.syntax))
14561 {
14562 text_style = text_style.highlight(highlight_style);
14563 }
14564 div()
14565 .block_mouse_down()
14566 .pl(cx.anchor_x)
14567 .child(EditorElement::new(
14568 &rename_editor,
14569 EditorStyle {
14570 background: cx.theme().system().transparent,
14571 local_player: cx.editor_style.local_player,
14572 text: text_style,
14573 scrollbar_width: cx.editor_style.scrollbar_width,
14574 syntax: cx.editor_style.syntax.clone(),
14575 status: cx.editor_style.status.clone(),
14576 inlay_hints_style: HighlightStyle {
14577 font_weight: Some(FontWeight::BOLD),
14578 ..make_inlay_hints_style(cx.app)
14579 },
14580 inline_completion_styles: make_suggestion_styles(
14581 cx.app,
14582 ),
14583 ..EditorStyle::default()
14584 },
14585 ))
14586 .into_any_element()
14587 }
14588 }),
14589 priority: 0,
14590 }],
14591 Some(Autoscroll::fit()),
14592 cx,
14593 )[0];
14594 this.pending_rename = Some(RenameState {
14595 range,
14596 old_name,
14597 editor: rename_editor,
14598 block_id,
14599 });
14600 })?;
14601 }
14602
14603 Ok(())
14604 }))
14605 }
14606
14607 pub fn confirm_rename(
14608 &mut self,
14609 _: &ConfirmRename,
14610 window: &mut Window,
14611 cx: &mut Context<Self>,
14612 ) -> Option<Task<Result<()>>> {
14613 let rename = self.take_rename(false, window, cx)?;
14614 let workspace = self.workspace()?.downgrade();
14615 let (buffer, start) = self
14616 .buffer
14617 .read(cx)
14618 .text_anchor_for_position(rename.range.start, cx)?;
14619 let (end_buffer, _) = self
14620 .buffer
14621 .read(cx)
14622 .text_anchor_for_position(rename.range.end, cx)?;
14623 if buffer != end_buffer {
14624 return None;
14625 }
14626
14627 let old_name = rename.old_name;
14628 let new_name = rename.editor.read(cx).text(cx);
14629
14630 let rename = self.semantics_provider.as_ref()?.perform_rename(
14631 &buffer,
14632 start,
14633 new_name.clone(),
14634 cx,
14635 )?;
14636
14637 Some(cx.spawn_in(window, async move |editor, cx| {
14638 let project_transaction = rename.await?;
14639 Self::open_project_transaction(
14640 &editor,
14641 workspace,
14642 project_transaction,
14643 format!("Rename: {} → {}", old_name, new_name),
14644 cx,
14645 )
14646 .await?;
14647
14648 editor.update(cx, |editor, cx| {
14649 editor.refresh_document_highlights(cx);
14650 })?;
14651 Ok(())
14652 }))
14653 }
14654
14655 fn take_rename(
14656 &mut self,
14657 moving_cursor: bool,
14658 window: &mut Window,
14659 cx: &mut Context<Self>,
14660 ) -> Option<RenameState> {
14661 let rename = self.pending_rename.take()?;
14662 if rename.editor.focus_handle(cx).is_focused(window) {
14663 window.focus(&self.focus_handle);
14664 }
14665
14666 self.remove_blocks(
14667 [rename.block_id].into_iter().collect(),
14668 Some(Autoscroll::fit()),
14669 cx,
14670 );
14671 self.clear_highlights::<Rename>(cx);
14672 self.show_local_selections = true;
14673
14674 if moving_cursor {
14675 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14676 editor.selections.newest::<usize>(cx).head()
14677 });
14678
14679 // Update the selection to match the position of the selection inside
14680 // the rename editor.
14681 let snapshot = self.buffer.read(cx).read(cx);
14682 let rename_range = rename.range.to_offset(&snapshot);
14683 let cursor_in_editor = snapshot
14684 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14685 .min(rename_range.end);
14686 drop(snapshot);
14687
14688 self.change_selections(None, window, cx, |s| {
14689 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14690 });
14691 } else {
14692 self.refresh_document_highlights(cx);
14693 }
14694
14695 Some(rename)
14696 }
14697
14698 pub fn pending_rename(&self) -> Option<&RenameState> {
14699 self.pending_rename.as_ref()
14700 }
14701
14702 fn format(
14703 &mut self,
14704 _: &Format,
14705 window: &mut Window,
14706 cx: &mut Context<Self>,
14707 ) -> Option<Task<Result<()>>> {
14708 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14709
14710 let project = match &self.project {
14711 Some(project) => project.clone(),
14712 None => return None,
14713 };
14714
14715 Some(self.perform_format(
14716 project,
14717 FormatTrigger::Manual,
14718 FormatTarget::Buffers,
14719 window,
14720 cx,
14721 ))
14722 }
14723
14724 fn format_selections(
14725 &mut self,
14726 _: &FormatSelections,
14727 window: &mut Window,
14728 cx: &mut Context<Self>,
14729 ) -> Option<Task<Result<()>>> {
14730 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14731
14732 let project = match &self.project {
14733 Some(project) => project.clone(),
14734 None => return None,
14735 };
14736
14737 let ranges = self
14738 .selections
14739 .all_adjusted(cx)
14740 .into_iter()
14741 .map(|selection| selection.range())
14742 .collect_vec();
14743
14744 Some(self.perform_format(
14745 project,
14746 FormatTrigger::Manual,
14747 FormatTarget::Ranges(ranges),
14748 window,
14749 cx,
14750 ))
14751 }
14752
14753 fn perform_format(
14754 &mut self,
14755 project: Entity<Project>,
14756 trigger: FormatTrigger,
14757 target: FormatTarget,
14758 window: &mut Window,
14759 cx: &mut Context<Self>,
14760 ) -> Task<Result<()>> {
14761 let buffer = self.buffer.clone();
14762 let (buffers, target) = match target {
14763 FormatTarget::Buffers => {
14764 let mut buffers = buffer.read(cx).all_buffers();
14765 if trigger == FormatTrigger::Save {
14766 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14767 }
14768 (buffers, LspFormatTarget::Buffers)
14769 }
14770 FormatTarget::Ranges(selection_ranges) => {
14771 let multi_buffer = buffer.read(cx);
14772 let snapshot = multi_buffer.read(cx);
14773 let mut buffers = HashSet::default();
14774 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14775 BTreeMap::new();
14776 for selection_range in selection_ranges {
14777 for (buffer, buffer_range, _) in
14778 snapshot.range_to_buffer_ranges(selection_range)
14779 {
14780 let buffer_id = buffer.remote_id();
14781 let start = buffer.anchor_before(buffer_range.start);
14782 let end = buffer.anchor_after(buffer_range.end);
14783 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14784 buffer_id_to_ranges
14785 .entry(buffer_id)
14786 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14787 .or_insert_with(|| vec![start..end]);
14788 }
14789 }
14790 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14791 }
14792 };
14793
14794 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14795 let selections_prev = transaction_id_prev
14796 .and_then(|transaction_id_prev| {
14797 // default to selections as they were after the last edit, if we have them,
14798 // instead of how they are now.
14799 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14800 // will take you back to where you made the last edit, instead of staying where you scrolled
14801 self.selection_history
14802 .transaction(transaction_id_prev)
14803 .map(|t| t.0.clone())
14804 })
14805 .unwrap_or_else(|| {
14806 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14807 self.selections.disjoint_anchors()
14808 });
14809
14810 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14811 let format = project.update(cx, |project, cx| {
14812 project.format(buffers, target, true, trigger, cx)
14813 });
14814
14815 cx.spawn_in(window, async move |editor, cx| {
14816 let transaction = futures::select_biased! {
14817 transaction = format.log_err().fuse() => transaction,
14818 () = timeout => {
14819 log::warn!("timed out waiting for formatting");
14820 None
14821 }
14822 };
14823
14824 buffer
14825 .update(cx, |buffer, cx| {
14826 if let Some(transaction) = transaction {
14827 if !buffer.is_singleton() {
14828 buffer.push_transaction(&transaction.0, cx);
14829 }
14830 }
14831 cx.notify();
14832 })
14833 .ok();
14834
14835 if let Some(transaction_id_now) =
14836 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14837 {
14838 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14839 if has_new_transaction {
14840 _ = editor.update(cx, |editor, _| {
14841 editor
14842 .selection_history
14843 .insert_transaction(transaction_id_now, selections_prev);
14844 });
14845 }
14846 }
14847
14848 Ok(())
14849 })
14850 }
14851
14852 fn organize_imports(
14853 &mut self,
14854 _: &OrganizeImports,
14855 window: &mut Window,
14856 cx: &mut Context<Self>,
14857 ) -> Option<Task<Result<()>>> {
14858 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14859 let project = match &self.project {
14860 Some(project) => project.clone(),
14861 None => return None,
14862 };
14863 Some(self.perform_code_action_kind(
14864 project,
14865 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14866 window,
14867 cx,
14868 ))
14869 }
14870
14871 fn perform_code_action_kind(
14872 &mut self,
14873 project: Entity<Project>,
14874 kind: CodeActionKind,
14875 window: &mut Window,
14876 cx: &mut Context<Self>,
14877 ) -> Task<Result<()>> {
14878 let buffer = self.buffer.clone();
14879 let buffers = buffer.read(cx).all_buffers();
14880 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14881 let apply_action = project.update(cx, |project, cx| {
14882 project.apply_code_action_kind(buffers, kind, true, cx)
14883 });
14884 cx.spawn_in(window, async move |_, cx| {
14885 let transaction = futures::select_biased! {
14886 () = timeout => {
14887 log::warn!("timed out waiting for executing code action");
14888 None
14889 }
14890 transaction = apply_action.log_err().fuse() => transaction,
14891 };
14892 buffer
14893 .update(cx, |buffer, cx| {
14894 // check if we need this
14895 if let Some(transaction) = transaction {
14896 if !buffer.is_singleton() {
14897 buffer.push_transaction(&transaction.0, cx);
14898 }
14899 }
14900 cx.notify();
14901 })
14902 .ok();
14903 Ok(())
14904 })
14905 }
14906
14907 fn restart_language_server(
14908 &mut self,
14909 _: &RestartLanguageServer,
14910 _: &mut Window,
14911 cx: &mut Context<Self>,
14912 ) {
14913 if let Some(project) = self.project.clone() {
14914 self.buffer.update(cx, |multi_buffer, cx| {
14915 project.update(cx, |project, cx| {
14916 project.restart_language_servers_for_buffers(
14917 multi_buffer.all_buffers().into_iter().collect(),
14918 cx,
14919 );
14920 });
14921 })
14922 }
14923 }
14924
14925 fn stop_language_server(
14926 &mut self,
14927 _: &StopLanguageServer,
14928 _: &mut Window,
14929 cx: &mut Context<Self>,
14930 ) {
14931 if let Some(project) = self.project.clone() {
14932 self.buffer.update(cx, |multi_buffer, cx| {
14933 project.update(cx, |project, cx| {
14934 project.stop_language_servers_for_buffers(
14935 multi_buffer.all_buffers().into_iter().collect(),
14936 cx,
14937 );
14938 cx.emit(project::Event::RefreshInlayHints);
14939 });
14940 });
14941 }
14942 }
14943
14944 fn cancel_language_server_work(
14945 workspace: &mut Workspace,
14946 _: &actions::CancelLanguageServerWork,
14947 _: &mut Window,
14948 cx: &mut Context<Workspace>,
14949 ) {
14950 let project = workspace.project();
14951 let buffers = workspace
14952 .active_item(cx)
14953 .and_then(|item| item.act_as::<Editor>(cx))
14954 .map_or(HashSet::default(), |editor| {
14955 editor.read(cx).buffer.read(cx).all_buffers()
14956 });
14957 project.update(cx, |project, cx| {
14958 project.cancel_language_server_work_for_buffers(buffers, cx);
14959 });
14960 }
14961
14962 fn show_character_palette(
14963 &mut self,
14964 _: &ShowCharacterPalette,
14965 window: &mut Window,
14966 _: &mut Context<Self>,
14967 ) {
14968 window.show_character_palette();
14969 }
14970
14971 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14972 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14973 let buffer = self.buffer.read(cx).snapshot(cx);
14974 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14975 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14976 let is_valid = buffer
14977 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14978 .any(|entry| {
14979 entry.diagnostic.is_primary
14980 && !entry.range.is_empty()
14981 && entry.range.start == primary_range_start
14982 && entry.diagnostic.message == active_diagnostics.active_message
14983 });
14984
14985 if !is_valid {
14986 self.dismiss_diagnostics(cx);
14987 }
14988 }
14989 }
14990
14991 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
14992 match &self.active_diagnostics {
14993 ActiveDiagnostic::Group(group) => Some(group),
14994 _ => None,
14995 }
14996 }
14997
14998 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
14999 self.dismiss_diagnostics(cx);
15000 self.active_diagnostics = ActiveDiagnostic::All;
15001 }
15002
15003 fn activate_diagnostics(
15004 &mut self,
15005 buffer_id: BufferId,
15006 diagnostic: DiagnosticEntry<usize>,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) {
15010 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15011 return;
15012 }
15013 self.dismiss_diagnostics(cx);
15014 let snapshot = self.snapshot(window, cx);
15015 let buffer = self.buffer.read(cx).snapshot(cx);
15016 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15017 return;
15018 };
15019
15020 let diagnostic_group = buffer
15021 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15022 .collect::<Vec<_>>();
15023
15024 let blocks =
15025 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15026
15027 let blocks = self.display_map.update(cx, |display_map, cx| {
15028 display_map.insert_blocks(blocks, cx).into_iter().collect()
15029 });
15030 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15031 active_range: buffer.anchor_before(diagnostic.range.start)
15032 ..buffer.anchor_after(diagnostic.range.end),
15033 active_message: diagnostic.diagnostic.message.clone(),
15034 group_id: diagnostic.diagnostic.group_id,
15035 blocks,
15036 });
15037 cx.notify();
15038 }
15039
15040 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15041 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15042 return;
15043 };
15044
15045 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15046 if let ActiveDiagnostic::Group(group) = prev {
15047 self.display_map.update(cx, |display_map, cx| {
15048 display_map.remove_blocks(group.blocks, cx);
15049 });
15050 cx.notify();
15051 }
15052 }
15053
15054 /// Disable inline diagnostics rendering for this editor.
15055 pub fn disable_inline_diagnostics(&mut self) {
15056 self.inline_diagnostics_enabled = false;
15057 self.inline_diagnostics_update = Task::ready(());
15058 self.inline_diagnostics.clear();
15059 }
15060
15061 pub fn inline_diagnostics_enabled(&self) -> bool {
15062 self.inline_diagnostics_enabled
15063 }
15064
15065 pub fn show_inline_diagnostics(&self) -> bool {
15066 self.show_inline_diagnostics
15067 }
15068
15069 pub fn toggle_inline_diagnostics(
15070 &mut self,
15071 _: &ToggleInlineDiagnostics,
15072 window: &mut Window,
15073 cx: &mut Context<Editor>,
15074 ) {
15075 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15076 self.refresh_inline_diagnostics(false, window, cx);
15077 }
15078
15079 fn refresh_inline_diagnostics(
15080 &mut self,
15081 debounce: bool,
15082 window: &mut Window,
15083 cx: &mut Context<Self>,
15084 ) {
15085 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
15086 self.inline_diagnostics_update = Task::ready(());
15087 self.inline_diagnostics.clear();
15088 return;
15089 }
15090
15091 let debounce_ms = ProjectSettings::get_global(cx)
15092 .diagnostics
15093 .inline
15094 .update_debounce_ms;
15095 let debounce = if debounce && debounce_ms > 0 {
15096 Some(Duration::from_millis(debounce_ms))
15097 } else {
15098 None
15099 };
15100 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15101 let editor = editor.upgrade().unwrap();
15102
15103 if let Some(debounce) = debounce {
15104 cx.background_executor().timer(debounce).await;
15105 }
15106 let Some(snapshot) = editor
15107 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15108 .ok()
15109 else {
15110 return;
15111 };
15112
15113 let new_inline_diagnostics = cx
15114 .background_spawn(async move {
15115 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15116 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15117 let message = diagnostic_entry
15118 .diagnostic
15119 .message
15120 .split_once('\n')
15121 .map(|(line, _)| line)
15122 .map(SharedString::new)
15123 .unwrap_or_else(|| {
15124 SharedString::from(diagnostic_entry.diagnostic.message)
15125 });
15126 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15127 let (Ok(i) | Err(i)) = inline_diagnostics
15128 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15129 inline_diagnostics.insert(
15130 i,
15131 (
15132 start_anchor,
15133 InlineDiagnostic {
15134 message,
15135 group_id: diagnostic_entry.diagnostic.group_id,
15136 start: diagnostic_entry.range.start.to_point(&snapshot),
15137 is_primary: diagnostic_entry.diagnostic.is_primary,
15138 severity: diagnostic_entry.diagnostic.severity,
15139 },
15140 ),
15141 );
15142 }
15143 inline_diagnostics
15144 })
15145 .await;
15146
15147 editor
15148 .update(cx, |editor, cx| {
15149 editor.inline_diagnostics = new_inline_diagnostics;
15150 cx.notify();
15151 })
15152 .ok();
15153 });
15154 }
15155
15156 pub fn set_selections_from_remote(
15157 &mut self,
15158 selections: Vec<Selection<Anchor>>,
15159 pending_selection: Option<Selection<Anchor>>,
15160 window: &mut Window,
15161 cx: &mut Context<Self>,
15162 ) {
15163 let old_cursor_position = self.selections.newest_anchor().head();
15164 self.selections.change_with(cx, |s| {
15165 s.select_anchors(selections);
15166 if let Some(pending_selection) = pending_selection {
15167 s.set_pending(pending_selection, SelectMode::Character);
15168 } else {
15169 s.clear_pending();
15170 }
15171 });
15172 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15173 }
15174
15175 fn push_to_selection_history(&mut self) {
15176 self.selection_history.push(SelectionHistoryEntry {
15177 selections: self.selections.disjoint_anchors(),
15178 select_next_state: self.select_next_state.clone(),
15179 select_prev_state: self.select_prev_state.clone(),
15180 add_selections_state: self.add_selections_state.clone(),
15181 });
15182 }
15183
15184 pub fn transact(
15185 &mut self,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15189 ) -> Option<TransactionId> {
15190 self.start_transaction_at(Instant::now(), window, cx);
15191 update(self, window, cx);
15192 self.end_transaction_at(Instant::now(), cx)
15193 }
15194
15195 pub fn start_transaction_at(
15196 &mut self,
15197 now: Instant,
15198 window: &mut Window,
15199 cx: &mut Context<Self>,
15200 ) {
15201 self.end_selection(window, cx);
15202 if let Some(tx_id) = self
15203 .buffer
15204 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15205 {
15206 self.selection_history
15207 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15208 cx.emit(EditorEvent::TransactionBegun {
15209 transaction_id: tx_id,
15210 })
15211 }
15212 }
15213
15214 pub fn end_transaction_at(
15215 &mut self,
15216 now: Instant,
15217 cx: &mut Context<Self>,
15218 ) -> Option<TransactionId> {
15219 if let Some(transaction_id) = self
15220 .buffer
15221 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15222 {
15223 if let Some((_, end_selections)) =
15224 self.selection_history.transaction_mut(transaction_id)
15225 {
15226 *end_selections = Some(self.selections.disjoint_anchors());
15227 } else {
15228 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15229 }
15230
15231 cx.emit(EditorEvent::Edited { transaction_id });
15232 Some(transaction_id)
15233 } else {
15234 None
15235 }
15236 }
15237
15238 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15239 if self.selection_mark_mode {
15240 self.change_selections(None, window, cx, |s| {
15241 s.move_with(|_, sel| {
15242 sel.collapse_to(sel.head(), SelectionGoal::None);
15243 });
15244 })
15245 }
15246 self.selection_mark_mode = true;
15247 cx.notify();
15248 }
15249
15250 pub fn swap_selection_ends(
15251 &mut self,
15252 _: &actions::SwapSelectionEnds,
15253 window: &mut Window,
15254 cx: &mut Context<Self>,
15255 ) {
15256 self.change_selections(None, window, cx, |s| {
15257 s.move_with(|_, sel| {
15258 if sel.start != sel.end {
15259 sel.reversed = !sel.reversed
15260 }
15261 });
15262 });
15263 self.request_autoscroll(Autoscroll::newest(), cx);
15264 cx.notify();
15265 }
15266
15267 pub fn toggle_fold(
15268 &mut self,
15269 _: &actions::ToggleFold,
15270 window: &mut Window,
15271 cx: &mut Context<Self>,
15272 ) {
15273 if self.is_singleton(cx) {
15274 let selection = self.selections.newest::<Point>(cx);
15275
15276 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15277 let range = if selection.is_empty() {
15278 let point = selection.head().to_display_point(&display_map);
15279 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15280 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15281 .to_point(&display_map);
15282 start..end
15283 } else {
15284 selection.range()
15285 };
15286 if display_map.folds_in_range(range).next().is_some() {
15287 self.unfold_lines(&Default::default(), window, cx)
15288 } else {
15289 self.fold(&Default::default(), window, cx)
15290 }
15291 } else {
15292 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15293 let buffer_ids: HashSet<_> = self
15294 .selections
15295 .disjoint_anchor_ranges()
15296 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15297 .collect();
15298
15299 let should_unfold = buffer_ids
15300 .iter()
15301 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15302
15303 for buffer_id in buffer_ids {
15304 if should_unfold {
15305 self.unfold_buffer(buffer_id, cx);
15306 } else {
15307 self.fold_buffer(buffer_id, cx);
15308 }
15309 }
15310 }
15311 }
15312
15313 pub fn toggle_fold_recursive(
15314 &mut self,
15315 _: &actions::ToggleFoldRecursive,
15316 window: &mut Window,
15317 cx: &mut Context<Self>,
15318 ) {
15319 let selection = self.selections.newest::<Point>(cx);
15320
15321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15322 let range = if selection.is_empty() {
15323 let point = selection.head().to_display_point(&display_map);
15324 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15325 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15326 .to_point(&display_map);
15327 start..end
15328 } else {
15329 selection.range()
15330 };
15331 if display_map.folds_in_range(range).next().is_some() {
15332 self.unfold_recursive(&Default::default(), window, cx)
15333 } else {
15334 self.fold_recursive(&Default::default(), window, cx)
15335 }
15336 }
15337
15338 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15339 if self.is_singleton(cx) {
15340 let mut to_fold = Vec::new();
15341 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15342 let selections = self.selections.all_adjusted(cx);
15343
15344 for selection in selections {
15345 let range = selection.range().sorted();
15346 let buffer_start_row = range.start.row;
15347
15348 if range.start.row != range.end.row {
15349 let mut found = false;
15350 let mut row = range.start.row;
15351 while row <= range.end.row {
15352 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15353 {
15354 found = true;
15355 row = crease.range().end.row + 1;
15356 to_fold.push(crease);
15357 } else {
15358 row += 1
15359 }
15360 }
15361 if found {
15362 continue;
15363 }
15364 }
15365
15366 for row in (0..=range.start.row).rev() {
15367 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15368 if crease.range().end.row >= buffer_start_row {
15369 to_fold.push(crease);
15370 if row <= range.start.row {
15371 break;
15372 }
15373 }
15374 }
15375 }
15376 }
15377
15378 self.fold_creases(to_fold, true, window, cx);
15379 } else {
15380 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15381 let buffer_ids = self
15382 .selections
15383 .disjoint_anchor_ranges()
15384 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15385 .collect::<HashSet<_>>();
15386 for buffer_id in buffer_ids {
15387 self.fold_buffer(buffer_id, cx);
15388 }
15389 }
15390 }
15391
15392 fn fold_at_level(
15393 &mut self,
15394 fold_at: &FoldAtLevel,
15395 window: &mut Window,
15396 cx: &mut Context<Self>,
15397 ) {
15398 if !self.buffer.read(cx).is_singleton() {
15399 return;
15400 }
15401
15402 let fold_at_level = fold_at.0;
15403 let snapshot = self.buffer.read(cx).snapshot(cx);
15404 let mut to_fold = Vec::new();
15405 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15406
15407 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15408 while start_row < end_row {
15409 match self
15410 .snapshot(window, cx)
15411 .crease_for_buffer_row(MultiBufferRow(start_row))
15412 {
15413 Some(crease) => {
15414 let nested_start_row = crease.range().start.row + 1;
15415 let nested_end_row = crease.range().end.row;
15416
15417 if current_level < fold_at_level {
15418 stack.push((nested_start_row, nested_end_row, current_level + 1));
15419 } else if current_level == fold_at_level {
15420 to_fold.push(crease);
15421 }
15422
15423 start_row = nested_end_row + 1;
15424 }
15425 None => start_row += 1,
15426 }
15427 }
15428 }
15429
15430 self.fold_creases(to_fold, true, window, cx);
15431 }
15432
15433 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15434 if self.buffer.read(cx).is_singleton() {
15435 let mut fold_ranges = Vec::new();
15436 let snapshot = self.buffer.read(cx).snapshot(cx);
15437
15438 for row in 0..snapshot.max_row().0 {
15439 if let Some(foldable_range) = self
15440 .snapshot(window, cx)
15441 .crease_for_buffer_row(MultiBufferRow(row))
15442 {
15443 fold_ranges.push(foldable_range);
15444 }
15445 }
15446
15447 self.fold_creases(fold_ranges, true, window, cx);
15448 } else {
15449 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15450 editor
15451 .update_in(cx, |editor, _, cx| {
15452 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15453 editor.fold_buffer(buffer_id, cx);
15454 }
15455 })
15456 .ok();
15457 });
15458 }
15459 }
15460
15461 pub fn fold_function_bodies(
15462 &mut self,
15463 _: &actions::FoldFunctionBodies,
15464 window: &mut Window,
15465 cx: &mut Context<Self>,
15466 ) {
15467 let snapshot = self.buffer.read(cx).snapshot(cx);
15468
15469 let ranges = snapshot
15470 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15471 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15472 .collect::<Vec<_>>();
15473
15474 let creases = ranges
15475 .into_iter()
15476 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15477 .collect();
15478
15479 self.fold_creases(creases, true, window, cx);
15480 }
15481
15482 pub fn fold_recursive(
15483 &mut self,
15484 _: &actions::FoldRecursive,
15485 window: &mut Window,
15486 cx: &mut Context<Self>,
15487 ) {
15488 let mut to_fold = Vec::new();
15489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15490 let selections = self.selections.all_adjusted(cx);
15491
15492 for selection in selections {
15493 let range = selection.range().sorted();
15494 let buffer_start_row = range.start.row;
15495
15496 if range.start.row != range.end.row {
15497 let mut found = false;
15498 for row in range.start.row..=range.end.row {
15499 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15500 found = true;
15501 to_fold.push(crease);
15502 }
15503 }
15504 if found {
15505 continue;
15506 }
15507 }
15508
15509 for row in (0..=range.start.row).rev() {
15510 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15511 if crease.range().end.row >= buffer_start_row {
15512 to_fold.push(crease);
15513 } else {
15514 break;
15515 }
15516 }
15517 }
15518 }
15519
15520 self.fold_creases(to_fold, true, window, cx);
15521 }
15522
15523 pub fn fold_at(
15524 &mut self,
15525 buffer_row: MultiBufferRow,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15530
15531 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15532 let autoscroll = self
15533 .selections
15534 .all::<Point>(cx)
15535 .iter()
15536 .any(|selection| crease.range().overlaps(&selection.range()));
15537
15538 self.fold_creases(vec![crease], autoscroll, window, cx);
15539 }
15540 }
15541
15542 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15543 if self.is_singleton(cx) {
15544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15545 let buffer = &display_map.buffer_snapshot;
15546 let selections = self.selections.all::<Point>(cx);
15547 let ranges = selections
15548 .iter()
15549 .map(|s| {
15550 let range = s.display_range(&display_map).sorted();
15551 let mut start = range.start.to_point(&display_map);
15552 let mut end = range.end.to_point(&display_map);
15553 start.column = 0;
15554 end.column = buffer.line_len(MultiBufferRow(end.row));
15555 start..end
15556 })
15557 .collect::<Vec<_>>();
15558
15559 self.unfold_ranges(&ranges, true, true, cx);
15560 } else {
15561 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15562 let buffer_ids = self
15563 .selections
15564 .disjoint_anchor_ranges()
15565 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15566 .collect::<HashSet<_>>();
15567 for buffer_id in buffer_ids {
15568 self.unfold_buffer(buffer_id, cx);
15569 }
15570 }
15571 }
15572
15573 pub fn unfold_recursive(
15574 &mut self,
15575 _: &UnfoldRecursive,
15576 _window: &mut Window,
15577 cx: &mut Context<Self>,
15578 ) {
15579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15580 let selections = self.selections.all::<Point>(cx);
15581 let ranges = selections
15582 .iter()
15583 .map(|s| {
15584 let mut range = s.display_range(&display_map).sorted();
15585 *range.start.column_mut() = 0;
15586 *range.end.column_mut() = display_map.line_len(range.end.row());
15587 let start = range.start.to_point(&display_map);
15588 let end = range.end.to_point(&display_map);
15589 start..end
15590 })
15591 .collect::<Vec<_>>();
15592
15593 self.unfold_ranges(&ranges, true, true, cx);
15594 }
15595
15596 pub fn unfold_at(
15597 &mut self,
15598 buffer_row: MultiBufferRow,
15599 _window: &mut Window,
15600 cx: &mut Context<Self>,
15601 ) {
15602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15603
15604 let intersection_range = Point::new(buffer_row.0, 0)
15605 ..Point::new(
15606 buffer_row.0,
15607 display_map.buffer_snapshot.line_len(buffer_row),
15608 );
15609
15610 let autoscroll = self
15611 .selections
15612 .all::<Point>(cx)
15613 .iter()
15614 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15615
15616 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15617 }
15618
15619 pub fn unfold_all(
15620 &mut self,
15621 _: &actions::UnfoldAll,
15622 _window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) {
15625 if self.buffer.read(cx).is_singleton() {
15626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15627 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15628 } else {
15629 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15630 editor
15631 .update(cx, |editor, cx| {
15632 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15633 editor.unfold_buffer(buffer_id, cx);
15634 }
15635 })
15636 .ok();
15637 });
15638 }
15639 }
15640
15641 pub fn fold_selected_ranges(
15642 &mut self,
15643 _: &FoldSelectedRanges,
15644 window: &mut Window,
15645 cx: &mut Context<Self>,
15646 ) {
15647 let selections = self.selections.all_adjusted(cx);
15648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15649 let ranges = selections
15650 .into_iter()
15651 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15652 .collect::<Vec<_>>();
15653 self.fold_creases(ranges, true, window, cx);
15654 }
15655
15656 pub fn fold_ranges<T: ToOffset + Clone>(
15657 &mut self,
15658 ranges: Vec<Range<T>>,
15659 auto_scroll: bool,
15660 window: &mut Window,
15661 cx: &mut Context<Self>,
15662 ) {
15663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15664 let ranges = ranges
15665 .into_iter()
15666 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15667 .collect::<Vec<_>>();
15668 self.fold_creases(ranges, auto_scroll, window, cx);
15669 }
15670
15671 pub fn fold_creases<T: ToOffset + Clone>(
15672 &mut self,
15673 creases: Vec<Crease<T>>,
15674 auto_scroll: bool,
15675 _window: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) {
15678 if creases.is_empty() {
15679 return;
15680 }
15681
15682 let mut buffers_affected = HashSet::default();
15683 let multi_buffer = self.buffer().read(cx);
15684 for crease in &creases {
15685 if let Some((_, buffer, _)) =
15686 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15687 {
15688 buffers_affected.insert(buffer.read(cx).remote_id());
15689 };
15690 }
15691
15692 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15693
15694 if auto_scroll {
15695 self.request_autoscroll(Autoscroll::fit(), cx);
15696 }
15697
15698 cx.notify();
15699
15700 self.scrollbar_marker_state.dirty = true;
15701 self.folds_did_change(cx);
15702 }
15703
15704 /// Removes any folds whose ranges intersect any of the given ranges.
15705 pub fn unfold_ranges<T: ToOffset + Clone>(
15706 &mut self,
15707 ranges: &[Range<T>],
15708 inclusive: bool,
15709 auto_scroll: bool,
15710 cx: &mut Context<Self>,
15711 ) {
15712 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15713 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15714 });
15715 self.folds_did_change(cx);
15716 }
15717
15718 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15719 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15720 return;
15721 }
15722 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15723 self.display_map.update(cx, |display_map, cx| {
15724 display_map.fold_buffers([buffer_id], cx)
15725 });
15726 cx.emit(EditorEvent::BufferFoldToggled {
15727 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15728 folded: true,
15729 });
15730 cx.notify();
15731 }
15732
15733 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15734 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15735 return;
15736 }
15737 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15738 self.display_map.update(cx, |display_map, cx| {
15739 display_map.unfold_buffers([buffer_id], cx);
15740 });
15741 cx.emit(EditorEvent::BufferFoldToggled {
15742 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15743 folded: false,
15744 });
15745 cx.notify();
15746 }
15747
15748 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15749 self.display_map.read(cx).is_buffer_folded(buffer)
15750 }
15751
15752 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15753 self.display_map.read(cx).folded_buffers()
15754 }
15755
15756 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15757 self.display_map.update(cx, |display_map, cx| {
15758 display_map.disable_header_for_buffer(buffer_id, cx);
15759 });
15760 cx.notify();
15761 }
15762
15763 /// Removes any folds with the given ranges.
15764 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15765 &mut self,
15766 ranges: &[Range<T>],
15767 type_id: TypeId,
15768 auto_scroll: bool,
15769 cx: &mut Context<Self>,
15770 ) {
15771 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15772 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15773 });
15774 self.folds_did_change(cx);
15775 }
15776
15777 fn remove_folds_with<T: ToOffset + Clone>(
15778 &mut self,
15779 ranges: &[Range<T>],
15780 auto_scroll: bool,
15781 cx: &mut Context<Self>,
15782 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15783 ) {
15784 if ranges.is_empty() {
15785 return;
15786 }
15787
15788 let mut buffers_affected = HashSet::default();
15789 let multi_buffer = self.buffer().read(cx);
15790 for range in ranges {
15791 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15792 buffers_affected.insert(buffer.read(cx).remote_id());
15793 };
15794 }
15795
15796 self.display_map.update(cx, update);
15797
15798 if auto_scroll {
15799 self.request_autoscroll(Autoscroll::fit(), cx);
15800 }
15801
15802 cx.notify();
15803 self.scrollbar_marker_state.dirty = true;
15804 self.active_indent_guides_state.dirty = true;
15805 }
15806
15807 pub fn update_fold_widths(
15808 &mut self,
15809 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15810 cx: &mut Context<Self>,
15811 ) -> bool {
15812 self.display_map
15813 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15814 }
15815
15816 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15817 self.display_map.read(cx).fold_placeholder.clone()
15818 }
15819
15820 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15821 self.buffer.update(cx, |buffer, cx| {
15822 buffer.set_all_diff_hunks_expanded(cx);
15823 });
15824 }
15825
15826 pub fn expand_all_diff_hunks(
15827 &mut self,
15828 _: &ExpandAllDiffHunks,
15829 _window: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 self.buffer.update(cx, |buffer, cx| {
15833 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15834 });
15835 }
15836
15837 pub fn toggle_selected_diff_hunks(
15838 &mut self,
15839 _: &ToggleSelectedDiffHunks,
15840 _window: &mut Window,
15841 cx: &mut Context<Self>,
15842 ) {
15843 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15844 self.toggle_diff_hunks_in_ranges(ranges, cx);
15845 }
15846
15847 pub fn diff_hunks_in_ranges<'a>(
15848 &'a self,
15849 ranges: &'a [Range<Anchor>],
15850 buffer: &'a MultiBufferSnapshot,
15851 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15852 ranges.iter().flat_map(move |range| {
15853 let end_excerpt_id = range.end.excerpt_id;
15854 let range = range.to_point(buffer);
15855 let mut peek_end = range.end;
15856 if range.end.row < buffer.max_row().0 {
15857 peek_end = Point::new(range.end.row + 1, 0);
15858 }
15859 buffer
15860 .diff_hunks_in_range(range.start..peek_end)
15861 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15862 })
15863 }
15864
15865 pub fn has_stageable_diff_hunks_in_ranges(
15866 &self,
15867 ranges: &[Range<Anchor>],
15868 snapshot: &MultiBufferSnapshot,
15869 ) -> bool {
15870 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15871 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15872 }
15873
15874 pub fn toggle_staged_selected_diff_hunks(
15875 &mut self,
15876 _: &::git::ToggleStaged,
15877 _: &mut Window,
15878 cx: &mut Context<Self>,
15879 ) {
15880 let snapshot = self.buffer.read(cx).snapshot(cx);
15881 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15882 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15883 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15884 }
15885
15886 pub fn set_render_diff_hunk_controls(
15887 &mut self,
15888 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15889 cx: &mut Context<Self>,
15890 ) {
15891 self.render_diff_hunk_controls = render_diff_hunk_controls;
15892 cx.notify();
15893 }
15894
15895 pub fn stage_and_next(
15896 &mut self,
15897 _: &::git::StageAndNext,
15898 window: &mut Window,
15899 cx: &mut Context<Self>,
15900 ) {
15901 self.do_stage_or_unstage_and_next(true, window, cx);
15902 }
15903
15904 pub fn unstage_and_next(
15905 &mut self,
15906 _: &::git::UnstageAndNext,
15907 window: &mut Window,
15908 cx: &mut Context<Self>,
15909 ) {
15910 self.do_stage_or_unstage_and_next(false, window, cx);
15911 }
15912
15913 pub fn stage_or_unstage_diff_hunks(
15914 &mut self,
15915 stage: bool,
15916 ranges: Vec<Range<Anchor>>,
15917 cx: &mut Context<Self>,
15918 ) {
15919 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15920 cx.spawn(async move |this, cx| {
15921 task.await?;
15922 this.update(cx, |this, cx| {
15923 let snapshot = this.buffer.read(cx).snapshot(cx);
15924 let chunk_by = this
15925 .diff_hunks_in_ranges(&ranges, &snapshot)
15926 .chunk_by(|hunk| hunk.buffer_id);
15927 for (buffer_id, hunks) in &chunk_by {
15928 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15929 }
15930 })
15931 })
15932 .detach_and_log_err(cx);
15933 }
15934
15935 fn save_buffers_for_ranges_if_needed(
15936 &mut self,
15937 ranges: &[Range<Anchor>],
15938 cx: &mut Context<Editor>,
15939 ) -> Task<Result<()>> {
15940 let multibuffer = self.buffer.read(cx);
15941 let snapshot = multibuffer.read(cx);
15942 let buffer_ids: HashSet<_> = ranges
15943 .iter()
15944 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15945 .collect();
15946 drop(snapshot);
15947
15948 let mut buffers = HashSet::default();
15949 for buffer_id in buffer_ids {
15950 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15951 let buffer = buffer_entity.read(cx);
15952 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15953 {
15954 buffers.insert(buffer_entity);
15955 }
15956 }
15957 }
15958
15959 if let Some(project) = &self.project {
15960 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15961 } else {
15962 Task::ready(Ok(()))
15963 }
15964 }
15965
15966 fn do_stage_or_unstage_and_next(
15967 &mut self,
15968 stage: bool,
15969 window: &mut Window,
15970 cx: &mut Context<Self>,
15971 ) {
15972 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15973
15974 if ranges.iter().any(|range| range.start != range.end) {
15975 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15976 return;
15977 }
15978
15979 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15980 let snapshot = self.snapshot(window, cx);
15981 let position = self.selections.newest::<Point>(cx).head();
15982 let mut row = snapshot
15983 .buffer_snapshot
15984 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15985 .find(|hunk| hunk.row_range.start.0 > position.row)
15986 .map(|hunk| hunk.row_range.start);
15987
15988 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15989 // Outside of the project diff editor, wrap around to the beginning.
15990 if !all_diff_hunks_expanded {
15991 row = row.or_else(|| {
15992 snapshot
15993 .buffer_snapshot
15994 .diff_hunks_in_range(Point::zero()..position)
15995 .find(|hunk| hunk.row_range.end.0 < position.row)
15996 .map(|hunk| hunk.row_range.start)
15997 });
15998 }
15999
16000 if let Some(row) = row {
16001 let destination = Point::new(row.0, 0);
16002 let autoscroll = Autoscroll::center();
16003
16004 self.unfold_ranges(&[destination..destination], false, false, cx);
16005 self.change_selections(Some(autoscroll), window, cx, |s| {
16006 s.select_ranges([destination..destination]);
16007 });
16008 }
16009 }
16010
16011 fn do_stage_or_unstage(
16012 &self,
16013 stage: bool,
16014 buffer_id: BufferId,
16015 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16016 cx: &mut App,
16017 ) -> Option<()> {
16018 let project = self.project.as_ref()?;
16019 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16020 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16021 let buffer_snapshot = buffer.read(cx).snapshot();
16022 let file_exists = buffer_snapshot
16023 .file()
16024 .is_some_and(|file| file.disk_state().exists());
16025 diff.update(cx, |diff, cx| {
16026 diff.stage_or_unstage_hunks(
16027 stage,
16028 &hunks
16029 .map(|hunk| buffer_diff::DiffHunk {
16030 buffer_range: hunk.buffer_range,
16031 diff_base_byte_range: hunk.diff_base_byte_range,
16032 secondary_status: hunk.secondary_status,
16033 range: Point::zero()..Point::zero(), // unused
16034 })
16035 .collect::<Vec<_>>(),
16036 &buffer_snapshot,
16037 file_exists,
16038 cx,
16039 )
16040 });
16041 None
16042 }
16043
16044 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16045 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16046 self.buffer
16047 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16048 }
16049
16050 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16051 self.buffer.update(cx, |buffer, cx| {
16052 let ranges = vec![Anchor::min()..Anchor::max()];
16053 if !buffer.all_diff_hunks_expanded()
16054 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16055 {
16056 buffer.collapse_diff_hunks(ranges, cx);
16057 true
16058 } else {
16059 false
16060 }
16061 })
16062 }
16063
16064 fn toggle_diff_hunks_in_ranges(
16065 &mut self,
16066 ranges: Vec<Range<Anchor>>,
16067 cx: &mut Context<Editor>,
16068 ) {
16069 self.buffer.update(cx, |buffer, cx| {
16070 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16071 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16072 })
16073 }
16074
16075 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16076 self.buffer.update(cx, |buffer, cx| {
16077 let snapshot = buffer.snapshot(cx);
16078 let excerpt_id = range.end.excerpt_id;
16079 let point_range = range.to_point(&snapshot);
16080 let expand = !buffer.single_hunk_is_expanded(range, cx);
16081 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16082 })
16083 }
16084
16085 pub(crate) fn apply_all_diff_hunks(
16086 &mut self,
16087 _: &ApplyAllDiffHunks,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16092
16093 let buffers = self.buffer.read(cx).all_buffers();
16094 for branch_buffer in buffers {
16095 branch_buffer.update(cx, |branch_buffer, cx| {
16096 branch_buffer.merge_into_base(Vec::new(), cx);
16097 });
16098 }
16099
16100 if let Some(project) = self.project.clone() {
16101 self.save(true, project, window, cx).detach_and_log_err(cx);
16102 }
16103 }
16104
16105 pub(crate) fn apply_selected_diff_hunks(
16106 &mut self,
16107 _: &ApplyDiffHunk,
16108 window: &mut Window,
16109 cx: &mut Context<Self>,
16110 ) {
16111 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16112 let snapshot = self.snapshot(window, cx);
16113 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16114 let mut ranges_by_buffer = HashMap::default();
16115 self.transact(window, cx, |editor, _window, cx| {
16116 for hunk in hunks {
16117 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16118 ranges_by_buffer
16119 .entry(buffer.clone())
16120 .or_insert_with(Vec::new)
16121 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16122 }
16123 }
16124
16125 for (buffer, ranges) in ranges_by_buffer {
16126 buffer.update(cx, |buffer, cx| {
16127 buffer.merge_into_base(ranges, cx);
16128 });
16129 }
16130 });
16131
16132 if let Some(project) = self.project.clone() {
16133 self.save(true, project, window, cx).detach_and_log_err(cx);
16134 }
16135 }
16136
16137 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16138 if hovered != self.gutter_hovered {
16139 self.gutter_hovered = hovered;
16140 cx.notify();
16141 }
16142 }
16143
16144 pub fn insert_blocks(
16145 &mut self,
16146 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16147 autoscroll: Option<Autoscroll>,
16148 cx: &mut Context<Self>,
16149 ) -> Vec<CustomBlockId> {
16150 let blocks = self
16151 .display_map
16152 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16153 if let Some(autoscroll) = autoscroll {
16154 self.request_autoscroll(autoscroll, cx);
16155 }
16156 cx.notify();
16157 blocks
16158 }
16159
16160 pub fn resize_blocks(
16161 &mut self,
16162 heights: HashMap<CustomBlockId, u32>,
16163 autoscroll: Option<Autoscroll>,
16164 cx: &mut Context<Self>,
16165 ) {
16166 self.display_map
16167 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16168 if let Some(autoscroll) = autoscroll {
16169 self.request_autoscroll(autoscroll, cx);
16170 }
16171 cx.notify();
16172 }
16173
16174 pub fn replace_blocks(
16175 &mut self,
16176 renderers: HashMap<CustomBlockId, RenderBlock>,
16177 autoscroll: Option<Autoscroll>,
16178 cx: &mut Context<Self>,
16179 ) {
16180 self.display_map
16181 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16182 if let Some(autoscroll) = autoscroll {
16183 self.request_autoscroll(autoscroll, cx);
16184 }
16185 cx.notify();
16186 }
16187
16188 pub fn remove_blocks(
16189 &mut self,
16190 block_ids: HashSet<CustomBlockId>,
16191 autoscroll: Option<Autoscroll>,
16192 cx: &mut Context<Self>,
16193 ) {
16194 self.display_map.update(cx, |display_map, cx| {
16195 display_map.remove_blocks(block_ids, cx)
16196 });
16197 if let Some(autoscroll) = autoscroll {
16198 self.request_autoscroll(autoscroll, cx);
16199 }
16200 cx.notify();
16201 }
16202
16203 pub fn row_for_block(
16204 &self,
16205 block_id: CustomBlockId,
16206 cx: &mut Context<Self>,
16207 ) -> Option<DisplayRow> {
16208 self.display_map
16209 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16210 }
16211
16212 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16213 self.focused_block = Some(focused_block);
16214 }
16215
16216 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16217 self.focused_block.take()
16218 }
16219
16220 pub fn insert_creases(
16221 &mut self,
16222 creases: impl IntoIterator<Item = Crease<Anchor>>,
16223 cx: &mut Context<Self>,
16224 ) -> Vec<CreaseId> {
16225 self.display_map
16226 .update(cx, |map, cx| map.insert_creases(creases, cx))
16227 }
16228
16229 pub fn remove_creases(
16230 &mut self,
16231 ids: impl IntoIterator<Item = CreaseId>,
16232 cx: &mut Context<Self>,
16233 ) {
16234 self.display_map
16235 .update(cx, |map, cx| map.remove_creases(ids, cx));
16236 }
16237
16238 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16239 self.display_map
16240 .update(cx, |map, cx| map.snapshot(cx))
16241 .longest_row()
16242 }
16243
16244 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16245 self.display_map
16246 .update(cx, |map, cx| map.snapshot(cx))
16247 .max_point()
16248 }
16249
16250 pub fn text(&self, cx: &App) -> String {
16251 self.buffer.read(cx).read(cx).text()
16252 }
16253
16254 pub fn is_empty(&self, cx: &App) -> bool {
16255 self.buffer.read(cx).read(cx).is_empty()
16256 }
16257
16258 pub fn text_option(&self, cx: &App) -> Option<String> {
16259 let text = self.text(cx);
16260 let text = text.trim();
16261
16262 if text.is_empty() {
16263 return None;
16264 }
16265
16266 Some(text.to_string())
16267 }
16268
16269 pub fn set_text(
16270 &mut self,
16271 text: impl Into<Arc<str>>,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) {
16275 self.transact(window, cx, |this, _, cx| {
16276 this.buffer
16277 .read(cx)
16278 .as_singleton()
16279 .expect("you can only call set_text on editors for singleton buffers")
16280 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16281 });
16282 }
16283
16284 pub fn display_text(&self, cx: &mut App) -> String {
16285 self.display_map
16286 .update(cx, |map, cx| map.snapshot(cx))
16287 .text()
16288 }
16289
16290 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16291 let mut wrap_guides = smallvec::smallvec![];
16292
16293 if self.show_wrap_guides == Some(false) {
16294 return wrap_guides;
16295 }
16296
16297 let settings = self.buffer.read(cx).language_settings(cx);
16298 if settings.show_wrap_guides {
16299 match self.soft_wrap_mode(cx) {
16300 SoftWrap::Column(soft_wrap) => {
16301 wrap_guides.push((soft_wrap as usize, true));
16302 }
16303 SoftWrap::Bounded(soft_wrap) => {
16304 wrap_guides.push((soft_wrap as usize, true));
16305 }
16306 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16307 }
16308 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16309 }
16310
16311 wrap_guides
16312 }
16313
16314 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16315 let settings = self.buffer.read(cx).language_settings(cx);
16316 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16317 match mode {
16318 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16319 SoftWrap::None
16320 }
16321 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16322 language_settings::SoftWrap::PreferredLineLength => {
16323 SoftWrap::Column(settings.preferred_line_length)
16324 }
16325 language_settings::SoftWrap::Bounded => {
16326 SoftWrap::Bounded(settings.preferred_line_length)
16327 }
16328 }
16329 }
16330
16331 pub fn set_soft_wrap_mode(
16332 &mut self,
16333 mode: language_settings::SoftWrap,
16334
16335 cx: &mut Context<Self>,
16336 ) {
16337 self.soft_wrap_mode_override = Some(mode);
16338 cx.notify();
16339 }
16340
16341 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16342 self.hard_wrap = hard_wrap;
16343 cx.notify();
16344 }
16345
16346 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16347 self.text_style_refinement = Some(style);
16348 }
16349
16350 /// called by the Element so we know what style we were most recently rendered with.
16351 pub(crate) fn set_style(
16352 &mut self,
16353 style: EditorStyle,
16354 window: &mut Window,
16355 cx: &mut Context<Self>,
16356 ) {
16357 let rem_size = window.rem_size();
16358 self.display_map.update(cx, |map, cx| {
16359 map.set_font(
16360 style.text.font(),
16361 style.text.font_size.to_pixels(rem_size),
16362 cx,
16363 )
16364 });
16365 self.style = Some(style);
16366 }
16367
16368 pub fn style(&self) -> Option<&EditorStyle> {
16369 self.style.as_ref()
16370 }
16371
16372 // Called by the element. This method is not designed to be called outside of the editor
16373 // element's layout code because it does not notify when rewrapping is computed synchronously.
16374 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16375 self.display_map
16376 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16377 }
16378
16379 pub fn set_soft_wrap(&mut self) {
16380 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16381 }
16382
16383 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16384 if self.soft_wrap_mode_override.is_some() {
16385 self.soft_wrap_mode_override.take();
16386 } else {
16387 let soft_wrap = match self.soft_wrap_mode(cx) {
16388 SoftWrap::GitDiff => return,
16389 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16390 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16391 language_settings::SoftWrap::None
16392 }
16393 };
16394 self.soft_wrap_mode_override = Some(soft_wrap);
16395 }
16396 cx.notify();
16397 }
16398
16399 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16400 let Some(workspace) = self.workspace() else {
16401 return;
16402 };
16403 let fs = workspace.read(cx).app_state().fs.clone();
16404 let current_show = TabBarSettings::get_global(cx).show;
16405 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16406 setting.show = Some(!current_show);
16407 });
16408 }
16409
16410 pub fn toggle_indent_guides(
16411 &mut self,
16412 _: &ToggleIndentGuides,
16413 _: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) {
16416 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16417 self.buffer
16418 .read(cx)
16419 .language_settings(cx)
16420 .indent_guides
16421 .enabled
16422 });
16423 self.show_indent_guides = Some(!currently_enabled);
16424 cx.notify();
16425 }
16426
16427 fn should_show_indent_guides(&self) -> Option<bool> {
16428 self.show_indent_guides
16429 }
16430
16431 pub fn toggle_line_numbers(
16432 &mut self,
16433 _: &ToggleLineNumbers,
16434 _: &mut Window,
16435 cx: &mut Context<Self>,
16436 ) {
16437 let mut editor_settings = EditorSettings::get_global(cx).clone();
16438 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16439 EditorSettings::override_global(editor_settings, cx);
16440 }
16441
16442 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16443 if let Some(show_line_numbers) = self.show_line_numbers {
16444 return show_line_numbers;
16445 }
16446 EditorSettings::get_global(cx).gutter.line_numbers
16447 }
16448
16449 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16450 self.use_relative_line_numbers
16451 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16452 }
16453
16454 pub fn toggle_relative_line_numbers(
16455 &mut self,
16456 _: &ToggleRelativeLineNumbers,
16457 _: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) {
16460 let is_relative = self.should_use_relative_line_numbers(cx);
16461 self.set_relative_line_number(Some(!is_relative), cx)
16462 }
16463
16464 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16465 self.use_relative_line_numbers = is_relative;
16466 cx.notify();
16467 }
16468
16469 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16470 self.show_gutter = show_gutter;
16471 cx.notify();
16472 }
16473
16474 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16475 self.show_scrollbars = show_scrollbars;
16476 cx.notify();
16477 }
16478
16479 pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
16480 self.disable_scrolling = true;
16481 cx.notify();
16482 }
16483
16484 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16485 self.show_line_numbers = Some(show_line_numbers);
16486 cx.notify();
16487 }
16488
16489 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16490 self.disable_expand_excerpt_buttons = true;
16491 cx.notify();
16492 }
16493
16494 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16495 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16496 cx.notify();
16497 }
16498
16499 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16500 self.show_code_actions = Some(show_code_actions);
16501 cx.notify();
16502 }
16503
16504 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16505 self.show_runnables = Some(show_runnables);
16506 cx.notify();
16507 }
16508
16509 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16510 self.show_breakpoints = Some(show_breakpoints);
16511 cx.notify();
16512 }
16513
16514 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16515 if self.display_map.read(cx).masked != masked {
16516 self.display_map.update(cx, |map, _| map.masked = masked);
16517 }
16518 cx.notify()
16519 }
16520
16521 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16522 self.show_wrap_guides = Some(show_wrap_guides);
16523 cx.notify();
16524 }
16525
16526 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16527 self.show_indent_guides = Some(show_indent_guides);
16528 cx.notify();
16529 }
16530
16531 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16532 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16533 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16534 if let Some(dir) = file.abs_path(cx).parent() {
16535 return Some(dir.to_owned());
16536 }
16537 }
16538
16539 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16540 return Some(project_path.path.to_path_buf());
16541 }
16542 }
16543
16544 None
16545 }
16546
16547 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16548 self.active_excerpt(cx)?
16549 .1
16550 .read(cx)
16551 .file()
16552 .and_then(|f| f.as_local())
16553 }
16554
16555 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16556 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16557 let buffer = buffer.read(cx);
16558 if let Some(project_path) = buffer.project_path(cx) {
16559 let project = self.project.as_ref()?.read(cx);
16560 project.absolute_path(&project_path, cx)
16561 } else {
16562 buffer
16563 .file()
16564 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16565 }
16566 })
16567 }
16568
16569 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16570 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16571 let project_path = buffer.read(cx).project_path(cx)?;
16572 let project = self.project.as_ref()?.read(cx);
16573 let entry = project.entry_for_path(&project_path, cx)?;
16574 let path = entry.path.to_path_buf();
16575 Some(path)
16576 })
16577 }
16578
16579 pub fn reveal_in_finder(
16580 &mut self,
16581 _: &RevealInFileManager,
16582 _window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 if let Some(target) = self.target_file(cx) {
16586 cx.reveal_path(&target.abs_path(cx));
16587 }
16588 }
16589
16590 pub fn copy_path(
16591 &mut self,
16592 _: &zed_actions::workspace::CopyPath,
16593 _window: &mut Window,
16594 cx: &mut Context<Self>,
16595 ) {
16596 if let Some(path) = self.target_file_abs_path(cx) {
16597 if let Some(path) = path.to_str() {
16598 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16599 }
16600 }
16601 }
16602
16603 pub fn copy_relative_path(
16604 &mut self,
16605 _: &zed_actions::workspace::CopyRelativePath,
16606 _window: &mut Window,
16607 cx: &mut Context<Self>,
16608 ) {
16609 if let Some(path) = self.target_file_path(cx) {
16610 if let Some(path) = path.to_str() {
16611 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16612 }
16613 }
16614 }
16615
16616 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16617 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16618 buffer.read(cx).project_path(cx)
16619 } else {
16620 None
16621 }
16622 }
16623
16624 // Returns true if the editor handled a go-to-line request
16625 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16626 maybe!({
16627 let breakpoint_store = self.breakpoint_store.as_ref()?;
16628
16629 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16630 else {
16631 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16632 return None;
16633 };
16634
16635 let position = active_stack_frame.position;
16636 let buffer_id = position.buffer_id?;
16637 let snapshot = self
16638 .project
16639 .as_ref()?
16640 .read(cx)
16641 .buffer_for_id(buffer_id, cx)?
16642 .read(cx)
16643 .snapshot();
16644
16645 let mut handled = false;
16646 for (id, ExcerptRange { context, .. }) in
16647 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16648 {
16649 if context.start.cmp(&position, &snapshot).is_ge()
16650 || context.end.cmp(&position, &snapshot).is_lt()
16651 {
16652 continue;
16653 }
16654 let snapshot = self.buffer.read(cx).snapshot(cx);
16655 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16656
16657 handled = true;
16658 self.clear_row_highlights::<DebugCurrentRowHighlight>();
16659 self.go_to_line::<DebugCurrentRowHighlight>(
16660 multibuffer_anchor,
16661 Some(cx.theme().colors().editor_debugger_active_line_background),
16662 window,
16663 cx,
16664 );
16665
16666 cx.notify();
16667 }
16668
16669 handled.then_some(())
16670 })
16671 .is_some()
16672 }
16673
16674 pub fn copy_file_name_without_extension(
16675 &mut self,
16676 _: &CopyFileNameWithoutExtension,
16677 _: &mut Window,
16678 cx: &mut Context<Self>,
16679 ) {
16680 if let Some(file) = self.target_file(cx) {
16681 if let Some(file_stem) = file.path().file_stem() {
16682 if let Some(name) = file_stem.to_str() {
16683 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16684 }
16685 }
16686 }
16687 }
16688
16689 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16690 if let Some(file) = self.target_file(cx) {
16691 if let Some(file_name) = file.path().file_name() {
16692 if let Some(name) = file_name.to_str() {
16693 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16694 }
16695 }
16696 }
16697 }
16698
16699 pub fn toggle_git_blame(
16700 &mut self,
16701 _: &::git::Blame,
16702 window: &mut Window,
16703 cx: &mut Context<Self>,
16704 ) {
16705 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16706
16707 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16708 self.start_git_blame(true, window, cx);
16709 }
16710
16711 cx.notify();
16712 }
16713
16714 pub fn toggle_git_blame_inline(
16715 &mut self,
16716 _: &ToggleGitBlameInline,
16717 window: &mut Window,
16718 cx: &mut Context<Self>,
16719 ) {
16720 self.toggle_git_blame_inline_internal(true, window, cx);
16721 cx.notify();
16722 }
16723
16724 pub fn open_git_blame_commit(
16725 &mut self,
16726 _: &OpenGitBlameCommit,
16727 window: &mut Window,
16728 cx: &mut Context<Self>,
16729 ) {
16730 self.open_git_blame_commit_internal(window, cx);
16731 }
16732
16733 fn open_git_blame_commit_internal(
16734 &mut self,
16735 window: &mut Window,
16736 cx: &mut Context<Self>,
16737 ) -> Option<()> {
16738 let blame = self.blame.as_ref()?;
16739 let snapshot = self.snapshot(window, cx);
16740 let cursor = self.selections.newest::<Point>(cx).head();
16741 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16742 let blame_entry = blame
16743 .update(cx, |blame, cx| {
16744 blame
16745 .blame_for_rows(
16746 &[RowInfo {
16747 buffer_id: Some(buffer.remote_id()),
16748 buffer_row: Some(point.row),
16749 ..Default::default()
16750 }],
16751 cx,
16752 )
16753 .next()
16754 })
16755 .flatten()?;
16756 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16757 let repo = blame.read(cx).repository(cx)?;
16758 let workspace = self.workspace()?.downgrade();
16759 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16760 None
16761 }
16762
16763 pub fn git_blame_inline_enabled(&self) -> bool {
16764 self.git_blame_inline_enabled
16765 }
16766
16767 pub fn toggle_selection_menu(
16768 &mut self,
16769 _: &ToggleSelectionMenu,
16770 _: &mut Window,
16771 cx: &mut Context<Self>,
16772 ) {
16773 self.show_selection_menu = self
16774 .show_selection_menu
16775 .map(|show_selections_menu| !show_selections_menu)
16776 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16777
16778 cx.notify();
16779 }
16780
16781 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16782 self.show_selection_menu
16783 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16784 }
16785
16786 fn start_git_blame(
16787 &mut self,
16788 user_triggered: bool,
16789 window: &mut Window,
16790 cx: &mut Context<Self>,
16791 ) {
16792 if let Some(project) = self.project.as_ref() {
16793 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16794 return;
16795 };
16796
16797 if buffer.read(cx).file().is_none() {
16798 return;
16799 }
16800
16801 let focused = self.focus_handle(cx).contains_focused(window, cx);
16802
16803 let project = project.clone();
16804 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16805 self.blame_subscription =
16806 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16807 self.blame = Some(blame);
16808 }
16809 }
16810
16811 fn toggle_git_blame_inline_internal(
16812 &mut self,
16813 user_triggered: bool,
16814 window: &mut Window,
16815 cx: &mut Context<Self>,
16816 ) {
16817 if self.git_blame_inline_enabled {
16818 self.git_blame_inline_enabled = false;
16819 self.show_git_blame_inline = false;
16820 self.show_git_blame_inline_delay_task.take();
16821 } else {
16822 self.git_blame_inline_enabled = true;
16823 self.start_git_blame_inline(user_triggered, window, cx);
16824 }
16825
16826 cx.notify();
16827 }
16828
16829 fn start_git_blame_inline(
16830 &mut self,
16831 user_triggered: bool,
16832 window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) {
16835 self.start_git_blame(user_triggered, window, cx);
16836
16837 if ProjectSettings::get_global(cx)
16838 .git
16839 .inline_blame_delay()
16840 .is_some()
16841 {
16842 self.start_inline_blame_timer(window, cx);
16843 } else {
16844 self.show_git_blame_inline = true
16845 }
16846 }
16847
16848 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16849 self.blame.as_ref()
16850 }
16851
16852 pub fn show_git_blame_gutter(&self) -> bool {
16853 self.show_git_blame_gutter
16854 }
16855
16856 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16857 self.show_git_blame_gutter && self.has_blame_entries(cx)
16858 }
16859
16860 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16861 self.show_git_blame_inline
16862 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16863 && !self.newest_selection_head_on_empty_line(cx)
16864 && self.has_blame_entries(cx)
16865 }
16866
16867 fn has_blame_entries(&self, cx: &App) -> bool {
16868 self.blame()
16869 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16870 }
16871
16872 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16873 let cursor_anchor = self.selections.newest_anchor().head();
16874
16875 let snapshot = self.buffer.read(cx).snapshot(cx);
16876 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16877
16878 snapshot.line_len(buffer_row) == 0
16879 }
16880
16881 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16882 let buffer_and_selection = maybe!({
16883 let selection = self.selections.newest::<Point>(cx);
16884 let selection_range = selection.range();
16885
16886 let multi_buffer = self.buffer().read(cx);
16887 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16888 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16889
16890 let (buffer, range, _) = if selection.reversed {
16891 buffer_ranges.first()
16892 } else {
16893 buffer_ranges.last()
16894 }?;
16895
16896 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16897 ..text::ToPoint::to_point(&range.end, &buffer).row;
16898 Some((
16899 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16900 selection,
16901 ))
16902 });
16903
16904 let Some((buffer, selection)) = buffer_and_selection else {
16905 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16906 };
16907
16908 let Some(project) = self.project.as_ref() else {
16909 return Task::ready(Err(anyhow!("editor does not have project")));
16910 };
16911
16912 project.update(cx, |project, cx| {
16913 project.get_permalink_to_line(&buffer, selection, cx)
16914 })
16915 }
16916
16917 pub fn copy_permalink_to_line(
16918 &mut self,
16919 _: &CopyPermalinkToLine,
16920 window: &mut Window,
16921 cx: &mut Context<Self>,
16922 ) {
16923 let permalink_task = self.get_permalink_to_line(cx);
16924 let workspace = self.workspace();
16925
16926 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16927 Ok(permalink) => {
16928 cx.update(|_, cx| {
16929 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16930 })
16931 .ok();
16932 }
16933 Err(err) => {
16934 let message = format!("Failed to copy permalink: {err}");
16935
16936 Err::<(), anyhow::Error>(err).log_err();
16937
16938 if let Some(workspace) = workspace {
16939 workspace
16940 .update_in(cx, |workspace, _, cx| {
16941 struct CopyPermalinkToLine;
16942
16943 workspace.show_toast(
16944 Toast::new(
16945 NotificationId::unique::<CopyPermalinkToLine>(),
16946 message,
16947 ),
16948 cx,
16949 )
16950 })
16951 .ok();
16952 }
16953 }
16954 })
16955 .detach();
16956 }
16957
16958 pub fn copy_file_location(
16959 &mut self,
16960 _: &CopyFileLocation,
16961 _: &mut Window,
16962 cx: &mut Context<Self>,
16963 ) {
16964 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16965 if let Some(file) = self.target_file(cx) {
16966 if let Some(path) = file.path().to_str() {
16967 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16968 }
16969 }
16970 }
16971
16972 pub fn open_permalink_to_line(
16973 &mut self,
16974 _: &OpenPermalinkToLine,
16975 window: &mut Window,
16976 cx: &mut Context<Self>,
16977 ) {
16978 let permalink_task = self.get_permalink_to_line(cx);
16979 let workspace = self.workspace();
16980
16981 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16982 Ok(permalink) => {
16983 cx.update(|_, cx| {
16984 cx.open_url(permalink.as_ref());
16985 })
16986 .ok();
16987 }
16988 Err(err) => {
16989 let message = format!("Failed to open permalink: {err}");
16990
16991 Err::<(), anyhow::Error>(err).log_err();
16992
16993 if let Some(workspace) = workspace {
16994 workspace
16995 .update(cx, |workspace, cx| {
16996 struct OpenPermalinkToLine;
16997
16998 workspace.show_toast(
16999 Toast::new(
17000 NotificationId::unique::<OpenPermalinkToLine>(),
17001 message,
17002 ),
17003 cx,
17004 )
17005 })
17006 .ok();
17007 }
17008 }
17009 })
17010 .detach();
17011 }
17012
17013 pub fn insert_uuid_v4(
17014 &mut self,
17015 _: &InsertUuidV4,
17016 window: &mut Window,
17017 cx: &mut Context<Self>,
17018 ) {
17019 self.insert_uuid(UuidVersion::V4, window, cx);
17020 }
17021
17022 pub fn insert_uuid_v7(
17023 &mut self,
17024 _: &InsertUuidV7,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 self.insert_uuid(UuidVersion::V7, window, cx);
17029 }
17030
17031 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17032 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17033 self.transact(window, cx, |this, window, cx| {
17034 let edits = this
17035 .selections
17036 .all::<Point>(cx)
17037 .into_iter()
17038 .map(|selection| {
17039 let uuid = match version {
17040 UuidVersion::V4 => uuid::Uuid::new_v4(),
17041 UuidVersion::V7 => uuid::Uuid::now_v7(),
17042 };
17043
17044 (selection.range(), uuid.to_string())
17045 });
17046 this.edit(edits, cx);
17047 this.refresh_inline_completion(true, false, window, cx);
17048 });
17049 }
17050
17051 pub fn open_selections_in_multibuffer(
17052 &mut self,
17053 _: &OpenSelectionsInMultibuffer,
17054 window: &mut Window,
17055 cx: &mut Context<Self>,
17056 ) {
17057 let multibuffer = self.buffer.read(cx);
17058
17059 let Some(buffer) = multibuffer.as_singleton() else {
17060 return;
17061 };
17062
17063 let Some(workspace) = self.workspace() else {
17064 return;
17065 };
17066
17067 let locations = self
17068 .selections
17069 .disjoint_anchors()
17070 .iter()
17071 .map(|range| Location {
17072 buffer: buffer.clone(),
17073 range: range.start.text_anchor..range.end.text_anchor,
17074 })
17075 .collect::<Vec<_>>();
17076
17077 let title = multibuffer.title(cx).to_string();
17078
17079 cx.spawn_in(window, async move |_, cx| {
17080 workspace.update_in(cx, |workspace, window, cx| {
17081 Self::open_locations_in_multibuffer(
17082 workspace,
17083 locations,
17084 format!("Selections for '{title}'"),
17085 false,
17086 MultibufferSelectionMode::All,
17087 window,
17088 cx,
17089 );
17090 })
17091 })
17092 .detach();
17093 }
17094
17095 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17096 /// last highlight added will be used.
17097 ///
17098 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17099 pub fn highlight_rows<T: 'static>(
17100 &mut self,
17101 range: Range<Anchor>,
17102 color: Hsla,
17103 options: RowHighlightOptions,
17104 cx: &mut Context<Self>,
17105 ) {
17106 let snapshot = self.buffer().read(cx).snapshot(cx);
17107 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17108 let ix = row_highlights.binary_search_by(|highlight| {
17109 Ordering::Equal
17110 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17111 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17112 });
17113
17114 if let Err(mut ix) = ix {
17115 let index = post_inc(&mut self.highlight_order);
17116
17117 // If this range intersects with the preceding highlight, then merge it with
17118 // the preceding highlight. Otherwise insert a new highlight.
17119 let mut merged = false;
17120 if ix > 0 {
17121 let prev_highlight = &mut row_highlights[ix - 1];
17122 if prev_highlight
17123 .range
17124 .end
17125 .cmp(&range.start, &snapshot)
17126 .is_ge()
17127 {
17128 ix -= 1;
17129 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17130 prev_highlight.range.end = range.end;
17131 }
17132 merged = true;
17133 prev_highlight.index = index;
17134 prev_highlight.color = color;
17135 prev_highlight.options = options;
17136 }
17137 }
17138
17139 if !merged {
17140 row_highlights.insert(
17141 ix,
17142 RowHighlight {
17143 range: range.clone(),
17144 index,
17145 color,
17146 options,
17147 type_id: TypeId::of::<T>(),
17148 },
17149 );
17150 }
17151
17152 // If any of the following highlights intersect with this one, merge them.
17153 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17154 let highlight = &row_highlights[ix];
17155 if next_highlight
17156 .range
17157 .start
17158 .cmp(&highlight.range.end, &snapshot)
17159 .is_le()
17160 {
17161 if next_highlight
17162 .range
17163 .end
17164 .cmp(&highlight.range.end, &snapshot)
17165 .is_gt()
17166 {
17167 row_highlights[ix].range.end = next_highlight.range.end;
17168 }
17169 row_highlights.remove(ix + 1);
17170 } else {
17171 break;
17172 }
17173 }
17174 }
17175 }
17176
17177 /// Remove any highlighted row ranges of the given type that intersect the
17178 /// given ranges.
17179 pub fn remove_highlighted_rows<T: 'static>(
17180 &mut self,
17181 ranges_to_remove: Vec<Range<Anchor>>,
17182 cx: &mut Context<Self>,
17183 ) {
17184 let snapshot = self.buffer().read(cx).snapshot(cx);
17185 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17186 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17187 row_highlights.retain(|highlight| {
17188 while let Some(range_to_remove) = ranges_to_remove.peek() {
17189 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17190 Ordering::Less | Ordering::Equal => {
17191 ranges_to_remove.next();
17192 }
17193 Ordering::Greater => {
17194 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17195 Ordering::Less | Ordering::Equal => {
17196 return false;
17197 }
17198 Ordering::Greater => break,
17199 }
17200 }
17201 }
17202 }
17203
17204 true
17205 })
17206 }
17207
17208 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17209 pub fn clear_row_highlights<T: 'static>(&mut self) {
17210 self.highlighted_rows.remove(&TypeId::of::<T>());
17211 }
17212
17213 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17214 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17215 self.highlighted_rows
17216 .get(&TypeId::of::<T>())
17217 .map_or(&[] as &[_], |vec| vec.as_slice())
17218 .iter()
17219 .map(|highlight| (highlight.range.clone(), highlight.color))
17220 }
17221
17222 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17223 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17224 /// Allows to ignore certain kinds of highlights.
17225 pub fn highlighted_display_rows(
17226 &self,
17227 window: &mut Window,
17228 cx: &mut App,
17229 ) -> BTreeMap<DisplayRow, LineHighlight> {
17230 let snapshot = self.snapshot(window, cx);
17231 let mut used_highlight_orders = HashMap::default();
17232 self.highlighted_rows
17233 .iter()
17234 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17235 .fold(
17236 BTreeMap::<DisplayRow, LineHighlight>::new(),
17237 |mut unique_rows, highlight| {
17238 let start = highlight.range.start.to_display_point(&snapshot);
17239 let end = highlight.range.end.to_display_point(&snapshot);
17240 let start_row = start.row().0;
17241 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17242 && end.column() == 0
17243 {
17244 end.row().0.saturating_sub(1)
17245 } else {
17246 end.row().0
17247 };
17248 for row in start_row..=end_row {
17249 let used_index =
17250 used_highlight_orders.entry(row).or_insert(highlight.index);
17251 if highlight.index >= *used_index {
17252 *used_index = highlight.index;
17253 unique_rows.insert(
17254 DisplayRow(row),
17255 LineHighlight {
17256 include_gutter: highlight.options.include_gutter,
17257 border: None,
17258 background: highlight.color.into(),
17259 type_id: Some(highlight.type_id),
17260 },
17261 );
17262 }
17263 }
17264 unique_rows
17265 },
17266 )
17267 }
17268
17269 pub fn highlighted_display_row_for_autoscroll(
17270 &self,
17271 snapshot: &DisplaySnapshot,
17272 ) -> Option<DisplayRow> {
17273 self.highlighted_rows
17274 .values()
17275 .flat_map(|highlighted_rows| highlighted_rows.iter())
17276 .filter_map(|highlight| {
17277 if highlight.options.autoscroll {
17278 Some(highlight.range.start.to_display_point(snapshot).row())
17279 } else {
17280 None
17281 }
17282 })
17283 .min()
17284 }
17285
17286 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17287 self.highlight_background::<SearchWithinRange>(
17288 ranges,
17289 |colors| colors.editor_document_highlight_read_background,
17290 cx,
17291 )
17292 }
17293
17294 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17295 self.breadcrumb_header = Some(new_header);
17296 }
17297
17298 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17299 self.clear_background_highlights::<SearchWithinRange>(cx);
17300 }
17301
17302 pub fn highlight_background<T: 'static>(
17303 &mut self,
17304 ranges: &[Range<Anchor>],
17305 color_fetcher: fn(&ThemeColors) -> Hsla,
17306 cx: &mut Context<Self>,
17307 ) {
17308 self.background_highlights
17309 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17310 self.scrollbar_marker_state.dirty = true;
17311 cx.notify();
17312 }
17313
17314 pub fn clear_background_highlights<T: 'static>(
17315 &mut self,
17316 cx: &mut Context<Self>,
17317 ) -> Option<BackgroundHighlight> {
17318 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17319 if !text_highlights.1.is_empty() {
17320 self.scrollbar_marker_state.dirty = true;
17321 cx.notify();
17322 }
17323 Some(text_highlights)
17324 }
17325
17326 pub fn highlight_gutter<T: 'static>(
17327 &mut self,
17328 ranges: &[Range<Anchor>],
17329 color_fetcher: fn(&App) -> Hsla,
17330 cx: &mut Context<Self>,
17331 ) {
17332 self.gutter_highlights
17333 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17334 cx.notify();
17335 }
17336
17337 pub fn clear_gutter_highlights<T: 'static>(
17338 &mut self,
17339 cx: &mut Context<Self>,
17340 ) -> Option<GutterHighlight> {
17341 cx.notify();
17342 self.gutter_highlights.remove(&TypeId::of::<T>())
17343 }
17344
17345 #[cfg(feature = "test-support")]
17346 pub fn all_text_background_highlights(
17347 &self,
17348 window: &mut Window,
17349 cx: &mut Context<Self>,
17350 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17351 let snapshot = self.snapshot(window, cx);
17352 let buffer = &snapshot.buffer_snapshot;
17353 let start = buffer.anchor_before(0);
17354 let end = buffer.anchor_after(buffer.len());
17355 let theme = cx.theme().colors();
17356 self.background_highlights_in_range(start..end, &snapshot, theme)
17357 }
17358
17359 #[cfg(feature = "test-support")]
17360 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17361 let snapshot = self.buffer().read(cx).snapshot(cx);
17362
17363 let highlights = self
17364 .background_highlights
17365 .get(&TypeId::of::<items::BufferSearchHighlights>());
17366
17367 if let Some((_color, ranges)) = highlights {
17368 ranges
17369 .iter()
17370 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17371 .collect_vec()
17372 } else {
17373 vec![]
17374 }
17375 }
17376
17377 fn document_highlights_for_position<'a>(
17378 &'a self,
17379 position: Anchor,
17380 buffer: &'a MultiBufferSnapshot,
17381 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17382 let read_highlights = self
17383 .background_highlights
17384 .get(&TypeId::of::<DocumentHighlightRead>())
17385 .map(|h| &h.1);
17386 let write_highlights = self
17387 .background_highlights
17388 .get(&TypeId::of::<DocumentHighlightWrite>())
17389 .map(|h| &h.1);
17390 let left_position = position.bias_left(buffer);
17391 let right_position = position.bias_right(buffer);
17392 read_highlights
17393 .into_iter()
17394 .chain(write_highlights)
17395 .flat_map(move |ranges| {
17396 let start_ix = match ranges.binary_search_by(|probe| {
17397 let cmp = probe.end.cmp(&left_position, buffer);
17398 if cmp.is_ge() {
17399 Ordering::Greater
17400 } else {
17401 Ordering::Less
17402 }
17403 }) {
17404 Ok(i) | Err(i) => i,
17405 };
17406
17407 ranges[start_ix..]
17408 .iter()
17409 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17410 })
17411 }
17412
17413 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17414 self.background_highlights
17415 .get(&TypeId::of::<T>())
17416 .map_or(false, |(_, highlights)| !highlights.is_empty())
17417 }
17418
17419 pub fn background_highlights_in_range(
17420 &self,
17421 search_range: Range<Anchor>,
17422 display_snapshot: &DisplaySnapshot,
17423 theme: &ThemeColors,
17424 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17425 let mut results = Vec::new();
17426 for (color_fetcher, ranges) in self.background_highlights.values() {
17427 let color = color_fetcher(theme);
17428 let start_ix = match ranges.binary_search_by(|probe| {
17429 let cmp = probe
17430 .end
17431 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17432 if cmp.is_gt() {
17433 Ordering::Greater
17434 } else {
17435 Ordering::Less
17436 }
17437 }) {
17438 Ok(i) | Err(i) => i,
17439 };
17440 for range in &ranges[start_ix..] {
17441 if range
17442 .start
17443 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17444 .is_ge()
17445 {
17446 break;
17447 }
17448
17449 let start = range.start.to_display_point(display_snapshot);
17450 let end = range.end.to_display_point(display_snapshot);
17451 results.push((start..end, color))
17452 }
17453 }
17454 results
17455 }
17456
17457 pub fn background_highlight_row_ranges<T: 'static>(
17458 &self,
17459 search_range: Range<Anchor>,
17460 display_snapshot: &DisplaySnapshot,
17461 count: usize,
17462 ) -> Vec<RangeInclusive<DisplayPoint>> {
17463 let mut results = Vec::new();
17464 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17465 return vec![];
17466 };
17467
17468 let start_ix = match ranges.binary_search_by(|probe| {
17469 let cmp = probe
17470 .end
17471 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17472 if cmp.is_gt() {
17473 Ordering::Greater
17474 } else {
17475 Ordering::Less
17476 }
17477 }) {
17478 Ok(i) | Err(i) => i,
17479 };
17480 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17481 if let (Some(start_display), Some(end_display)) = (start, end) {
17482 results.push(
17483 start_display.to_display_point(display_snapshot)
17484 ..=end_display.to_display_point(display_snapshot),
17485 );
17486 }
17487 };
17488 let mut start_row: Option<Point> = None;
17489 let mut end_row: Option<Point> = None;
17490 if ranges.len() > count {
17491 return Vec::new();
17492 }
17493 for range in &ranges[start_ix..] {
17494 if range
17495 .start
17496 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17497 .is_ge()
17498 {
17499 break;
17500 }
17501 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17502 if let Some(current_row) = &end_row {
17503 if end.row == current_row.row {
17504 continue;
17505 }
17506 }
17507 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17508 if start_row.is_none() {
17509 assert_eq!(end_row, None);
17510 start_row = Some(start);
17511 end_row = Some(end);
17512 continue;
17513 }
17514 if let Some(current_end) = end_row.as_mut() {
17515 if start.row > current_end.row + 1 {
17516 push_region(start_row, end_row);
17517 start_row = Some(start);
17518 end_row = Some(end);
17519 } else {
17520 // Merge two hunks.
17521 *current_end = end;
17522 }
17523 } else {
17524 unreachable!();
17525 }
17526 }
17527 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17528 push_region(start_row, end_row);
17529 results
17530 }
17531
17532 pub fn gutter_highlights_in_range(
17533 &self,
17534 search_range: Range<Anchor>,
17535 display_snapshot: &DisplaySnapshot,
17536 cx: &App,
17537 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17538 let mut results = Vec::new();
17539 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17540 let color = color_fetcher(cx);
17541 let start_ix = match ranges.binary_search_by(|probe| {
17542 let cmp = probe
17543 .end
17544 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17545 if cmp.is_gt() {
17546 Ordering::Greater
17547 } else {
17548 Ordering::Less
17549 }
17550 }) {
17551 Ok(i) | Err(i) => i,
17552 };
17553 for range in &ranges[start_ix..] {
17554 if range
17555 .start
17556 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17557 .is_ge()
17558 {
17559 break;
17560 }
17561
17562 let start = range.start.to_display_point(display_snapshot);
17563 let end = range.end.to_display_point(display_snapshot);
17564 results.push((start..end, color))
17565 }
17566 }
17567 results
17568 }
17569
17570 /// Get the text ranges corresponding to the redaction query
17571 pub fn redacted_ranges(
17572 &self,
17573 search_range: Range<Anchor>,
17574 display_snapshot: &DisplaySnapshot,
17575 cx: &App,
17576 ) -> Vec<Range<DisplayPoint>> {
17577 display_snapshot
17578 .buffer_snapshot
17579 .redacted_ranges(search_range, |file| {
17580 if let Some(file) = file {
17581 file.is_private()
17582 && EditorSettings::get(
17583 Some(SettingsLocation {
17584 worktree_id: file.worktree_id(cx),
17585 path: file.path().as_ref(),
17586 }),
17587 cx,
17588 )
17589 .redact_private_values
17590 } else {
17591 false
17592 }
17593 })
17594 .map(|range| {
17595 range.start.to_display_point(display_snapshot)
17596 ..range.end.to_display_point(display_snapshot)
17597 })
17598 .collect()
17599 }
17600
17601 pub fn highlight_text<T: 'static>(
17602 &mut self,
17603 ranges: Vec<Range<Anchor>>,
17604 style: HighlightStyle,
17605 cx: &mut Context<Self>,
17606 ) {
17607 self.display_map.update(cx, |map, _| {
17608 map.highlight_text(TypeId::of::<T>(), ranges, style)
17609 });
17610 cx.notify();
17611 }
17612
17613 pub(crate) fn highlight_inlays<T: 'static>(
17614 &mut self,
17615 highlights: Vec<InlayHighlight>,
17616 style: HighlightStyle,
17617 cx: &mut Context<Self>,
17618 ) {
17619 self.display_map.update(cx, |map, _| {
17620 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17621 });
17622 cx.notify();
17623 }
17624
17625 pub fn text_highlights<'a, T: 'static>(
17626 &'a self,
17627 cx: &'a App,
17628 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17629 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17630 }
17631
17632 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17633 let cleared = self
17634 .display_map
17635 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17636 if cleared {
17637 cx.notify();
17638 }
17639 }
17640
17641 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17642 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17643 && self.focus_handle.is_focused(window)
17644 }
17645
17646 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17647 self.show_cursor_when_unfocused = is_enabled;
17648 cx.notify();
17649 }
17650
17651 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17652 cx.notify();
17653 }
17654
17655 fn on_debug_session_event(
17656 &mut self,
17657 _session: Entity<Session>,
17658 event: &SessionEvent,
17659 cx: &mut Context<Self>,
17660 ) {
17661 match event {
17662 SessionEvent::InvalidateInlineValue => {
17663 self.refresh_inline_values(cx);
17664 }
17665 _ => {}
17666 }
17667 }
17668
17669 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17670 let Some(project) = self.project.clone() else {
17671 return;
17672 };
17673 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17674 return;
17675 };
17676 if !self.inline_value_cache.enabled {
17677 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17678 self.splice_inlays(&inlays, Vec::new(), cx);
17679 return;
17680 }
17681
17682 let current_execution_position = self
17683 .highlighted_rows
17684 .get(&TypeId::of::<DebugCurrentRowHighlight>())
17685 .and_then(|lines| lines.last().map(|line| line.range.start));
17686
17687 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17688 let snapshot = editor
17689 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17690 .ok()?;
17691
17692 let inline_values = editor
17693 .update(cx, |_, cx| {
17694 let Some(current_execution_position) = current_execution_position else {
17695 return Some(Task::ready(Ok(Vec::new())));
17696 };
17697
17698 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17699 // anchor is in the same buffer
17700 let range =
17701 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17702 project.inline_values(buffer, range, cx)
17703 })
17704 .ok()
17705 .flatten()?
17706 .await
17707 .context("refreshing debugger inlays")
17708 .log_err()?;
17709
17710 let (excerpt_id, buffer_id) = snapshot
17711 .excerpts()
17712 .next()
17713 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17714 editor
17715 .update(cx, |editor, cx| {
17716 let new_inlays = inline_values
17717 .into_iter()
17718 .map(|debugger_value| {
17719 Inlay::debugger_hint(
17720 post_inc(&mut editor.next_inlay_id),
17721 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17722 debugger_value.text(),
17723 )
17724 })
17725 .collect::<Vec<_>>();
17726 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17727 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17728
17729 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17730 })
17731 .ok()?;
17732 Some(())
17733 });
17734 }
17735
17736 fn on_buffer_event(
17737 &mut self,
17738 multibuffer: &Entity<MultiBuffer>,
17739 event: &multi_buffer::Event,
17740 window: &mut Window,
17741 cx: &mut Context<Self>,
17742 ) {
17743 match event {
17744 multi_buffer::Event::Edited {
17745 singleton_buffer_edited,
17746 edited_buffer: buffer_edited,
17747 } => {
17748 self.scrollbar_marker_state.dirty = true;
17749 self.active_indent_guides_state.dirty = true;
17750 self.refresh_active_diagnostics(cx);
17751 self.refresh_code_actions(window, cx);
17752 if self.has_active_inline_completion() {
17753 self.update_visible_inline_completion(window, cx);
17754 }
17755 if let Some(buffer) = buffer_edited {
17756 let buffer_id = buffer.read(cx).remote_id();
17757 if !self.registered_buffers.contains_key(&buffer_id) {
17758 if let Some(project) = self.project.as_ref() {
17759 project.update(cx, |project, cx| {
17760 self.registered_buffers.insert(
17761 buffer_id,
17762 project.register_buffer_with_language_servers(&buffer, cx),
17763 );
17764 })
17765 }
17766 }
17767 }
17768 cx.emit(EditorEvent::BufferEdited);
17769 cx.emit(SearchEvent::MatchesInvalidated);
17770 if *singleton_buffer_edited {
17771 if let Some(project) = &self.project {
17772 #[allow(clippy::mutable_key_type)]
17773 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17774 multibuffer
17775 .all_buffers()
17776 .into_iter()
17777 .filter_map(|buffer| {
17778 buffer.update(cx, |buffer, cx| {
17779 let language = buffer.language()?;
17780 let should_discard = project.update(cx, |project, cx| {
17781 project.is_local()
17782 && !project.has_language_servers_for(buffer, cx)
17783 });
17784 should_discard.not().then_some(language.clone())
17785 })
17786 })
17787 .collect::<HashSet<_>>()
17788 });
17789 if !languages_affected.is_empty() {
17790 self.refresh_inlay_hints(
17791 InlayHintRefreshReason::BufferEdited(languages_affected),
17792 cx,
17793 );
17794 }
17795 }
17796 }
17797
17798 let Some(project) = &self.project else { return };
17799 let (telemetry, is_via_ssh) = {
17800 let project = project.read(cx);
17801 let telemetry = project.client().telemetry().clone();
17802 let is_via_ssh = project.is_via_ssh();
17803 (telemetry, is_via_ssh)
17804 };
17805 refresh_linked_ranges(self, window, cx);
17806 telemetry.log_edit_event("editor", is_via_ssh);
17807 }
17808 multi_buffer::Event::ExcerptsAdded {
17809 buffer,
17810 predecessor,
17811 excerpts,
17812 } => {
17813 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17814 let buffer_id = buffer.read(cx).remote_id();
17815 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17816 if let Some(project) = &self.project {
17817 get_uncommitted_diff_for_buffer(
17818 project,
17819 [buffer.clone()],
17820 self.buffer.clone(),
17821 cx,
17822 )
17823 .detach();
17824 }
17825 }
17826 cx.emit(EditorEvent::ExcerptsAdded {
17827 buffer: buffer.clone(),
17828 predecessor: *predecessor,
17829 excerpts: excerpts.clone(),
17830 });
17831 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17832 }
17833 multi_buffer::Event::ExcerptsRemoved {
17834 ids,
17835 removed_buffer_ids,
17836 } => {
17837 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17838 let buffer = self.buffer.read(cx);
17839 self.registered_buffers
17840 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17841 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17842 cx.emit(EditorEvent::ExcerptsRemoved {
17843 ids: ids.clone(),
17844 removed_buffer_ids: removed_buffer_ids.clone(),
17845 })
17846 }
17847 multi_buffer::Event::ExcerptsEdited {
17848 excerpt_ids,
17849 buffer_ids,
17850 } => {
17851 self.display_map.update(cx, |map, cx| {
17852 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17853 });
17854 cx.emit(EditorEvent::ExcerptsEdited {
17855 ids: excerpt_ids.clone(),
17856 })
17857 }
17858 multi_buffer::Event::ExcerptsExpanded { ids } => {
17859 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17860 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17861 }
17862 multi_buffer::Event::Reparsed(buffer_id) => {
17863 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17864 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17865
17866 cx.emit(EditorEvent::Reparsed(*buffer_id));
17867 }
17868 multi_buffer::Event::DiffHunksToggled => {
17869 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17870 }
17871 multi_buffer::Event::LanguageChanged(buffer_id) => {
17872 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17873 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17874 cx.emit(EditorEvent::Reparsed(*buffer_id));
17875 cx.notify();
17876 }
17877 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17878 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17879 multi_buffer::Event::FileHandleChanged
17880 | multi_buffer::Event::Reloaded
17881 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17882 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17883 multi_buffer::Event::DiagnosticsUpdated => {
17884 self.refresh_active_diagnostics(cx);
17885 self.refresh_inline_diagnostics(true, window, cx);
17886 self.scrollbar_marker_state.dirty = true;
17887 cx.notify();
17888 }
17889 _ => {}
17890 };
17891 }
17892
17893 fn on_display_map_changed(
17894 &mut self,
17895 _: Entity<DisplayMap>,
17896 _: &mut Window,
17897 cx: &mut Context<Self>,
17898 ) {
17899 cx.notify();
17900 }
17901
17902 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17903 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17904 self.update_edit_prediction_settings(cx);
17905 self.refresh_inline_completion(true, false, window, cx);
17906 self.refresh_inlay_hints(
17907 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17908 self.selections.newest_anchor().head(),
17909 &self.buffer.read(cx).snapshot(cx),
17910 cx,
17911 )),
17912 cx,
17913 );
17914
17915 let old_cursor_shape = self.cursor_shape;
17916
17917 {
17918 let editor_settings = EditorSettings::get_global(cx);
17919 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17920 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17921 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17922 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17923 }
17924
17925 if old_cursor_shape != self.cursor_shape {
17926 cx.emit(EditorEvent::CursorShapeChanged);
17927 }
17928
17929 let project_settings = ProjectSettings::get_global(cx);
17930 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17931
17932 if self.mode.is_full() {
17933 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17934 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17935 if self.show_inline_diagnostics != show_inline_diagnostics {
17936 self.show_inline_diagnostics = show_inline_diagnostics;
17937 self.refresh_inline_diagnostics(false, window, cx);
17938 }
17939
17940 if self.git_blame_inline_enabled != inline_blame_enabled {
17941 self.toggle_git_blame_inline_internal(false, window, cx);
17942 }
17943 }
17944
17945 cx.notify();
17946 }
17947
17948 pub fn set_searchable(&mut self, searchable: bool) {
17949 self.searchable = searchable;
17950 }
17951
17952 pub fn searchable(&self) -> bool {
17953 self.searchable
17954 }
17955
17956 fn open_proposed_changes_editor(
17957 &mut self,
17958 _: &OpenProposedChangesEditor,
17959 window: &mut Window,
17960 cx: &mut Context<Self>,
17961 ) {
17962 let Some(workspace) = self.workspace() else {
17963 cx.propagate();
17964 return;
17965 };
17966
17967 let selections = self.selections.all::<usize>(cx);
17968 let multi_buffer = self.buffer.read(cx);
17969 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17970 let mut new_selections_by_buffer = HashMap::default();
17971 for selection in selections {
17972 for (buffer, range, _) in
17973 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17974 {
17975 let mut range = range.to_point(buffer);
17976 range.start.column = 0;
17977 range.end.column = buffer.line_len(range.end.row);
17978 new_selections_by_buffer
17979 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17980 .or_insert(Vec::new())
17981 .push(range)
17982 }
17983 }
17984
17985 let proposed_changes_buffers = new_selections_by_buffer
17986 .into_iter()
17987 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17988 .collect::<Vec<_>>();
17989 let proposed_changes_editor = cx.new(|cx| {
17990 ProposedChangesEditor::new(
17991 "Proposed changes",
17992 proposed_changes_buffers,
17993 self.project.clone(),
17994 window,
17995 cx,
17996 )
17997 });
17998
17999 window.defer(cx, move |window, cx| {
18000 workspace.update(cx, |workspace, cx| {
18001 workspace.active_pane().update(cx, |pane, cx| {
18002 pane.add_item(
18003 Box::new(proposed_changes_editor),
18004 true,
18005 true,
18006 None,
18007 window,
18008 cx,
18009 );
18010 });
18011 });
18012 });
18013 }
18014
18015 pub fn open_excerpts_in_split(
18016 &mut self,
18017 _: &OpenExcerptsSplit,
18018 window: &mut Window,
18019 cx: &mut Context<Self>,
18020 ) {
18021 self.open_excerpts_common(None, true, window, cx)
18022 }
18023
18024 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18025 self.open_excerpts_common(None, false, window, cx)
18026 }
18027
18028 fn open_excerpts_common(
18029 &mut self,
18030 jump_data: Option<JumpData>,
18031 split: bool,
18032 window: &mut Window,
18033 cx: &mut Context<Self>,
18034 ) {
18035 let Some(workspace) = self.workspace() else {
18036 cx.propagate();
18037 return;
18038 };
18039
18040 if self.buffer.read(cx).is_singleton() {
18041 cx.propagate();
18042 return;
18043 }
18044
18045 let mut new_selections_by_buffer = HashMap::default();
18046 match &jump_data {
18047 Some(JumpData::MultiBufferPoint {
18048 excerpt_id,
18049 position,
18050 anchor,
18051 line_offset_from_top,
18052 }) => {
18053 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18054 if let Some(buffer) = multi_buffer_snapshot
18055 .buffer_id_for_excerpt(*excerpt_id)
18056 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18057 {
18058 let buffer_snapshot = buffer.read(cx).snapshot();
18059 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18060 language::ToPoint::to_point(anchor, &buffer_snapshot)
18061 } else {
18062 buffer_snapshot.clip_point(*position, Bias::Left)
18063 };
18064 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18065 new_selections_by_buffer.insert(
18066 buffer,
18067 (
18068 vec![jump_to_offset..jump_to_offset],
18069 Some(*line_offset_from_top),
18070 ),
18071 );
18072 }
18073 }
18074 Some(JumpData::MultiBufferRow {
18075 row,
18076 line_offset_from_top,
18077 }) => {
18078 let point = MultiBufferPoint::new(row.0, 0);
18079 if let Some((buffer, buffer_point, _)) =
18080 self.buffer.read(cx).point_to_buffer_point(point, cx)
18081 {
18082 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18083 new_selections_by_buffer
18084 .entry(buffer)
18085 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18086 .0
18087 .push(buffer_offset..buffer_offset)
18088 }
18089 }
18090 None => {
18091 let selections = self.selections.all::<usize>(cx);
18092 let multi_buffer = self.buffer.read(cx);
18093 for selection in selections {
18094 for (snapshot, range, _, anchor) in multi_buffer
18095 .snapshot(cx)
18096 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18097 {
18098 if let Some(anchor) = anchor {
18099 // selection is in a deleted hunk
18100 let Some(buffer_id) = anchor.buffer_id else {
18101 continue;
18102 };
18103 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18104 continue;
18105 };
18106 let offset = text::ToOffset::to_offset(
18107 &anchor.text_anchor,
18108 &buffer_handle.read(cx).snapshot(),
18109 );
18110 let range = offset..offset;
18111 new_selections_by_buffer
18112 .entry(buffer_handle)
18113 .or_insert((Vec::new(), None))
18114 .0
18115 .push(range)
18116 } else {
18117 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18118 else {
18119 continue;
18120 };
18121 new_selections_by_buffer
18122 .entry(buffer_handle)
18123 .or_insert((Vec::new(), None))
18124 .0
18125 .push(range)
18126 }
18127 }
18128 }
18129 }
18130 }
18131
18132 new_selections_by_buffer
18133 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18134
18135 if new_selections_by_buffer.is_empty() {
18136 return;
18137 }
18138
18139 // We defer the pane interaction because we ourselves are a workspace item
18140 // and activating a new item causes the pane to call a method on us reentrantly,
18141 // which panics if we're on the stack.
18142 window.defer(cx, move |window, cx| {
18143 workspace.update(cx, |workspace, cx| {
18144 let pane = if split {
18145 workspace.adjacent_pane(window, cx)
18146 } else {
18147 workspace.active_pane().clone()
18148 };
18149
18150 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18151 let editor = buffer
18152 .read(cx)
18153 .file()
18154 .is_none()
18155 .then(|| {
18156 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18157 // so `workspace.open_project_item` will never find them, always opening a new editor.
18158 // Instead, we try to activate the existing editor in the pane first.
18159 let (editor, pane_item_index) =
18160 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18161 let editor = item.downcast::<Editor>()?;
18162 let singleton_buffer =
18163 editor.read(cx).buffer().read(cx).as_singleton()?;
18164 if singleton_buffer == buffer {
18165 Some((editor, i))
18166 } else {
18167 None
18168 }
18169 })?;
18170 pane.update(cx, |pane, cx| {
18171 pane.activate_item(pane_item_index, true, true, window, cx)
18172 });
18173 Some(editor)
18174 })
18175 .flatten()
18176 .unwrap_or_else(|| {
18177 workspace.open_project_item::<Self>(
18178 pane.clone(),
18179 buffer,
18180 true,
18181 true,
18182 window,
18183 cx,
18184 )
18185 });
18186
18187 editor.update(cx, |editor, cx| {
18188 let autoscroll = match scroll_offset {
18189 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18190 None => Autoscroll::newest(),
18191 };
18192 let nav_history = editor.nav_history.take();
18193 editor.change_selections(Some(autoscroll), window, cx, |s| {
18194 s.select_ranges(ranges);
18195 });
18196 editor.nav_history = nav_history;
18197 });
18198 }
18199 })
18200 });
18201 }
18202
18203 // For now, don't allow opening excerpts in buffers that aren't backed by
18204 // regular project files.
18205 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18206 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18207 }
18208
18209 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18210 let snapshot = self.buffer.read(cx).read(cx);
18211 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18212 Some(
18213 ranges
18214 .iter()
18215 .map(move |range| {
18216 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18217 })
18218 .collect(),
18219 )
18220 }
18221
18222 fn selection_replacement_ranges(
18223 &self,
18224 range: Range<OffsetUtf16>,
18225 cx: &mut App,
18226 ) -> Vec<Range<OffsetUtf16>> {
18227 let selections = self.selections.all::<OffsetUtf16>(cx);
18228 let newest_selection = selections
18229 .iter()
18230 .max_by_key(|selection| selection.id)
18231 .unwrap();
18232 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18233 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18234 let snapshot = self.buffer.read(cx).read(cx);
18235 selections
18236 .into_iter()
18237 .map(|mut selection| {
18238 selection.start.0 =
18239 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18240 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18241 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18242 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18243 })
18244 .collect()
18245 }
18246
18247 fn report_editor_event(
18248 &self,
18249 event_type: &'static str,
18250 file_extension: Option<String>,
18251 cx: &App,
18252 ) {
18253 if cfg!(any(test, feature = "test-support")) {
18254 return;
18255 }
18256
18257 let Some(project) = &self.project else { return };
18258
18259 // If None, we are in a file without an extension
18260 let file = self
18261 .buffer
18262 .read(cx)
18263 .as_singleton()
18264 .and_then(|b| b.read(cx).file());
18265 let file_extension = file_extension.or(file
18266 .as_ref()
18267 .and_then(|file| Path::new(file.file_name(cx)).extension())
18268 .and_then(|e| e.to_str())
18269 .map(|a| a.to_string()));
18270
18271 let vim_mode = vim_enabled(cx);
18272
18273 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18274 let copilot_enabled = edit_predictions_provider
18275 == language::language_settings::EditPredictionProvider::Copilot;
18276 let copilot_enabled_for_language = self
18277 .buffer
18278 .read(cx)
18279 .language_settings(cx)
18280 .show_edit_predictions;
18281
18282 let project = project.read(cx);
18283 telemetry::event!(
18284 event_type,
18285 file_extension,
18286 vim_mode,
18287 copilot_enabled,
18288 copilot_enabled_for_language,
18289 edit_predictions_provider,
18290 is_via_ssh = project.is_via_ssh(),
18291 );
18292 }
18293
18294 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18295 /// with each line being an array of {text, highlight} objects.
18296 fn copy_highlight_json(
18297 &mut self,
18298 _: &CopyHighlightJson,
18299 window: &mut Window,
18300 cx: &mut Context<Self>,
18301 ) {
18302 #[derive(Serialize)]
18303 struct Chunk<'a> {
18304 text: String,
18305 highlight: Option<&'a str>,
18306 }
18307
18308 let snapshot = self.buffer.read(cx).snapshot(cx);
18309 let range = self
18310 .selected_text_range(false, window, cx)
18311 .and_then(|selection| {
18312 if selection.range.is_empty() {
18313 None
18314 } else {
18315 Some(selection.range)
18316 }
18317 })
18318 .unwrap_or_else(|| 0..snapshot.len());
18319
18320 let chunks = snapshot.chunks(range, true);
18321 let mut lines = Vec::new();
18322 let mut line: VecDeque<Chunk> = VecDeque::new();
18323
18324 let Some(style) = self.style.as_ref() else {
18325 return;
18326 };
18327
18328 for chunk in chunks {
18329 let highlight = chunk
18330 .syntax_highlight_id
18331 .and_then(|id| id.name(&style.syntax));
18332 let mut chunk_lines = chunk.text.split('\n').peekable();
18333 while let Some(text) = chunk_lines.next() {
18334 let mut merged_with_last_token = false;
18335 if let Some(last_token) = line.back_mut() {
18336 if last_token.highlight == highlight {
18337 last_token.text.push_str(text);
18338 merged_with_last_token = true;
18339 }
18340 }
18341
18342 if !merged_with_last_token {
18343 line.push_back(Chunk {
18344 text: text.into(),
18345 highlight,
18346 });
18347 }
18348
18349 if chunk_lines.peek().is_some() {
18350 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18351 line.pop_front();
18352 }
18353 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18354 line.pop_back();
18355 }
18356
18357 lines.push(mem::take(&mut line));
18358 }
18359 }
18360 }
18361
18362 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18363 return;
18364 };
18365 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18366 }
18367
18368 pub fn open_context_menu(
18369 &mut self,
18370 _: &OpenContextMenu,
18371 window: &mut Window,
18372 cx: &mut Context<Self>,
18373 ) {
18374 self.request_autoscroll(Autoscroll::newest(), cx);
18375 let position = self.selections.newest_display(cx).start;
18376 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18377 }
18378
18379 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18380 &self.inlay_hint_cache
18381 }
18382
18383 pub fn replay_insert_event(
18384 &mut self,
18385 text: &str,
18386 relative_utf16_range: Option<Range<isize>>,
18387 window: &mut Window,
18388 cx: &mut Context<Self>,
18389 ) {
18390 if !self.input_enabled {
18391 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18392 return;
18393 }
18394 if let Some(relative_utf16_range) = relative_utf16_range {
18395 let selections = self.selections.all::<OffsetUtf16>(cx);
18396 self.change_selections(None, window, cx, |s| {
18397 let new_ranges = selections.into_iter().map(|range| {
18398 let start = OffsetUtf16(
18399 range
18400 .head()
18401 .0
18402 .saturating_add_signed(relative_utf16_range.start),
18403 );
18404 let end = OffsetUtf16(
18405 range
18406 .head()
18407 .0
18408 .saturating_add_signed(relative_utf16_range.end),
18409 );
18410 start..end
18411 });
18412 s.select_ranges(new_ranges);
18413 });
18414 }
18415
18416 self.handle_input(text, window, cx);
18417 }
18418
18419 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18420 let Some(provider) = self.semantics_provider.as_ref() else {
18421 return false;
18422 };
18423
18424 let mut supports = false;
18425 self.buffer().update(cx, |this, cx| {
18426 this.for_each_buffer(|buffer| {
18427 supports |= provider.supports_inlay_hints(buffer, cx);
18428 });
18429 });
18430
18431 supports
18432 }
18433
18434 pub fn is_focused(&self, window: &Window) -> bool {
18435 self.focus_handle.is_focused(window)
18436 }
18437
18438 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18439 cx.emit(EditorEvent::Focused);
18440
18441 if let Some(descendant) = self
18442 .last_focused_descendant
18443 .take()
18444 .and_then(|descendant| descendant.upgrade())
18445 {
18446 window.focus(&descendant);
18447 } else {
18448 if let Some(blame) = self.blame.as_ref() {
18449 blame.update(cx, GitBlame::focus)
18450 }
18451
18452 self.blink_manager.update(cx, BlinkManager::enable);
18453 self.show_cursor_names(window, cx);
18454 self.buffer.update(cx, |buffer, cx| {
18455 buffer.finalize_last_transaction(cx);
18456 if self.leader_peer_id.is_none() {
18457 buffer.set_active_selections(
18458 &self.selections.disjoint_anchors(),
18459 self.selections.line_mode,
18460 self.cursor_shape,
18461 cx,
18462 );
18463 }
18464 });
18465 }
18466 }
18467
18468 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18469 cx.emit(EditorEvent::FocusedIn)
18470 }
18471
18472 fn handle_focus_out(
18473 &mut self,
18474 event: FocusOutEvent,
18475 _window: &mut Window,
18476 cx: &mut Context<Self>,
18477 ) {
18478 if event.blurred != self.focus_handle {
18479 self.last_focused_descendant = Some(event.blurred);
18480 }
18481 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18482 }
18483
18484 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18485 self.blink_manager.update(cx, BlinkManager::disable);
18486 self.buffer
18487 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18488
18489 if let Some(blame) = self.blame.as_ref() {
18490 blame.update(cx, GitBlame::blur)
18491 }
18492 if !self.hover_state.focused(window, cx) {
18493 hide_hover(self, cx);
18494 }
18495 if !self
18496 .context_menu
18497 .borrow()
18498 .as_ref()
18499 .is_some_and(|context_menu| context_menu.focused(window, cx))
18500 {
18501 self.hide_context_menu(window, cx);
18502 }
18503 self.discard_inline_completion(false, cx);
18504 cx.emit(EditorEvent::Blurred);
18505 cx.notify();
18506 }
18507
18508 pub fn register_action<A: Action>(
18509 &mut self,
18510 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18511 ) -> Subscription {
18512 let id = self.next_editor_action_id.post_inc();
18513 let listener = Arc::new(listener);
18514 self.editor_actions.borrow_mut().insert(
18515 id,
18516 Box::new(move |window, _| {
18517 let listener = listener.clone();
18518 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18519 let action = action.downcast_ref().unwrap();
18520 if phase == DispatchPhase::Bubble {
18521 listener(action, window, cx)
18522 }
18523 })
18524 }),
18525 );
18526
18527 let editor_actions = self.editor_actions.clone();
18528 Subscription::new(move || {
18529 editor_actions.borrow_mut().remove(&id);
18530 })
18531 }
18532
18533 pub fn file_header_size(&self) -> u32 {
18534 FILE_HEADER_HEIGHT
18535 }
18536
18537 pub fn restore(
18538 &mut self,
18539 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18540 window: &mut Window,
18541 cx: &mut Context<Self>,
18542 ) {
18543 let workspace = self.workspace();
18544 let project = self.project.as_ref();
18545 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18546 let mut tasks = Vec::new();
18547 for (buffer_id, changes) in revert_changes {
18548 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18549 buffer.update(cx, |buffer, cx| {
18550 buffer.edit(
18551 changes
18552 .into_iter()
18553 .map(|(range, text)| (range, text.to_string())),
18554 None,
18555 cx,
18556 );
18557 });
18558
18559 if let Some(project) =
18560 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18561 {
18562 project.update(cx, |project, cx| {
18563 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18564 })
18565 }
18566 }
18567 }
18568 tasks
18569 });
18570 cx.spawn_in(window, async move |_, cx| {
18571 for (buffer, task) in save_tasks {
18572 let result = task.await;
18573 if result.is_err() {
18574 let Some(path) = buffer
18575 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18576 .ok()
18577 else {
18578 continue;
18579 };
18580 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18581 let Some(task) = cx
18582 .update_window_entity(&workspace, |workspace, window, cx| {
18583 workspace
18584 .open_path_preview(path, None, false, false, false, window, cx)
18585 })
18586 .ok()
18587 else {
18588 continue;
18589 };
18590 task.await.log_err();
18591 }
18592 }
18593 }
18594 })
18595 .detach();
18596 self.change_selections(None, window, cx, |selections| selections.refresh());
18597 }
18598
18599 pub fn to_pixel_point(
18600 &self,
18601 source: multi_buffer::Anchor,
18602 editor_snapshot: &EditorSnapshot,
18603 window: &mut Window,
18604 ) -> Option<gpui::Point<Pixels>> {
18605 let source_point = source.to_display_point(editor_snapshot);
18606 self.display_to_pixel_point(source_point, editor_snapshot, window)
18607 }
18608
18609 pub fn display_to_pixel_point(
18610 &self,
18611 source: DisplayPoint,
18612 editor_snapshot: &EditorSnapshot,
18613 window: &mut Window,
18614 ) -> Option<gpui::Point<Pixels>> {
18615 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18616 let text_layout_details = self.text_layout_details(window);
18617 let scroll_top = text_layout_details
18618 .scroll_anchor
18619 .scroll_position(editor_snapshot)
18620 .y;
18621
18622 if source.row().as_f32() < scroll_top.floor() {
18623 return None;
18624 }
18625 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18626 let source_y = line_height * (source.row().as_f32() - scroll_top);
18627 Some(gpui::Point::new(source_x, source_y))
18628 }
18629
18630 pub fn has_visible_completions_menu(&self) -> bool {
18631 !self.edit_prediction_preview_is_active()
18632 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18633 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18634 })
18635 }
18636
18637 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18638 self.addons
18639 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18640 }
18641
18642 pub fn unregister_addon<T: Addon>(&mut self) {
18643 self.addons.remove(&std::any::TypeId::of::<T>());
18644 }
18645
18646 pub fn addon<T: Addon>(&self) -> Option<&T> {
18647 let type_id = std::any::TypeId::of::<T>();
18648 self.addons
18649 .get(&type_id)
18650 .and_then(|item| item.to_any().downcast_ref::<T>())
18651 }
18652
18653 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18654 let type_id = std::any::TypeId::of::<T>();
18655 self.addons
18656 .get_mut(&type_id)
18657 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18658 }
18659
18660 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18661 let text_layout_details = self.text_layout_details(window);
18662 let style = &text_layout_details.editor_style;
18663 let font_id = window.text_system().resolve_font(&style.text.font());
18664 let font_size = style.text.font_size.to_pixels(window.rem_size());
18665 let line_height = style.text.line_height_in_pixels(window.rem_size());
18666 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18667
18668 gpui::Size::new(em_width, line_height)
18669 }
18670
18671 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18672 self.load_diff_task.clone()
18673 }
18674
18675 fn read_metadata_from_db(
18676 &mut self,
18677 item_id: u64,
18678 workspace_id: WorkspaceId,
18679 window: &mut Window,
18680 cx: &mut Context<Editor>,
18681 ) {
18682 if self.is_singleton(cx)
18683 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18684 {
18685 let buffer_snapshot = OnceCell::new();
18686
18687 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18688 if !folds.is_empty() {
18689 let snapshot =
18690 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18691 self.fold_ranges(
18692 folds
18693 .into_iter()
18694 .map(|(start, end)| {
18695 snapshot.clip_offset(start, Bias::Left)
18696 ..snapshot.clip_offset(end, Bias::Right)
18697 })
18698 .collect(),
18699 false,
18700 window,
18701 cx,
18702 );
18703 }
18704 }
18705
18706 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18707 if !selections.is_empty() {
18708 let snapshot =
18709 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18710 self.change_selections(None, window, cx, |s| {
18711 s.select_ranges(selections.into_iter().map(|(start, end)| {
18712 snapshot.clip_offset(start, Bias::Left)
18713 ..snapshot.clip_offset(end, Bias::Right)
18714 }));
18715 });
18716 }
18717 };
18718 }
18719
18720 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18721 }
18722}
18723
18724fn vim_enabled(cx: &App) -> bool {
18725 cx.global::<SettingsStore>()
18726 .raw_user_settings()
18727 .get("vim_mode")
18728 == Some(&serde_json::Value::Bool(true))
18729}
18730
18731// Consider user intent and default settings
18732fn choose_completion_range(
18733 completion: &Completion,
18734 intent: CompletionIntent,
18735 buffer: &Entity<Buffer>,
18736 cx: &mut Context<Editor>,
18737) -> Range<usize> {
18738 fn should_replace(
18739 completion: &Completion,
18740 insert_range: &Range<text::Anchor>,
18741 intent: CompletionIntent,
18742 completion_mode_setting: LspInsertMode,
18743 buffer: &Buffer,
18744 ) -> bool {
18745 // specific actions take precedence over settings
18746 match intent {
18747 CompletionIntent::CompleteWithInsert => return false,
18748 CompletionIntent::CompleteWithReplace => return true,
18749 CompletionIntent::Complete | CompletionIntent::Compose => {}
18750 }
18751
18752 match completion_mode_setting {
18753 LspInsertMode::Insert => false,
18754 LspInsertMode::Replace => true,
18755 LspInsertMode::ReplaceSubsequence => {
18756 let mut text_to_replace = buffer.chars_for_range(
18757 buffer.anchor_before(completion.replace_range.start)
18758 ..buffer.anchor_after(completion.replace_range.end),
18759 );
18760 let mut completion_text = completion.new_text.chars();
18761
18762 // is `text_to_replace` a subsequence of `completion_text`
18763 text_to_replace
18764 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18765 }
18766 LspInsertMode::ReplaceSuffix => {
18767 let range_after_cursor = insert_range.end..completion.replace_range.end;
18768
18769 let text_after_cursor = buffer
18770 .text_for_range(
18771 buffer.anchor_before(range_after_cursor.start)
18772 ..buffer.anchor_after(range_after_cursor.end),
18773 )
18774 .collect::<String>();
18775 completion.new_text.ends_with(&text_after_cursor)
18776 }
18777 }
18778 }
18779
18780 let buffer = buffer.read(cx);
18781
18782 if let CompletionSource::Lsp {
18783 insert_range: Some(insert_range),
18784 ..
18785 } = &completion.source
18786 {
18787 let completion_mode_setting =
18788 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18789 .completions
18790 .lsp_insert_mode;
18791
18792 if !should_replace(
18793 completion,
18794 &insert_range,
18795 intent,
18796 completion_mode_setting,
18797 buffer,
18798 ) {
18799 return insert_range.to_offset(buffer);
18800 }
18801 }
18802
18803 completion.replace_range.to_offset(buffer)
18804}
18805
18806fn insert_extra_newline_brackets(
18807 buffer: &MultiBufferSnapshot,
18808 range: Range<usize>,
18809 language: &language::LanguageScope,
18810) -> bool {
18811 let leading_whitespace_len = buffer
18812 .reversed_chars_at(range.start)
18813 .take_while(|c| c.is_whitespace() && *c != '\n')
18814 .map(|c| c.len_utf8())
18815 .sum::<usize>();
18816 let trailing_whitespace_len = buffer
18817 .chars_at(range.end)
18818 .take_while(|c| c.is_whitespace() && *c != '\n')
18819 .map(|c| c.len_utf8())
18820 .sum::<usize>();
18821 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18822
18823 language.brackets().any(|(pair, enabled)| {
18824 let pair_start = pair.start.trim_end();
18825 let pair_end = pair.end.trim_start();
18826
18827 enabled
18828 && pair.newline
18829 && buffer.contains_str_at(range.end, pair_end)
18830 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18831 })
18832}
18833
18834fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18835 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18836 [(buffer, range, _)] => (*buffer, range.clone()),
18837 _ => return false,
18838 };
18839 let pair = {
18840 let mut result: Option<BracketMatch> = None;
18841
18842 for pair in buffer
18843 .all_bracket_ranges(range.clone())
18844 .filter(move |pair| {
18845 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18846 })
18847 {
18848 let len = pair.close_range.end - pair.open_range.start;
18849
18850 if let Some(existing) = &result {
18851 let existing_len = existing.close_range.end - existing.open_range.start;
18852 if len > existing_len {
18853 continue;
18854 }
18855 }
18856
18857 result = Some(pair);
18858 }
18859
18860 result
18861 };
18862 let Some(pair) = pair else {
18863 return false;
18864 };
18865 pair.newline_only
18866 && buffer
18867 .chars_for_range(pair.open_range.end..range.start)
18868 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18869 .all(|c| c.is_whitespace() && c != '\n')
18870}
18871
18872fn get_uncommitted_diff_for_buffer(
18873 project: &Entity<Project>,
18874 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18875 buffer: Entity<MultiBuffer>,
18876 cx: &mut App,
18877) -> Task<()> {
18878 let mut tasks = Vec::new();
18879 project.update(cx, |project, cx| {
18880 for buffer in buffers {
18881 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18882 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18883 }
18884 }
18885 });
18886 cx.spawn(async move |cx| {
18887 let diffs = future::join_all(tasks).await;
18888 buffer
18889 .update(cx, |buffer, cx| {
18890 for diff in diffs.into_iter().flatten() {
18891 buffer.add_diff(diff, cx);
18892 }
18893 })
18894 .ok();
18895 })
18896}
18897
18898fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18899 let tab_size = tab_size.get() as usize;
18900 let mut width = offset;
18901
18902 for ch in text.chars() {
18903 width += if ch == '\t' {
18904 tab_size - (width % tab_size)
18905 } else {
18906 1
18907 };
18908 }
18909
18910 width - offset
18911}
18912
18913#[cfg(test)]
18914mod tests {
18915 use super::*;
18916
18917 #[test]
18918 fn test_string_size_with_expanded_tabs() {
18919 let nz = |val| NonZeroU32::new(val).unwrap();
18920 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18921 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18922 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18923 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18924 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18925 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18926 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18927 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18928 }
18929}
18930
18931/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18932struct WordBreakingTokenizer<'a> {
18933 input: &'a str,
18934}
18935
18936impl<'a> WordBreakingTokenizer<'a> {
18937 fn new(input: &'a str) -> Self {
18938 Self { input }
18939 }
18940}
18941
18942fn is_char_ideographic(ch: char) -> bool {
18943 use unicode_script::Script::*;
18944 use unicode_script::UnicodeScript;
18945 matches!(ch.script(), Han | Tangut | Yi)
18946}
18947
18948fn is_grapheme_ideographic(text: &str) -> bool {
18949 text.chars().any(is_char_ideographic)
18950}
18951
18952fn is_grapheme_whitespace(text: &str) -> bool {
18953 text.chars().any(|x| x.is_whitespace())
18954}
18955
18956fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18957 text.chars().next().map_or(false, |ch| {
18958 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18959 })
18960}
18961
18962#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18963enum WordBreakToken<'a> {
18964 Word { token: &'a str, grapheme_len: usize },
18965 InlineWhitespace { token: &'a str, grapheme_len: usize },
18966 Newline,
18967}
18968
18969impl<'a> Iterator for WordBreakingTokenizer<'a> {
18970 /// Yields a span, the count of graphemes in the token, and whether it was
18971 /// whitespace. Note that it also breaks at word boundaries.
18972 type Item = WordBreakToken<'a>;
18973
18974 fn next(&mut self) -> Option<Self::Item> {
18975 use unicode_segmentation::UnicodeSegmentation;
18976 if self.input.is_empty() {
18977 return None;
18978 }
18979
18980 let mut iter = self.input.graphemes(true).peekable();
18981 let mut offset = 0;
18982 let mut grapheme_len = 0;
18983 if let Some(first_grapheme) = iter.next() {
18984 let is_newline = first_grapheme == "\n";
18985 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18986 offset += first_grapheme.len();
18987 grapheme_len += 1;
18988 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18989 if let Some(grapheme) = iter.peek().copied() {
18990 if should_stay_with_preceding_ideograph(grapheme) {
18991 offset += grapheme.len();
18992 grapheme_len += 1;
18993 }
18994 }
18995 } else {
18996 let mut words = self.input[offset..].split_word_bound_indices().peekable();
18997 let mut next_word_bound = words.peek().copied();
18998 if next_word_bound.map_or(false, |(i, _)| i == 0) {
18999 next_word_bound = words.next();
19000 }
19001 while let Some(grapheme) = iter.peek().copied() {
19002 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19003 break;
19004 };
19005 if is_grapheme_whitespace(grapheme) != is_whitespace
19006 || (grapheme == "\n") != is_newline
19007 {
19008 break;
19009 };
19010 offset += grapheme.len();
19011 grapheme_len += 1;
19012 iter.next();
19013 }
19014 }
19015 let token = &self.input[..offset];
19016 self.input = &self.input[offset..];
19017 if token == "\n" {
19018 Some(WordBreakToken::Newline)
19019 } else if is_whitespace {
19020 Some(WordBreakToken::InlineWhitespace {
19021 token,
19022 grapheme_len,
19023 })
19024 } else {
19025 Some(WordBreakToken::Word {
19026 token,
19027 grapheme_len,
19028 })
19029 }
19030 } else {
19031 None
19032 }
19033 }
19034}
19035
19036#[test]
19037fn test_word_breaking_tokenizer() {
19038 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19039 ("", &[]),
19040 (" ", &[whitespace(" ", 2)]),
19041 ("Ʒ", &[word("Ʒ", 1)]),
19042 ("Ǽ", &[word("Ǽ", 1)]),
19043 ("⋑", &[word("⋑", 1)]),
19044 ("⋑⋑", &[word("⋑⋑", 2)]),
19045 (
19046 "原理,进而",
19047 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19048 ),
19049 (
19050 "hello world",
19051 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19052 ),
19053 (
19054 "hello, world",
19055 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19056 ),
19057 (
19058 " hello world",
19059 &[
19060 whitespace(" ", 2),
19061 word("hello", 5),
19062 whitespace(" ", 1),
19063 word("world", 5),
19064 ],
19065 ),
19066 (
19067 "这是什么 \n 钢笔",
19068 &[
19069 word("这", 1),
19070 word("是", 1),
19071 word("什", 1),
19072 word("么", 1),
19073 whitespace(" ", 1),
19074 newline(),
19075 whitespace(" ", 1),
19076 word("钢", 1),
19077 word("笔", 1),
19078 ],
19079 ),
19080 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19081 ];
19082
19083 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19084 WordBreakToken::Word {
19085 token,
19086 grapheme_len,
19087 }
19088 }
19089
19090 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19091 WordBreakToken::InlineWhitespace {
19092 token,
19093 grapheme_len,
19094 }
19095 }
19096
19097 fn newline() -> WordBreakToken<'static> {
19098 WordBreakToken::Newline
19099 }
19100
19101 for (input, result) in tests {
19102 assert_eq!(
19103 WordBreakingTokenizer::new(input)
19104 .collect::<Vec<_>>()
19105 .as_slice(),
19106 *result,
19107 );
19108 }
19109}
19110
19111fn wrap_with_prefix(
19112 line_prefix: String,
19113 unwrapped_text: String,
19114 wrap_column: usize,
19115 tab_size: NonZeroU32,
19116 preserve_existing_whitespace: bool,
19117) -> String {
19118 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19119 let mut wrapped_text = String::new();
19120 let mut current_line = line_prefix.clone();
19121
19122 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19123 let mut current_line_len = line_prefix_len;
19124 let mut in_whitespace = false;
19125 for token in tokenizer {
19126 let have_preceding_whitespace = in_whitespace;
19127 match token {
19128 WordBreakToken::Word {
19129 token,
19130 grapheme_len,
19131 } => {
19132 in_whitespace = false;
19133 if current_line_len + grapheme_len > wrap_column
19134 && current_line_len != line_prefix_len
19135 {
19136 wrapped_text.push_str(current_line.trim_end());
19137 wrapped_text.push('\n');
19138 current_line.truncate(line_prefix.len());
19139 current_line_len = line_prefix_len;
19140 }
19141 current_line.push_str(token);
19142 current_line_len += grapheme_len;
19143 }
19144 WordBreakToken::InlineWhitespace {
19145 mut token,
19146 mut grapheme_len,
19147 } => {
19148 in_whitespace = true;
19149 if have_preceding_whitespace && !preserve_existing_whitespace {
19150 continue;
19151 }
19152 if !preserve_existing_whitespace {
19153 token = " ";
19154 grapheme_len = 1;
19155 }
19156 if current_line_len + grapheme_len > wrap_column {
19157 wrapped_text.push_str(current_line.trim_end());
19158 wrapped_text.push('\n');
19159 current_line.truncate(line_prefix.len());
19160 current_line_len = line_prefix_len;
19161 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19162 current_line.push_str(token);
19163 current_line_len += grapheme_len;
19164 }
19165 }
19166 WordBreakToken::Newline => {
19167 in_whitespace = true;
19168 if preserve_existing_whitespace {
19169 wrapped_text.push_str(current_line.trim_end());
19170 wrapped_text.push('\n');
19171 current_line.truncate(line_prefix.len());
19172 current_line_len = line_prefix_len;
19173 } else if have_preceding_whitespace {
19174 continue;
19175 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19176 {
19177 wrapped_text.push_str(current_line.trim_end());
19178 wrapped_text.push('\n');
19179 current_line.truncate(line_prefix.len());
19180 current_line_len = line_prefix_len;
19181 } else if current_line_len != line_prefix_len {
19182 current_line.push(' ');
19183 current_line_len += 1;
19184 }
19185 }
19186 }
19187 }
19188
19189 if !current_line.is_empty() {
19190 wrapped_text.push_str(¤t_line);
19191 }
19192 wrapped_text
19193}
19194
19195#[test]
19196fn test_wrap_with_prefix() {
19197 assert_eq!(
19198 wrap_with_prefix(
19199 "# ".to_string(),
19200 "abcdefg".to_string(),
19201 4,
19202 NonZeroU32::new(4).unwrap(),
19203 false,
19204 ),
19205 "# abcdefg"
19206 );
19207 assert_eq!(
19208 wrap_with_prefix(
19209 "".to_string(),
19210 "\thello world".to_string(),
19211 8,
19212 NonZeroU32::new(4).unwrap(),
19213 false,
19214 ),
19215 "hello\nworld"
19216 );
19217 assert_eq!(
19218 wrap_with_prefix(
19219 "// ".to_string(),
19220 "xx \nyy zz aa bb cc".to_string(),
19221 12,
19222 NonZeroU32::new(4).unwrap(),
19223 false,
19224 ),
19225 "// xx yy zz\n// aa bb cc"
19226 );
19227 assert_eq!(
19228 wrap_with_prefix(
19229 String::new(),
19230 "这是什么 \n 钢笔".to_string(),
19231 3,
19232 NonZeroU32::new(4).unwrap(),
19233 false,
19234 ),
19235 "这是什\n么 钢\n笔"
19236 );
19237}
19238
19239pub trait CollaborationHub {
19240 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19241 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19242 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19243}
19244
19245impl CollaborationHub for Entity<Project> {
19246 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19247 self.read(cx).collaborators()
19248 }
19249
19250 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19251 self.read(cx).user_store().read(cx).participant_indices()
19252 }
19253
19254 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19255 let this = self.read(cx);
19256 let user_ids = this.collaborators().values().map(|c| c.user_id);
19257 this.user_store().read_with(cx, |user_store, cx| {
19258 user_store.participant_names(user_ids, cx)
19259 })
19260 }
19261}
19262
19263pub trait SemanticsProvider {
19264 fn hover(
19265 &self,
19266 buffer: &Entity<Buffer>,
19267 position: text::Anchor,
19268 cx: &mut App,
19269 ) -> Option<Task<Vec<project::Hover>>>;
19270
19271 fn inline_values(
19272 &self,
19273 buffer_handle: Entity<Buffer>,
19274 range: Range<text::Anchor>,
19275 cx: &mut App,
19276 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19277
19278 fn inlay_hints(
19279 &self,
19280 buffer_handle: Entity<Buffer>,
19281 range: Range<text::Anchor>,
19282 cx: &mut App,
19283 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19284
19285 fn resolve_inlay_hint(
19286 &self,
19287 hint: InlayHint,
19288 buffer_handle: Entity<Buffer>,
19289 server_id: LanguageServerId,
19290 cx: &mut App,
19291 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19292
19293 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19294
19295 fn document_highlights(
19296 &self,
19297 buffer: &Entity<Buffer>,
19298 position: text::Anchor,
19299 cx: &mut App,
19300 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19301
19302 fn definitions(
19303 &self,
19304 buffer: &Entity<Buffer>,
19305 position: text::Anchor,
19306 kind: GotoDefinitionKind,
19307 cx: &mut App,
19308 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19309
19310 fn range_for_rename(
19311 &self,
19312 buffer: &Entity<Buffer>,
19313 position: text::Anchor,
19314 cx: &mut App,
19315 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19316
19317 fn perform_rename(
19318 &self,
19319 buffer: &Entity<Buffer>,
19320 position: text::Anchor,
19321 new_name: String,
19322 cx: &mut App,
19323 ) -> Option<Task<Result<ProjectTransaction>>>;
19324}
19325
19326pub trait CompletionProvider {
19327 fn completions(
19328 &self,
19329 excerpt_id: ExcerptId,
19330 buffer: &Entity<Buffer>,
19331 buffer_position: text::Anchor,
19332 trigger: CompletionContext,
19333 window: &mut Window,
19334 cx: &mut Context<Editor>,
19335 ) -> Task<Result<Option<Vec<Completion>>>>;
19336
19337 fn resolve_completions(
19338 &self,
19339 buffer: Entity<Buffer>,
19340 completion_indices: Vec<usize>,
19341 completions: Rc<RefCell<Box<[Completion]>>>,
19342 cx: &mut Context<Editor>,
19343 ) -> Task<Result<bool>>;
19344
19345 fn apply_additional_edits_for_completion(
19346 &self,
19347 _buffer: Entity<Buffer>,
19348 _completions: Rc<RefCell<Box<[Completion]>>>,
19349 _completion_index: usize,
19350 _push_to_history: bool,
19351 _cx: &mut Context<Editor>,
19352 ) -> Task<Result<Option<language::Transaction>>> {
19353 Task::ready(Ok(None))
19354 }
19355
19356 fn is_completion_trigger(
19357 &self,
19358 buffer: &Entity<Buffer>,
19359 position: language::Anchor,
19360 text: &str,
19361 trigger_in_words: bool,
19362 cx: &mut Context<Editor>,
19363 ) -> bool;
19364
19365 fn sort_completions(&self) -> bool {
19366 true
19367 }
19368
19369 fn filter_completions(&self) -> bool {
19370 true
19371 }
19372}
19373
19374pub trait CodeActionProvider {
19375 fn id(&self) -> Arc<str>;
19376
19377 fn code_actions(
19378 &self,
19379 buffer: &Entity<Buffer>,
19380 range: Range<text::Anchor>,
19381 window: &mut Window,
19382 cx: &mut App,
19383 ) -> Task<Result<Vec<CodeAction>>>;
19384
19385 fn apply_code_action(
19386 &self,
19387 buffer_handle: Entity<Buffer>,
19388 action: CodeAction,
19389 excerpt_id: ExcerptId,
19390 push_to_history: bool,
19391 window: &mut Window,
19392 cx: &mut App,
19393 ) -> Task<Result<ProjectTransaction>>;
19394}
19395
19396impl CodeActionProvider for Entity<Project> {
19397 fn id(&self) -> Arc<str> {
19398 "project".into()
19399 }
19400
19401 fn code_actions(
19402 &self,
19403 buffer: &Entity<Buffer>,
19404 range: Range<text::Anchor>,
19405 _window: &mut Window,
19406 cx: &mut App,
19407 ) -> Task<Result<Vec<CodeAction>>> {
19408 self.update(cx, |project, cx| {
19409 let code_lens = project.code_lens(buffer, range.clone(), cx);
19410 let code_actions = project.code_actions(buffer, range, None, cx);
19411 cx.background_spawn(async move {
19412 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19413 Ok(code_lens
19414 .context("code lens fetch")?
19415 .into_iter()
19416 .chain(code_actions.context("code action fetch")?)
19417 .collect())
19418 })
19419 })
19420 }
19421
19422 fn apply_code_action(
19423 &self,
19424 buffer_handle: Entity<Buffer>,
19425 action: CodeAction,
19426 _excerpt_id: ExcerptId,
19427 push_to_history: bool,
19428 _window: &mut Window,
19429 cx: &mut App,
19430 ) -> Task<Result<ProjectTransaction>> {
19431 self.update(cx, |project, cx| {
19432 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19433 })
19434 }
19435}
19436
19437fn snippet_completions(
19438 project: &Project,
19439 buffer: &Entity<Buffer>,
19440 buffer_position: text::Anchor,
19441 cx: &mut App,
19442) -> Task<Result<Vec<Completion>>> {
19443 let languages = buffer.read(cx).languages_at(buffer_position);
19444 let snippet_store = project.snippets().read(cx);
19445
19446 let scopes: Vec<_> = languages
19447 .iter()
19448 .filter_map(|language| {
19449 let language_name = language.lsp_id();
19450 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19451
19452 if snippets.is_empty() {
19453 None
19454 } else {
19455 Some((language.default_scope(), snippets))
19456 }
19457 })
19458 .collect();
19459
19460 if scopes.is_empty() {
19461 return Task::ready(Ok(vec![]));
19462 }
19463
19464 let snapshot = buffer.read(cx).text_snapshot();
19465 let chars: String = snapshot
19466 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19467 .collect();
19468 let executor = cx.background_executor().clone();
19469
19470 cx.background_spawn(async move {
19471 let mut all_results: Vec<Completion> = Vec::new();
19472 for (scope, snippets) in scopes.into_iter() {
19473 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19474 let mut last_word = chars
19475 .chars()
19476 .take_while(|c| classifier.is_word(*c))
19477 .collect::<String>();
19478 last_word = last_word.chars().rev().collect();
19479
19480 if last_word.is_empty() {
19481 return Ok(vec![]);
19482 }
19483
19484 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19485 let to_lsp = |point: &text::Anchor| {
19486 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19487 point_to_lsp(end)
19488 };
19489 let lsp_end = to_lsp(&buffer_position);
19490
19491 let candidates = snippets
19492 .iter()
19493 .enumerate()
19494 .flat_map(|(ix, snippet)| {
19495 snippet
19496 .prefix
19497 .iter()
19498 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19499 })
19500 .collect::<Vec<StringMatchCandidate>>();
19501
19502 let mut matches = fuzzy::match_strings(
19503 &candidates,
19504 &last_word,
19505 last_word.chars().any(|c| c.is_uppercase()),
19506 100,
19507 &Default::default(),
19508 executor.clone(),
19509 )
19510 .await;
19511
19512 // Remove all candidates where the query's start does not match the start of any word in the candidate
19513 if let Some(query_start) = last_word.chars().next() {
19514 matches.retain(|string_match| {
19515 split_words(&string_match.string).any(|word| {
19516 // Check that the first codepoint of the word as lowercase matches the first
19517 // codepoint of the query as lowercase
19518 word.chars()
19519 .flat_map(|codepoint| codepoint.to_lowercase())
19520 .zip(query_start.to_lowercase())
19521 .all(|(word_cp, query_cp)| word_cp == query_cp)
19522 })
19523 });
19524 }
19525
19526 let matched_strings = matches
19527 .into_iter()
19528 .map(|m| m.string)
19529 .collect::<HashSet<_>>();
19530
19531 let mut result: Vec<Completion> = snippets
19532 .iter()
19533 .filter_map(|snippet| {
19534 let matching_prefix = snippet
19535 .prefix
19536 .iter()
19537 .find(|prefix| matched_strings.contains(*prefix))?;
19538 let start = as_offset - last_word.len();
19539 let start = snapshot.anchor_before(start);
19540 let range = start..buffer_position;
19541 let lsp_start = to_lsp(&start);
19542 let lsp_range = lsp::Range {
19543 start: lsp_start,
19544 end: lsp_end,
19545 };
19546 Some(Completion {
19547 replace_range: range,
19548 new_text: snippet.body.clone(),
19549 source: CompletionSource::Lsp {
19550 insert_range: None,
19551 server_id: LanguageServerId(usize::MAX),
19552 resolved: true,
19553 lsp_completion: Box::new(lsp::CompletionItem {
19554 label: snippet.prefix.first().unwrap().clone(),
19555 kind: Some(CompletionItemKind::SNIPPET),
19556 label_details: snippet.description.as_ref().map(|description| {
19557 lsp::CompletionItemLabelDetails {
19558 detail: Some(description.clone()),
19559 description: None,
19560 }
19561 }),
19562 insert_text_format: Some(InsertTextFormat::SNIPPET),
19563 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19564 lsp::InsertReplaceEdit {
19565 new_text: snippet.body.clone(),
19566 insert: lsp_range,
19567 replace: lsp_range,
19568 },
19569 )),
19570 filter_text: Some(snippet.body.clone()),
19571 sort_text: Some(char::MAX.to_string()),
19572 ..lsp::CompletionItem::default()
19573 }),
19574 lsp_defaults: None,
19575 },
19576 label: CodeLabel {
19577 text: matching_prefix.clone(),
19578 runs: Vec::new(),
19579 filter_range: 0..matching_prefix.len(),
19580 },
19581 icon_path: None,
19582 documentation: snippet.description.clone().map(|description| {
19583 CompletionDocumentation::SingleLine(description.into())
19584 }),
19585 insert_text_mode: None,
19586 confirm: None,
19587 })
19588 })
19589 .collect();
19590
19591 all_results.append(&mut result);
19592 }
19593
19594 Ok(all_results)
19595 })
19596}
19597
19598impl CompletionProvider for Entity<Project> {
19599 fn completions(
19600 &self,
19601 _excerpt_id: ExcerptId,
19602 buffer: &Entity<Buffer>,
19603 buffer_position: text::Anchor,
19604 options: CompletionContext,
19605 _window: &mut Window,
19606 cx: &mut Context<Editor>,
19607 ) -> Task<Result<Option<Vec<Completion>>>> {
19608 self.update(cx, |project, cx| {
19609 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19610 let project_completions = project.completions(buffer, buffer_position, options, cx);
19611 cx.background_spawn(async move {
19612 let snippets_completions = snippets.await?;
19613 match project_completions.await? {
19614 Some(mut completions) => {
19615 completions.extend(snippets_completions);
19616 Ok(Some(completions))
19617 }
19618 None => {
19619 if snippets_completions.is_empty() {
19620 Ok(None)
19621 } else {
19622 Ok(Some(snippets_completions))
19623 }
19624 }
19625 }
19626 })
19627 })
19628 }
19629
19630 fn resolve_completions(
19631 &self,
19632 buffer: Entity<Buffer>,
19633 completion_indices: Vec<usize>,
19634 completions: Rc<RefCell<Box<[Completion]>>>,
19635 cx: &mut Context<Editor>,
19636 ) -> Task<Result<bool>> {
19637 self.update(cx, |project, cx| {
19638 project.lsp_store().update(cx, |lsp_store, cx| {
19639 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19640 })
19641 })
19642 }
19643
19644 fn apply_additional_edits_for_completion(
19645 &self,
19646 buffer: Entity<Buffer>,
19647 completions: Rc<RefCell<Box<[Completion]>>>,
19648 completion_index: usize,
19649 push_to_history: bool,
19650 cx: &mut Context<Editor>,
19651 ) -> Task<Result<Option<language::Transaction>>> {
19652 self.update(cx, |project, cx| {
19653 project.lsp_store().update(cx, |lsp_store, cx| {
19654 lsp_store.apply_additional_edits_for_completion(
19655 buffer,
19656 completions,
19657 completion_index,
19658 push_to_history,
19659 cx,
19660 )
19661 })
19662 })
19663 }
19664
19665 fn is_completion_trigger(
19666 &self,
19667 buffer: &Entity<Buffer>,
19668 position: language::Anchor,
19669 text: &str,
19670 trigger_in_words: bool,
19671 cx: &mut Context<Editor>,
19672 ) -> bool {
19673 let mut chars = text.chars();
19674 let char = if let Some(char) = chars.next() {
19675 char
19676 } else {
19677 return false;
19678 };
19679 if chars.next().is_some() {
19680 return false;
19681 }
19682
19683 let buffer = buffer.read(cx);
19684 let snapshot = buffer.snapshot();
19685 if !snapshot.settings_at(position, cx).show_completions_on_input {
19686 return false;
19687 }
19688 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19689 if trigger_in_words && classifier.is_word(char) {
19690 return true;
19691 }
19692
19693 buffer.completion_triggers().contains(text)
19694 }
19695}
19696
19697impl SemanticsProvider for Entity<Project> {
19698 fn hover(
19699 &self,
19700 buffer: &Entity<Buffer>,
19701 position: text::Anchor,
19702 cx: &mut App,
19703 ) -> Option<Task<Vec<project::Hover>>> {
19704 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19705 }
19706
19707 fn document_highlights(
19708 &self,
19709 buffer: &Entity<Buffer>,
19710 position: text::Anchor,
19711 cx: &mut App,
19712 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19713 Some(self.update(cx, |project, cx| {
19714 project.document_highlights(buffer, position, cx)
19715 }))
19716 }
19717
19718 fn definitions(
19719 &self,
19720 buffer: &Entity<Buffer>,
19721 position: text::Anchor,
19722 kind: GotoDefinitionKind,
19723 cx: &mut App,
19724 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19725 Some(self.update(cx, |project, cx| match kind {
19726 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19727 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19728 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19729 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19730 }))
19731 }
19732
19733 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19734 // TODO: make this work for remote projects
19735 self.update(cx, |project, cx| {
19736 if project
19737 .active_debug_session(cx)
19738 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19739 {
19740 return true;
19741 }
19742
19743 buffer.update(cx, |buffer, cx| {
19744 project.any_language_server_supports_inlay_hints(buffer, cx)
19745 })
19746 })
19747 }
19748
19749 fn inline_values(
19750 &self,
19751 buffer_handle: Entity<Buffer>,
19752 range: Range<text::Anchor>,
19753 cx: &mut App,
19754 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19755 self.update(cx, |project, cx| {
19756 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19757
19758 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19759 })
19760 }
19761
19762 fn inlay_hints(
19763 &self,
19764 buffer_handle: Entity<Buffer>,
19765 range: Range<text::Anchor>,
19766 cx: &mut App,
19767 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19768 Some(self.update(cx, |project, cx| {
19769 project.inlay_hints(buffer_handle, range, cx)
19770 }))
19771 }
19772
19773 fn resolve_inlay_hint(
19774 &self,
19775 hint: InlayHint,
19776 buffer_handle: Entity<Buffer>,
19777 server_id: LanguageServerId,
19778 cx: &mut App,
19779 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19780 Some(self.update(cx, |project, cx| {
19781 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19782 }))
19783 }
19784
19785 fn range_for_rename(
19786 &self,
19787 buffer: &Entity<Buffer>,
19788 position: text::Anchor,
19789 cx: &mut App,
19790 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19791 Some(self.update(cx, |project, cx| {
19792 let buffer = buffer.clone();
19793 let task = project.prepare_rename(buffer.clone(), position, cx);
19794 cx.spawn(async move |_, cx| {
19795 Ok(match task.await? {
19796 PrepareRenameResponse::Success(range) => Some(range),
19797 PrepareRenameResponse::InvalidPosition => None,
19798 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19799 // Fallback on using TreeSitter info to determine identifier range
19800 buffer.update(cx, |buffer, _| {
19801 let snapshot = buffer.snapshot();
19802 let (range, kind) = snapshot.surrounding_word(position);
19803 if kind != Some(CharKind::Word) {
19804 return None;
19805 }
19806 Some(
19807 snapshot.anchor_before(range.start)
19808 ..snapshot.anchor_after(range.end),
19809 )
19810 })?
19811 }
19812 })
19813 })
19814 }))
19815 }
19816
19817 fn perform_rename(
19818 &self,
19819 buffer: &Entity<Buffer>,
19820 position: text::Anchor,
19821 new_name: String,
19822 cx: &mut App,
19823 ) -> Option<Task<Result<ProjectTransaction>>> {
19824 Some(self.update(cx, |project, cx| {
19825 project.perform_rename(buffer.clone(), position, new_name, cx)
19826 }))
19827 }
19828}
19829
19830fn inlay_hint_settings(
19831 location: Anchor,
19832 snapshot: &MultiBufferSnapshot,
19833 cx: &mut Context<Editor>,
19834) -> InlayHintSettings {
19835 let file = snapshot.file_at(location);
19836 let language = snapshot.language_at(location).map(|l| l.name());
19837 language_settings(language, file, cx).inlay_hints
19838}
19839
19840fn consume_contiguous_rows(
19841 contiguous_row_selections: &mut Vec<Selection<Point>>,
19842 selection: &Selection<Point>,
19843 display_map: &DisplaySnapshot,
19844 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19845) -> (MultiBufferRow, MultiBufferRow) {
19846 contiguous_row_selections.push(selection.clone());
19847 let start_row = MultiBufferRow(selection.start.row);
19848 let mut end_row = ending_row(selection, display_map);
19849
19850 while let Some(next_selection) = selections.peek() {
19851 if next_selection.start.row <= end_row.0 {
19852 end_row = ending_row(next_selection, display_map);
19853 contiguous_row_selections.push(selections.next().unwrap().clone());
19854 } else {
19855 break;
19856 }
19857 }
19858 (start_row, end_row)
19859}
19860
19861fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19862 if next_selection.end.column > 0 || next_selection.is_empty() {
19863 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19864 } else {
19865 MultiBufferRow(next_selection.end.row)
19866 }
19867}
19868
19869impl EditorSnapshot {
19870 pub fn remote_selections_in_range<'a>(
19871 &'a self,
19872 range: &'a Range<Anchor>,
19873 collaboration_hub: &dyn CollaborationHub,
19874 cx: &'a App,
19875 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19876 let participant_names = collaboration_hub.user_names(cx);
19877 let participant_indices = collaboration_hub.user_participant_indices(cx);
19878 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19879 let collaborators_by_replica_id = collaborators_by_peer_id
19880 .iter()
19881 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19882 .collect::<HashMap<_, _>>();
19883 self.buffer_snapshot
19884 .selections_in_range(range, false)
19885 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19886 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19887 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19888 let user_name = participant_names.get(&collaborator.user_id).cloned();
19889 Some(RemoteSelection {
19890 replica_id,
19891 selection,
19892 cursor_shape,
19893 line_mode,
19894 participant_index,
19895 peer_id: collaborator.peer_id,
19896 user_name,
19897 })
19898 })
19899 }
19900
19901 pub fn hunks_for_ranges(
19902 &self,
19903 ranges: impl IntoIterator<Item = Range<Point>>,
19904 ) -> Vec<MultiBufferDiffHunk> {
19905 let mut hunks = Vec::new();
19906 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19907 HashMap::default();
19908 for query_range in ranges {
19909 let query_rows =
19910 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19911 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19912 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19913 ) {
19914 // Include deleted hunks that are adjacent to the query range, because
19915 // otherwise they would be missed.
19916 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19917 if hunk.status().is_deleted() {
19918 intersects_range |= hunk.row_range.start == query_rows.end;
19919 intersects_range |= hunk.row_range.end == query_rows.start;
19920 }
19921 if intersects_range {
19922 if !processed_buffer_rows
19923 .entry(hunk.buffer_id)
19924 .or_default()
19925 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19926 {
19927 continue;
19928 }
19929 hunks.push(hunk);
19930 }
19931 }
19932 }
19933
19934 hunks
19935 }
19936
19937 fn display_diff_hunks_for_rows<'a>(
19938 &'a self,
19939 display_rows: Range<DisplayRow>,
19940 folded_buffers: &'a HashSet<BufferId>,
19941 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19942 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19943 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19944
19945 self.buffer_snapshot
19946 .diff_hunks_in_range(buffer_start..buffer_end)
19947 .filter_map(|hunk| {
19948 if folded_buffers.contains(&hunk.buffer_id) {
19949 return None;
19950 }
19951
19952 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19953 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19954
19955 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19956 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19957
19958 let display_hunk = if hunk_display_start.column() != 0 {
19959 DisplayDiffHunk::Folded {
19960 display_row: hunk_display_start.row(),
19961 }
19962 } else {
19963 let mut end_row = hunk_display_end.row();
19964 if hunk_display_end.column() > 0 {
19965 end_row.0 += 1;
19966 }
19967 let is_created_file = hunk.is_created_file();
19968 DisplayDiffHunk::Unfolded {
19969 status: hunk.status(),
19970 diff_base_byte_range: hunk.diff_base_byte_range,
19971 display_row_range: hunk_display_start.row()..end_row,
19972 multi_buffer_range: Anchor::range_in_buffer(
19973 hunk.excerpt_id,
19974 hunk.buffer_id,
19975 hunk.buffer_range,
19976 ),
19977 is_created_file,
19978 }
19979 };
19980
19981 Some(display_hunk)
19982 })
19983 }
19984
19985 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19986 self.display_snapshot.buffer_snapshot.language_at(position)
19987 }
19988
19989 pub fn is_focused(&self) -> bool {
19990 self.is_focused
19991 }
19992
19993 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
19994 self.placeholder_text.as_ref()
19995 }
19996
19997 pub fn scroll_position(&self) -> gpui::Point<f32> {
19998 self.scroll_anchor.scroll_position(&self.display_snapshot)
19999 }
20000
20001 fn gutter_dimensions(
20002 &self,
20003 font_id: FontId,
20004 font_size: Pixels,
20005 max_line_number_width: Pixels,
20006 cx: &App,
20007 ) -> Option<GutterDimensions> {
20008 if !self.show_gutter {
20009 return None;
20010 }
20011
20012 let descent = cx.text_system().descent(font_id, font_size);
20013 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20014 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20015
20016 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20017 matches!(
20018 ProjectSettings::get_global(cx).git.git_gutter,
20019 Some(GitGutterSetting::TrackedFiles)
20020 )
20021 });
20022 let gutter_settings = EditorSettings::get_global(cx).gutter;
20023 let show_line_numbers = self
20024 .show_line_numbers
20025 .unwrap_or(gutter_settings.line_numbers);
20026 let line_gutter_width = if show_line_numbers {
20027 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20028 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20029 max_line_number_width.max(min_width_for_number_on_gutter)
20030 } else {
20031 0.0.into()
20032 };
20033
20034 let show_code_actions = self
20035 .show_code_actions
20036 .unwrap_or(gutter_settings.code_actions);
20037
20038 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20039 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20040
20041 let git_blame_entries_width =
20042 self.git_blame_gutter_max_author_length
20043 .map(|max_author_length| {
20044 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20045 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20046
20047 /// The number of characters to dedicate to gaps and margins.
20048 const SPACING_WIDTH: usize = 4;
20049
20050 let max_char_count = max_author_length.min(renderer.max_author_length())
20051 + ::git::SHORT_SHA_LENGTH
20052 + MAX_RELATIVE_TIMESTAMP.len()
20053 + SPACING_WIDTH;
20054
20055 em_advance * max_char_count
20056 });
20057
20058 let is_singleton = self.buffer_snapshot.is_singleton();
20059
20060 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20061 left_padding += if !is_singleton {
20062 em_width * 4.0
20063 } else if show_code_actions || show_runnables || show_breakpoints {
20064 em_width * 3.0
20065 } else if show_git_gutter && show_line_numbers {
20066 em_width * 2.0
20067 } else if show_git_gutter || show_line_numbers {
20068 em_width
20069 } else {
20070 px(0.)
20071 };
20072
20073 let shows_folds = is_singleton && gutter_settings.folds;
20074
20075 let right_padding = if shows_folds && show_line_numbers {
20076 em_width * 4.0
20077 } else if shows_folds || (!is_singleton && show_line_numbers) {
20078 em_width * 3.0
20079 } else if show_line_numbers {
20080 em_width
20081 } else {
20082 px(0.)
20083 };
20084
20085 Some(GutterDimensions {
20086 left_padding,
20087 right_padding,
20088 width: line_gutter_width + left_padding + right_padding,
20089 margin: -descent,
20090 git_blame_entries_width,
20091 })
20092 }
20093
20094 pub fn render_crease_toggle(
20095 &self,
20096 buffer_row: MultiBufferRow,
20097 row_contains_cursor: bool,
20098 editor: Entity<Editor>,
20099 window: &mut Window,
20100 cx: &mut App,
20101 ) -> Option<AnyElement> {
20102 let folded = self.is_line_folded(buffer_row);
20103 let mut is_foldable = false;
20104
20105 if let Some(crease) = self
20106 .crease_snapshot
20107 .query_row(buffer_row, &self.buffer_snapshot)
20108 {
20109 is_foldable = true;
20110 match crease {
20111 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20112 if let Some(render_toggle) = render_toggle {
20113 let toggle_callback =
20114 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20115 if folded {
20116 editor.update(cx, |editor, cx| {
20117 editor.fold_at(buffer_row, window, cx)
20118 });
20119 } else {
20120 editor.update(cx, |editor, cx| {
20121 editor.unfold_at(buffer_row, window, cx)
20122 });
20123 }
20124 });
20125 return Some((render_toggle)(
20126 buffer_row,
20127 folded,
20128 toggle_callback,
20129 window,
20130 cx,
20131 ));
20132 }
20133 }
20134 }
20135 }
20136
20137 is_foldable |= self.starts_indent(buffer_row);
20138
20139 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20140 Some(
20141 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20142 .toggle_state(folded)
20143 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20144 if folded {
20145 this.unfold_at(buffer_row, window, cx);
20146 } else {
20147 this.fold_at(buffer_row, window, cx);
20148 }
20149 }))
20150 .into_any_element(),
20151 )
20152 } else {
20153 None
20154 }
20155 }
20156
20157 pub fn render_crease_trailer(
20158 &self,
20159 buffer_row: MultiBufferRow,
20160 window: &mut Window,
20161 cx: &mut App,
20162 ) -> Option<AnyElement> {
20163 let folded = self.is_line_folded(buffer_row);
20164 if let Crease::Inline { render_trailer, .. } = self
20165 .crease_snapshot
20166 .query_row(buffer_row, &self.buffer_snapshot)?
20167 {
20168 let render_trailer = render_trailer.as_ref()?;
20169 Some(render_trailer(buffer_row, folded, window, cx))
20170 } else {
20171 None
20172 }
20173 }
20174}
20175
20176impl Deref for EditorSnapshot {
20177 type Target = DisplaySnapshot;
20178
20179 fn deref(&self) -> &Self::Target {
20180 &self.display_snapshot
20181 }
20182}
20183
20184#[derive(Clone, Debug, PartialEq, Eq)]
20185pub enum EditorEvent {
20186 InputIgnored {
20187 text: Arc<str>,
20188 },
20189 InputHandled {
20190 utf16_range_to_replace: Option<Range<isize>>,
20191 text: Arc<str>,
20192 },
20193 ExcerptsAdded {
20194 buffer: Entity<Buffer>,
20195 predecessor: ExcerptId,
20196 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20197 },
20198 ExcerptsRemoved {
20199 ids: Vec<ExcerptId>,
20200 removed_buffer_ids: Vec<BufferId>,
20201 },
20202 BufferFoldToggled {
20203 ids: Vec<ExcerptId>,
20204 folded: bool,
20205 },
20206 ExcerptsEdited {
20207 ids: Vec<ExcerptId>,
20208 },
20209 ExcerptsExpanded {
20210 ids: Vec<ExcerptId>,
20211 },
20212 BufferEdited,
20213 Edited {
20214 transaction_id: clock::Lamport,
20215 },
20216 Reparsed(BufferId),
20217 Focused,
20218 FocusedIn,
20219 Blurred,
20220 DirtyChanged,
20221 Saved,
20222 TitleChanged,
20223 DiffBaseChanged,
20224 SelectionsChanged {
20225 local: bool,
20226 },
20227 ScrollPositionChanged {
20228 local: bool,
20229 autoscroll: bool,
20230 },
20231 Closed,
20232 TransactionUndone {
20233 transaction_id: clock::Lamport,
20234 },
20235 TransactionBegun {
20236 transaction_id: clock::Lamport,
20237 },
20238 Reloaded,
20239 CursorShapeChanged,
20240 PushedToNavHistory {
20241 anchor: Anchor,
20242 is_deactivate: bool,
20243 },
20244}
20245
20246impl EventEmitter<EditorEvent> for Editor {}
20247
20248impl Focusable for Editor {
20249 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20250 self.focus_handle.clone()
20251 }
20252}
20253
20254impl Render for Editor {
20255 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20256 let settings = ThemeSettings::get_global(cx);
20257
20258 let mut text_style = match self.mode {
20259 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20260 color: cx.theme().colors().editor_foreground,
20261 font_family: settings.ui_font.family.clone(),
20262 font_features: settings.ui_font.features.clone(),
20263 font_fallbacks: settings.ui_font.fallbacks.clone(),
20264 font_size: rems(0.875).into(),
20265 font_weight: settings.ui_font.weight,
20266 line_height: relative(settings.buffer_line_height.value()),
20267 ..Default::default()
20268 },
20269 EditorMode::Full { .. } => TextStyle {
20270 color: cx.theme().colors().editor_foreground,
20271 font_family: settings.buffer_font.family.clone(),
20272 font_features: settings.buffer_font.features.clone(),
20273 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20274 font_size: settings.buffer_font_size(cx).into(),
20275 font_weight: settings.buffer_font.weight,
20276 line_height: relative(settings.buffer_line_height.value()),
20277 ..Default::default()
20278 },
20279 };
20280 if let Some(text_style_refinement) = &self.text_style_refinement {
20281 text_style.refine(text_style_refinement)
20282 }
20283
20284 let background = match self.mode {
20285 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20286 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20287 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20288 };
20289
20290 EditorElement::new(
20291 &cx.entity(),
20292 EditorStyle {
20293 background,
20294 local_player: cx.theme().players().local(),
20295 text: text_style,
20296 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20297 syntax: cx.theme().syntax().clone(),
20298 status: cx.theme().status().clone(),
20299 inlay_hints_style: make_inlay_hints_style(cx),
20300 inline_completion_styles: make_suggestion_styles(cx),
20301 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20302 },
20303 )
20304 }
20305}
20306
20307impl EntityInputHandler for Editor {
20308 fn text_for_range(
20309 &mut self,
20310 range_utf16: Range<usize>,
20311 adjusted_range: &mut Option<Range<usize>>,
20312 _: &mut Window,
20313 cx: &mut Context<Self>,
20314 ) -> Option<String> {
20315 let snapshot = self.buffer.read(cx).read(cx);
20316 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20317 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20318 if (start.0..end.0) != range_utf16 {
20319 adjusted_range.replace(start.0..end.0);
20320 }
20321 Some(snapshot.text_for_range(start..end).collect())
20322 }
20323
20324 fn selected_text_range(
20325 &mut self,
20326 ignore_disabled_input: bool,
20327 _: &mut Window,
20328 cx: &mut Context<Self>,
20329 ) -> Option<UTF16Selection> {
20330 // Prevent the IME menu from appearing when holding down an alphabetic key
20331 // while input is disabled.
20332 if !ignore_disabled_input && !self.input_enabled {
20333 return None;
20334 }
20335
20336 let selection = self.selections.newest::<OffsetUtf16>(cx);
20337 let range = selection.range();
20338
20339 Some(UTF16Selection {
20340 range: range.start.0..range.end.0,
20341 reversed: selection.reversed,
20342 })
20343 }
20344
20345 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20346 let snapshot = self.buffer.read(cx).read(cx);
20347 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20348 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20349 }
20350
20351 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20352 self.clear_highlights::<InputComposition>(cx);
20353 self.ime_transaction.take();
20354 }
20355
20356 fn replace_text_in_range(
20357 &mut self,
20358 range_utf16: Option<Range<usize>>,
20359 text: &str,
20360 window: &mut Window,
20361 cx: &mut Context<Self>,
20362 ) {
20363 if !self.input_enabled {
20364 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20365 return;
20366 }
20367
20368 self.transact(window, cx, |this, window, cx| {
20369 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20370 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20371 Some(this.selection_replacement_ranges(range_utf16, cx))
20372 } else {
20373 this.marked_text_ranges(cx)
20374 };
20375
20376 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20377 let newest_selection_id = this.selections.newest_anchor().id;
20378 this.selections
20379 .all::<OffsetUtf16>(cx)
20380 .iter()
20381 .zip(ranges_to_replace.iter())
20382 .find_map(|(selection, range)| {
20383 if selection.id == newest_selection_id {
20384 Some(
20385 (range.start.0 as isize - selection.head().0 as isize)
20386 ..(range.end.0 as isize - selection.head().0 as isize),
20387 )
20388 } else {
20389 None
20390 }
20391 })
20392 });
20393
20394 cx.emit(EditorEvent::InputHandled {
20395 utf16_range_to_replace: range_to_replace,
20396 text: text.into(),
20397 });
20398
20399 if let Some(new_selected_ranges) = new_selected_ranges {
20400 this.change_selections(None, window, cx, |selections| {
20401 selections.select_ranges(new_selected_ranges)
20402 });
20403 this.backspace(&Default::default(), window, cx);
20404 }
20405
20406 this.handle_input(text, window, cx);
20407 });
20408
20409 if let Some(transaction) = self.ime_transaction {
20410 self.buffer.update(cx, |buffer, cx| {
20411 buffer.group_until_transaction(transaction, cx);
20412 });
20413 }
20414
20415 self.unmark_text(window, cx);
20416 }
20417
20418 fn replace_and_mark_text_in_range(
20419 &mut self,
20420 range_utf16: Option<Range<usize>>,
20421 text: &str,
20422 new_selected_range_utf16: Option<Range<usize>>,
20423 window: &mut Window,
20424 cx: &mut Context<Self>,
20425 ) {
20426 if !self.input_enabled {
20427 return;
20428 }
20429
20430 let transaction = self.transact(window, cx, |this, window, cx| {
20431 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20432 let snapshot = this.buffer.read(cx).read(cx);
20433 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20434 for marked_range in &mut marked_ranges {
20435 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20436 marked_range.start.0 += relative_range_utf16.start;
20437 marked_range.start =
20438 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20439 marked_range.end =
20440 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20441 }
20442 }
20443 Some(marked_ranges)
20444 } else if let Some(range_utf16) = range_utf16 {
20445 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20446 Some(this.selection_replacement_ranges(range_utf16, cx))
20447 } else {
20448 None
20449 };
20450
20451 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20452 let newest_selection_id = this.selections.newest_anchor().id;
20453 this.selections
20454 .all::<OffsetUtf16>(cx)
20455 .iter()
20456 .zip(ranges_to_replace.iter())
20457 .find_map(|(selection, range)| {
20458 if selection.id == newest_selection_id {
20459 Some(
20460 (range.start.0 as isize - selection.head().0 as isize)
20461 ..(range.end.0 as isize - selection.head().0 as isize),
20462 )
20463 } else {
20464 None
20465 }
20466 })
20467 });
20468
20469 cx.emit(EditorEvent::InputHandled {
20470 utf16_range_to_replace: range_to_replace,
20471 text: text.into(),
20472 });
20473
20474 if let Some(ranges) = ranges_to_replace {
20475 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20476 }
20477
20478 let marked_ranges = {
20479 let snapshot = this.buffer.read(cx).read(cx);
20480 this.selections
20481 .disjoint_anchors()
20482 .iter()
20483 .map(|selection| {
20484 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20485 })
20486 .collect::<Vec<_>>()
20487 };
20488
20489 if text.is_empty() {
20490 this.unmark_text(window, cx);
20491 } else {
20492 this.highlight_text::<InputComposition>(
20493 marked_ranges.clone(),
20494 HighlightStyle {
20495 underline: Some(UnderlineStyle {
20496 thickness: px(1.),
20497 color: None,
20498 wavy: false,
20499 }),
20500 ..Default::default()
20501 },
20502 cx,
20503 );
20504 }
20505
20506 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20507 let use_autoclose = this.use_autoclose;
20508 let use_auto_surround = this.use_auto_surround;
20509 this.set_use_autoclose(false);
20510 this.set_use_auto_surround(false);
20511 this.handle_input(text, window, cx);
20512 this.set_use_autoclose(use_autoclose);
20513 this.set_use_auto_surround(use_auto_surround);
20514
20515 if let Some(new_selected_range) = new_selected_range_utf16 {
20516 let snapshot = this.buffer.read(cx).read(cx);
20517 let new_selected_ranges = marked_ranges
20518 .into_iter()
20519 .map(|marked_range| {
20520 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20521 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20522 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20523 snapshot.clip_offset_utf16(new_start, Bias::Left)
20524 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20525 })
20526 .collect::<Vec<_>>();
20527
20528 drop(snapshot);
20529 this.change_selections(None, window, cx, |selections| {
20530 selections.select_ranges(new_selected_ranges)
20531 });
20532 }
20533 });
20534
20535 self.ime_transaction = self.ime_transaction.or(transaction);
20536 if let Some(transaction) = self.ime_transaction {
20537 self.buffer.update(cx, |buffer, cx| {
20538 buffer.group_until_transaction(transaction, cx);
20539 });
20540 }
20541
20542 if self.text_highlights::<InputComposition>(cx).is_none() {
20543 self.ime_transaction.take();
20544 }
20545 }
20546
20547 fn bounds_for_range(
20548 &mut self,
20549 range_utf16: Range<usize>,
20550 element_bounds: gpui::Bounds<Pixels>,
20551 window: &mut Window,
20552 cx: &mut Context<Self>,
20553 ) -> Option<gpui::Bounds<Pixels>> {
20554 let text_layout_details = self.text_layout_details(window);
20555 let gpui::Size {
20556 width: em_width,
20557 height: line_height,
20558 } = self.character_size(window);
20559
20560 let snapshot = self.snapshot(window, cx);
20561 let scroll_position = snapshot.scroll_position();
20562 let scroll_left = scroll_position.x * em_width;
20563
20564 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20565 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20566 + self.gutter_dimensions.width
20567 + self.gutter_dimensions.margin;
20568 let y = line_height * (start.row().as_f32() - scroll_position.y);
20569
20570 Some(Bounds {
20571 origin: element_bounds.origin + point(x, y),
20572 size: size(em_width, line_height),
20573 })
20574 }
20575
20576 fn character_index_for_point(
20577 &mut self,
20578 point: gpui::Point<Pixels>,
20579 _window: &mut Window,
20580 _cx: &mut Context<Self>,
20581 ) -> Option<usize> {
20582 let position_map = self.last_position_map.as_ref()?;
20583 if !position_map.text_hitbox.contains(&point) {
20584 return None;
20585 }
20586 let display_point = position_map.point_for_position(point).previous_valid;
20587 let anchor = position_map
20588 .snapshot
20589 .display_point_to_anchor(display_point, Bias::Left);
20590 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20591 Some(utf16_offset.0)
20592 }
20593}
20594
20595trait SelectionExt {
20596 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20597 fn spanned_rows(
20598 &self,
20599 include_end_if_at_line_start: bool,
20600 map: &DisplaySnapshot,
20601 ) -> Range<MultiBufferRow>;
20602}
20603
20604impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20605 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20606 let start = self
20607 .start
20608 .to_point(&map.buffer_snapshot)
20609 .to_display_point(map);
20610 let end = self
20611 .end
20612 .to_point(&map.buffer_snapshot)
20613 .to_display_point(map);
20614 if self.reversed {
20615 end..start
20616 } else {
20617 start..end
20618 }
20619 }
20620
20621 fn spanned_rows(
20622 &self,
20623 include_end_if_at_line_start: bool,
20624 map: &DisplaySnapshot,
20625 ) -> Range<MultiBufferRow> {
20626 let start = self.start.to_point(&map.buffer_snapshot);
20627 let mut end = self.end.to_point(&map.buffer_snapshot);
20628 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20629 end.row -= 1;
20630 }
20631
20632 let buffer_start = map.prev_line_boundary(start).0;
20633 let buffer_end = map.next_line_boundary(end).0;
20634 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20635 }
20636}
20637
20638impl<T: InvalidationRegion> InvalidationStack<T> {
20639 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20640 where
20641 S: Clone + ToOffset,
20642 {
20643 while let Some(region) = self.last() {
20644 let all_selections_inside_invalidation_ranges =
20645 if selections.len() == region.ranges().len() {
20646 selections
20647 .iter()
20648 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20649 .all(|(selection, invalidation_range)| {
20650 let head = selection.head().to_offset(buffer);
20651 invalidation_range.start <= head && invalidation_range.end >= head
20652 })
20653 } else {
20654 false
20655 };
20656
20657 if all_selections_inside_invalidation_ranges {
20658 break;
20659 } else {
20660 self.pop();
20661 }
20662 }
20663 }
20664}
20665
20666impl<T> Default for InvalidationStack<T> {
20667 fn default() -> Self {
20668 Self(Default::default())
20669 }
20670}
20671
20672impl<T> Deref for InvalidationStack<T> {
20673 type Target = Vec<T>;
20674
20675 fn deref(&self) -> &Self::Target {
20676 &self.0
20677 }
20678}
20679
20680impl<T> DerefMut for InvalidationStack<T> {
20681 fn deref_mut(&mut self) -> &mut Self::Target {
20682 &mut self.0
20683 }
20684}
20685
20686impl InvalidationRegion for SnippetState {
20687 fn ranges(&self) -> &[Range<Anchor>] {
20688 &self.ranges[self.active_index]
20689 }
20690}
20691
20692fn inline_completion_edit_text(
20693 current_snapshot: &BufferSnapshot,
20694 edits: &[(Range<Anchor>, String)],
20695 edit_preview: &EditPreview,
20696 include_deletions: bool,
20697 cx: &App,
20698) -> HighlightedText {
20699 let edits = edits
20700 .iter()
20701 .map(|(anchor, text)| {
20702 (
20703 anchor.start.text_anchor..anchor.end.text_anchor,
20704 text.clone(),
20705 )
20706 })
20707 .collect::<Vec<_>>();
20708
20709 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20710}
20711
20712pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20713 match severity {
20714 DiagnosticSeverity::ERROR => colors.error,
20715 DiagnosticSeverity::WARNING => colors.warning,
20716 DiagnosticSeverity::INFORMATION => colors.info,
20717 DiagnosticSeverity::HINT => colors.info,
20718 _ => colors.ignored,
20719 }
20720}
20721
20722pub fn styled_runs_for_code_label<'a>(
20723 label: &'a CodeLabel,
20724 syntax_theme: &'a theme::SyntaxTheme,
20725) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20726 let fade_out = HighlightStyle {
20727 fade_out: Some(0.35),
20728 ..Default::default()
20729 };
20730
20731 let mut prev_end = label.filter_range.end;
20732 label
20733 .runs
20734 .iter()
20735 .enumerate()
20736 .flat_map(move |(ix, (range, highlight_id))| {
20737 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20738 style
20739 } else {
20740 return Default::default();
20741 };
20742 let mut muted_style = style;
20743 muted_style.highlight(fade_out);
20744
20745 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20746 if range.start >= label.filter_range.end {
20747 if range.start > prev_end {
20748 runs.push((prev_end..range.start, fade_out));
20749 }
20750 runs.push((range.clone(), muted_style));
20751 } else if range.end <= label.filter_range.end {
20752 runs.push((range.clone(), style));
20753 } else {
20754 runs.push((range.start..label.filter_range.end, style));
20755 runs.push((label.filter_range.end..range.end, muted_style));
20756 }
20757 prev_end = cmp::max(prev_end, range.end);
20758
20759 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20760 runs.push((prev_end..label.text.len(), fade_out));
20761 }
20762
20763 runs
20764 })
20765}
20766
20767pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20768 let mut prev_index = 0;
20769 let mut prev_codepoint: Option<char> = None;
20770 text.char_indices()
20771 .chain([(text.len(), '\0')])
20772 .filter_map(move |(index, codepoint)| {
20773 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20774 let is_boundary = index == text.len()
20775 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20776 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20777 if is_boundary {
20778 let chunk = &text[prev_index..index];
20779 prev_index = index;
20780 Some(chunk)
20781 } else {
20782 None
20783 }
20784 })
20785}
20786
20787pub trait RangeToAnchorExt: Sized {
20788 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20789
20790 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20791 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20792 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20793 }
20794}
20795
20796impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20797 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20798 let start_offset = self.start.to_offset(snapshot);
20799 let end_offset = self.end.to_offset(snapshot);
20800 if start_offset == end_offset {
20801 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20802 } else {
20803 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20804 }
20805 }
20806}
20807
20808pub trait RowExt {
20809 fn as_f32(&self) -> f32;
20810
20811 fn next_row(&self) -> Self;
20812
20813 fn previous_row(&self) -> Self;
20814
20815 fn minus(&self, other: Self) -> u32;
20816}
20817
20818impl RowExt for DisplayRow {
20819 fn as_f32(&self) -> f32 {
20820 self.0 as f32
20821 }
20822
20823 fn next_row(&self) -> Self {
20824 Self(self.0 + 1)
20825 }
20826
20827 fn previous_row(&self) -> Self {
20828 Self(self.0.saturating_sub(1))
20829 }
20830
20831 fn minus(&self, other: Self) -> u32 {
20832 self.0 - other.0
20833 }
20834}
20835
20836impl RowExt for MultiBufferRow {
20837 fn as_f32(&self) -> f32 {
20838 self.0 as f32
20839 }
20840
20841 fn next_row(&self) -> Self {
20842 Self(self.0 + 1)
20843 }
20844
20845 fn previous_row(&self) -> Self {
20846 Self(self.0.saturating_sub(1))
20847 }
20848
20849 fn minus(&self, other: Self) -> u32 {
20850 self.0 - other.0
20851 }
20852}
20853
20854trait RowRangeExt {
20855 type Row;
20856
20857 fn len(&self) -> usize;
20858
20859 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20860}
20861
20862impl RowRangeExt for Range<MultiBufferRow> {
20863 type Row = MultiBufferRow;
20864
20865 fn len(&self) -> usize {
20866 (self.end.0 - self.start.0) as usize
20867 }
20868
20869 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20870 (self.start.0..self.end.0).map(MultiBufferRow)
20871 }
20872}
20873
20874impl RowRangeExt for Range<DisplayRow> {
20875 type Row = DisplayRow;
20876
20877 fn len(&self) -> usize {
20878 (self.end.0 - self.start.0) as usize
20879 }
20880
20881 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20882 (self.start.0..self.end.0).map(DisplayRow)
20883 }
20884}
20885
20886/// If select range has more than one line, we
20887/// just point the cursor to range.start.
20888fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20889 if range.start.row == range.end.row {
20890 range
20891 } else {
20892 range.start..range.start
20893 }
20894}
20895pub struct KillRing(ClipboardItem);
20896impl Global for KillRing {}
20897
20898const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20899
20900enum BreakpointPromptEditAction {
20901 Log,
20902 Condition,
20903 HitCondition,
20904}
20905
20906struct BreakpointPromptEditor {
20907 pub(crate) prompt: Entity<Editor>,
20908 editor: WeakEntity<Editor>,
20909 breakpoint_anchor: Anchor,
20910 breakpoint: Breakpoint,
20911 edit_action: BreakpointPromptEditAction,
20912 block_ids: HashSet<CustomBlockId>,
20913 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20914 _subscriptions: Vec<Subscription>,
20915}
20916
20917impl BreakpointPromptEditor {
20918 const MAX_LINES: u8 = 4;
20919
20920 fn new(
20921 editor: WeakEntity<Editor>,
20922 breakpoint_anchor: Anchor,
20923 breakpoint: Breakpoint,
20924 edit_action: BreakpointPromptEditAction,
20925 window: &mut Window,
20926 cx: &mut Context<Self>,
20927 ) -> Self {
20928 let base_text = match edit_action {
20929 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20930 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20931 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20932 }
20933 .map(|msg| msg.to_string())
20934 .unwrap_or_default();
20935
20936 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20937 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20938
20939 let prompt = cx.new(|cx| {
20940 let mut prompt = Editor::new(
20941 EditorMode::AutoHeight {
20942 max_lines: Self::MAX_LINES as usize,
20943 },
20944 buffer,
20945 None,
20946 window,
20947 cx,
20948 );
20949 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20950 prompt.set_show_cursor_when_unfocused(false, cx);
20951 prompt.set_placeholder_text(
20952 match edit_action {
20953 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20954 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20955 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20956 },
20957 cx,
20958 );
20959
20960 prompt
20961 });
20962
20963 Self {
20964 prompt,
20965 editor,
20966 breakpoint_anchor,
20967 breakpoint,
20968 edit_action,
20969 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20970 block_ids: Default::default(),
20971 _subscriptions: vec![],
20972 }
20973 }
20974
20975 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20976 self.block_ids.extend(block_ids)
20977 }
20978
20979 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20980 if let Some(editor) = self.editor.upgrade() {
20981 let message = self
20982 .prompt
20983 .read(cx)
20984 .buffer
20985 .read(cx)
20986 .as_singleton()
20987 .expect("A multi buffer in breakpoint prompt isn't possible")
20988 .read(cx)
20989 .as_rope()
20990 .to_string();
20991
20992 editor.update(cx, |editor, cx| {
20993 editor.edit_breakpoint_at_anchor(
20994 self.breakpoint_anchor,
20995 self.breakpoint.clone(),
20996 match self.edit_action {
20997 BreakpointPromptEditAction::Log => {
20998 BreakpointEditAction::EditLogMessage(message.into())
20999 }
21000 BreakpointPromptEditAction::Condition => {
21001 BreakpointEditAction::EditCondition(message.into())
21002 }
21003 BreakpointPromptEditAction::HitCondition => {
21004 BreakpointEditAction::EditHitCondition(message.into())
21005 }
21006 },
21007 cx,
21008 );
21009
21010 editor.remove_blocks(self.block_ids.clone(), None, cx);
21011 cx.focus_self(window);
21012 });
21013 }
21014 }
21015
21016 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21017 self.editor
21018 .update(cx, |editor, cx| {
21019 editor.remove_blocks(self.block_ids.clone(), None, cx);
21020 window.focus(&editor.focus_handle);
21021 })
21022 .log_err();
21023 }
21024
21025 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21026 let settings = ThemeSettings::get_global(cx);
21027 let text_style = TextStyle {
21028 color: if self.prompt.read(cx).read_only(cx) {
21029 cx.theme().colors().text_disabled
21030 } else {
21031 cx.theme().colors().text
21032 },
21033 font_family: settings.buffer_font.family.clone(),
21034 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21035 font_size: settings.buffer_font_size(cx).into(),
21036 font_weight: settings.buffer_font.weight,
21037 line_height: relative(settings.buffer_line_height.value()),
21038 ..Default::default()
21039 };
21040 EditorElement::new(
21041 &self.prompt,
21042 EditorStyle {
21043 background: cx.theme().colors().editor_background,
21044 local_player: cx.theme().players().local(),
21045 text: text_style,
21046 ..Default::default()
21047 },
21048 )
21049 }
21050}
21051
21052impl Render for BreakpointPromptEditor {
21053 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21054 let gutter_dimensions = *self.gutter_dimensions.lock();
21055 h_flex()
21056 .key_context("Editor")
21057 .bg(cx.theme().colors().editor_background)
21058 .border_y_1()
21059 .border_color(cx.theme().status().info_border)
21060 .size_full()
21061 .py(window.line_height() / 2.5)
21062 .on_action(cx.listener(Self::confirm))
21063 .on_action(cx.listener(Self::cancel))
21064 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21065 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21066 }
21067}
21068
21069impl Focusable for BreakpointPromptEditor {
21070 fn focus_handle(&self, cx: &App) -> FocusHandle {
21071 self.prompt.focus_handle(cx)
21072 }
21073}
21074
21075fn all_edits_insertions_or_deletions(
21076 edits: &Vec<(Range<Anchor>, String)>,
21077 snapshot: &MultiBufferSnapshot,
21078) -> bool {
21079 let mut all_insertions = true;
21080 let mut all_deletions = true;
21081
21082 for (range, new_text) in edits.iter() {
21083 let range_is_empty = range.to_offset(&snapshot).is_empty();
21084 let text_is_empty = new_text.is_empty();
21085
21086 if range_is_empty != text_is_empty {
21087 if range_is_empty {
21088 all_deletions = false;
21089 } else {
21090 all_insertions = false;
21091 }
21092 } else {
21093 return false;
21094 }
21095
21096 if !all_insertions && !all_deletions {
21097 return false;
21098 }
21099 }
21100 all_insertions || all_deletions
21101}
21102
21103struct MissingEditPredictionKeybindingTooltip;
21104
21105impl Render for MissingEditPredictionKeybindingTooltip {
21106 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21107 ui::tooltip_container(window, cx, |container, _, cx| {
21108 container
21109 .flex_shrink_0()
21110 .max_w_80()
21111 .min_h(rems_from_px(124.))
21112 .justify_between()
21113 .child(
21114 v_flex()
21115 .flex_1()
21116 .text_ui_sm(cx)
21117 .child(Label::new("Conflict with Accept Keybinding"))
21118 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21119 )
21120 .child(
21121 h_flex()
21122 .pb_1()
21123 .gap_1()
21124 .items_end()
21125 .w_full()
21126 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21127 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21128 }))
21129 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21130 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21131 })),
21132 )
21133 })
21134 }
21135}
21136
21137#[derive(Debug, Clone, Copy, PartialEq)]
21138pub struct LineHighlight {
21139 pub background: Background,
21140 pub border: Option<gpui::Hsla>,
21141 pub include_gutter: bool,
21142 pub type_id: Option<TypeId>,
21143}
21144
21145fn render_diff_hunk_controls(
21146 row: u32,
21147 status: &DiffHunkStatus,
21148 hunk_range: Range<Anchor>,
21149 is_created_file: bool,
21150 line_height: Pixels,
21151 editor: &Entity<Editor>,
21152 _window: &mut Window,
21153 cx: &mut App,
21154) -> AnyElement {
21155 h_flex()
21156 .h(line_height)
21157 .mr_1()
21158 .gap_1()
21159 .px_0p5()
21160 .pb_1()
21161 .border_x_1()
21162 .border_b_1()
21163 .border_color(cx.theme().colors().border_variant)
21164 .rounded_b_lg()
21165 .bg(cx.theme().colors().editor_background)
21166 .gap_1()
21167 .occlude()
21168 .shadow_md()
21169 .child(if status.has_secondary_hunk() {
21170 Button::new(("stage", row as u64), "Stage")
21171 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21172 .tooltip({
21173 let focus_handle = editor.focus_handle(cx);
21174 move |window, cx| {
21175 Tooltip::for_action_in(
21176 "Stage Hunk",
21177 &::git::ToggleStaged,
21178 &focus_handle,
21179 window,
21180 cx,
21181 )
21182 }
21183 })
21184 .on_click({
21185 let editor = editor.clone();
21186 move |_event, _window, cx| {
21187 editor.update(cx, |editor, cx| {
21188 editor.stage_or_unstage_diff_hunks(
21189 true,
21190 vec![hunk_range.start..hunk_range.start],
21191 cx,
21192 );
21193 });
21194 }
21195 })
21196 } else {
21197 Button::new(("unstage", row as u64), "Unstage")
21198 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21199 .tooltip({
21200 let focus_handle = editor.focus_handle(cx);
21201 move |window, cx| {
21202 Tooltip::for_action_in(
21203 "Unstage Hunk",
21204 &::git::ToggleStaged,
21205 &focus_handle,
21206 window,
21207 cx,
21208 )
21209 }
21210 })
21211 .on_click({
21212 let editor = editor.clone();
21213 move |_event, _window, cx| {
21214 editor.update(cx, |editor, cx| {
21215 editor.stage_or_unstage_diff_hunks(
21216 false,
21217 vec![hunk_range.start..hunk_range.start],
21218 cx,
21219 );
21220 });
21221 }
21222 })
21223 })
21224 .child(
21225 Button::new(("restore", row as u64), "Restore")
21226 .tooltip({
21227 let focus_handle = editor.focus_handle(cx);
21228 move |window, cx| {
21229 Tooltip::for_action_in(
21230 "Restore Hunk",
21231 &::git::Restore,
21232 &focus_handle,
21233 window,
21234 cx,
21235 )
21236 }
21237 })
21238 .on_click({
21239 let editor = editor.clone();
21240 move |_event, window, cx| {
21241 editor.update(cx, |editor, cx| {
21242 let snapshot = editor.snapshot(window, cx);
21243 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21244 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21245 });
21246 }
21247 })
21248 .disabled(is_created_file),
21249 )
21250 .when(
21251 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21252 |el| {
21253 el.child(
21254 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21255 .shape(IconButtonShape::Square)
21256 .icon_size(IconSize::Small)
21257 // .disabled(!has_multiple_hunks)
21258 .tooltip({
21259 let focus_handle = editor.focus_handle(cx);
21260 move |window, cx| {
21261 Tooltip::for_action_in(
21262 "Next Hunk",
21263 &GoToHunk,
21264 &focus_handle,
21265 window,
21266 cx,
21267 )
21268 }
21269 })
21270 .on_click({
21271 let editor = editor.clone();
21272 move |_event, window, cx| {
21273 editor.update(cx, |editor, cx| {
21274 let snapshot = editor.snapshot(window, cx);
21275 let position =
21276 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21277 editor.go_to_hunk_before_or_after_position(
21278 &snapshot,
21279 position,
21280 Direction::Next,
21281 window,
21282 cx,
21283 );
21284 editor.expand_selected_diff_hunks(cx);
21285 });
21286 }
21287 }),
21288 )
21289 .child(
21290 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21291 .shape(IconButtonShape::Square)
21292 .icon_size(IconSize::Small)
21293 // .disabled(!has_multiple_hunks)
21294 .tooltip({
21295 let focus_handle = editor.focus_handle(cx);
21296 move |window, cx| {
21297 Tooltip::for_action_in(
21298 "Previous Hunk",
21299 &GoToPreviousHunk,
21300 &focus_handle,
21301 window,
21302 cx,
21303 )
21304 }
21305 })
21306 .on_click({
21307 let editor = editor.clone();
21308 move |_event, window, cx| {
21309 editor.update(cx, |editor, cx| {
21310 let snapshot = editor.snapshot(window, cx);
21311 let point =
21312 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21313 editor.go_to_hunk_before_or_after_position(
21314 &snapshot,
21315 point,
21316 Direction::Prev,
21317 window,
21318 cx,
21319 );
21320 editor.expand_selected_diff_hunks(cx);
21321 });
21322 }
21323 }),
21324 )
21325 },
21326 )
21327 .into_any_element()
21328}