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 ActiveDebugLine {}
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_expand_excerpt_buttons: bool,
875 show_line_numbers: Option<bool>,
876 use_relative_line_numbers: Option<bool>,
877 show_git_diff_gutter: Option<bool>,
878 show_code_actions: Option<bool>,
879 show_runnables: Option<bool>,
880 show_breakpoints: Option<bool>,
881 show_wrap_guides: Option<bool>,
882 show_indent_guides: Option<bool>,
883 placeholder_text: Option<Arc<str>>,
884 highlight_order: usize,
885 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
886 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
887 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
888 scrollbar_marker_state: ScrollbarMarkerState,
889 active_indent_guides_state: ActiveIndentGuidesState,
890 nav_history: Option<ItemNavHistory>,
891 context_menu: RefCell<Option<CodeContextMenu>>,
892 context_menu_options: Option<ContextMenuOptions>,
893 mouse_context_menu: Option<MouseContextMenu>,
894 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
895 inline_blame_popover: Option<InlineBlamePopover>,
896 signature_help_state: SignatureHelpState,
897 auto_signature_help: Option<bool>,
898 find_all_references_task_sources: Vec<Anchor>,
899 next_completion_id: CompletionId,
900 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
901 code_actions_task: Option<Task<Result<()>>>,
902 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
903 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
904 document_highlights_task: Option<Task<()>>,
905 linked_editing_range_task: Option<Task<Option<()>>>,
906 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
907 pending_rename: Option<RenameState>,
908 searchable: bool,
909 cursor_shape: CursorShape,
910 current_line_highlight: Option<CurrentLineHighlight>,
911 collapse_matches: bool,
912 autoindent_mode: Option<AutoindentMode>,
913 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
914 input_enabled: bool,
915 use_modal_editing: bool,
916 read_only: bool,
917 leader_peer_id: Option<PeerId>,
918 remote_id: Option<ViewId>,
919 pub hover_state: HoverState,
920 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
921 gutter_hovered: bool,
922 hovered_link_state: Option<HoveredLinkState>,
923 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
924 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
925 active_inline_completion: Option<InlineCompletionState>,
926 /// Used to prevent flickering as the user types while the menu is open
927 stale_inline_completion_in_menu: Option<InlineCompletionState>,
928 edit_prediction_settings: EditPredictionSettings,
929 inline_completions_hidden_for_vim_mode: bool,
930 show_inline_completions_override: Option<bool>,
931 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
932 edit_prediction_preview: EditPredictionPreview,
933 edit_prediction_indent_conflict: bool,
934 edit_prediction_requires_modifier_in_indent_conflict: bool,
935 inlay_hint_cache: InlayHintCache,
936 next_inlay_id: usize,
937 _subscriptions: Vec<Subscription>,
938 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
939 gutter_dimensions: GutterDimensions,
940 style: Option<EditorStyle>,
941 text_style_refinement: Option<TextStyleRefinement>,
942 next_editor_action_id: EditorActionId,
943 editor_actions:
944 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
945 use_autoclose: bool,
946 use_auto_surround: bool,
947 auto_replace_emoji_shortcode: bool,
948 jsx_tag_auto_close_enabled_in_any_buffer: bool,
949 show_git_blame_gutter: bool,
950 show_git_blame_inline: bool,
951 show_git_blame_inline_delay_task: Option<Task<()>>,
952 git_blame_inline_enabled: bool,
953 render_diff_hunk_controls: RenderDiffHunkControlsFn,
954 serialize_dirty_buffers: bool,
955 show_selection_menu: Option<bool>,
956 blame: Option<Entity<GitBlame>>,
957 blame_subscription: Option<Subscription>,
958 custom_context_menu: Option<
959 Box<
960 dyn 'static
961 + Fn(
962 &mut Self,
963 DisplayPoint,
964 &mut Window,
965 &mut Context<Self>,
966 ) -> Option<Entity<ui::ContextMenu>>,
967 >,
968 >,
969 last_bounds: Option<Bounds<Pixels>>,
970 last_position_map: Option<Rc<PositionMap>>,
971 expect_bounds_change: Option<Bounds<Pixels>>,
972 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
973 tasks_update_task: Option<Task<()>>,
974 breakpoint_store: Option<Entity<BreakpointStore>>,
975 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
976 in_project_search: bool,
977 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
978 breadcrumb_header: Option<String>,
979 focused_block: Option<FocusedBlock>,
980 next_scroll_position: NextScrollCursorCenterTopBottom,
981 addons: HashMap<TypeId, Box<dyn Addon>>,
982 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
983 load_diff_task: Option<Shared<Task<()>>>,
984 selection_mark_mode: bool,
985 toggle_fold_multiple_buffers: Task<()>,
986 _scroll_cursor_center_top_bottom_task: Task<()>,
987 serialize_selections: Task<()>,
988 serialize_folds: Task<()>,
989 mouse_cursor_hidden: bool,
990 hide_mouse_mode: HideMouseMode,
991 pub change_list: ChangeList,
992 inline_value_cache: InlineValueCache,
993}
994
995#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
996enum NextScrollCursorCenterTopBottom {
997 #[default]
998 Center,
999 Top,
1000 Bottom,
1001}
1002
1003impl NextScrollCursorCenterTopBottom {
1004 fn next(&self) -> Self {
1005 match self {
1006 Self::Center => Self::Top,
1007 Self::Top => Self::Bottom,
1008 Self::Bottom => Self::Center,
1009 }
1010 }
1011}
1012
1013#[derive(Clone)]
1014pub struct EditorSnapshot {
1015 pub mode: EditorMode,
1016 show_gutter: bool,
1017 show_line_numbers: Option<bool>,
1018 show_git_diff_gutter: Option<bool>,
1019 show_code_actions: Option<bool>,
1020 show_runnables: Option<bool>,
1021 show_breakpoints: Option<bool>,
1022 git_blame_gutter_max_author_length: Option<usize>,
1023 pub display_snapshot: DisplaySnapshot,
1024 pub placeholder_text: Option<Arc<str>>,
1025 is_focused: bool,
1026 scroll_anchor: ScrollAnchor,
1027 ongoing_scroll: OngoingScroll,
1028 current_line_highlight: CurrentLineHighlight,
1029 gutter_hovered: bool,
1030}
1031
1032#[derive(Default, Debug, Clone, Copy)]
1033pub struct GutterDimensions {
1034 pub left_padding: Pixels,
1035 pub right_padding: Pixels,
1036 pub width: Pixels,
1037 pub margin: Pixels,
1038 pub git_blame_entries_width: Option<Pixels>,
1039}
1040
1041impl GutterDimensions {
1042 /// The full width of the space taken up by the gutter.
1043 pub fn full_width(&self) -> Pixels {
1044 self.margin + self.width
1045 }
1046
1047 /// The width of the space reserved for the fold indicators,
1048 /// use alongside 'justify_end' and `gutter_width` to
1049 /// right align content with the line numbers
1050 pub fn fold_area_width(&self) -> Pixels {
1051 self.margin + self.right_padding
1052 }
1053}
1054
1055#[derive(Debug)]
1056pub struct RemoteSelection {
1057 pub replica_id: ReplicaId,
1058 pub selection: Selection<Anchor>,
1059 pub cursor_shape: CursorShape,
1060 pub peer_id: PeerId,
1061 pub line_mode: bool,
1062 pub participant_index: Option<ParticipantIndex>,
1063 pub user_name: Option<SharedString>,
1064}
1065
1066#[derive(Clone, Debug)]
1067struct SelectionHistoryEntry {
1068 selections: Arc<[Selection<Anchor>]>,
1069 select_next_state: Option<SelectNextState>,
1070 select_prev_state: Option<SelectNextState>,
1071 add_selections_state: Option<AddSelectionsState>,
1072}
1073
1074enum SelectionHistoryMode {
1075 Normal,
1076 Undoing,
1077 Redoing,
1078}
1079
1080#[derive(Clone, PartialEq, Eq, Hash)]
1081struct HoveredCursor {
1082 replica_id: u16,
1083 selection_id: usize,
1084}
1085
1086impl Default for SelectionHistoryMode {
1087 fn default() -> Self {
1088 Self::Normal
1089 }
1090}
1091
1092#[derive(Default)]
1093struct SelectionHistory {
1094 #[allow(clippy::type_complexity)]
1095 selections_by_transaction:
1096 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1097 mode: SelectionHistoryMode,
1098 undo_stack: VecDeque<SelectionHistoryEntry>,
1099 redo_stack: VecDeque<SelectionHistoryEntry>,
1100}
1101
1102impl SelectionHistory {
1103 fn insert_transaction(
1104 &mut self,
1105 transaction_id: TransactionId,
1106 selections: Arc<[Selection<Anchor>]>,
1107 ) {
1108 self.selections_by_transaction
1109 .insert(transaction_id, (selections, None));
1110 }
1111
1112 #[allow(clippy::type_complexity)]
1113 fn transaction(
1114 &self,
1115 transaction_id: TransactionId,
1116 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1117 self.selections_by_transaction.get(&transaction_id)
1118 }
1119
1120 #[allow(clippy::type_complexity)]
1121 fn transaction_mut(
1122 &mut self,
1123 transaction_id: TransactionId,
1124 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1125 self.selections_by_transaction.get_mut(&transaction_id)
1126 }
1127
1128 fn push(&mut self, entry: SelectionHistoryEntry) {
1129 if !entry.selections.is_empty() {
1130 match self.mode {
1131 SelectionHistoryMode::Normal => {
1132 self.push_undo(entry);
1133 self.redo_stack.clear();
1134 }
1135 SelectionHistoryMode::Undoing => self.push_redo(entry),
1136 SelectionHistoryMode::Redoing => self.push_undo(entry),
1137 }
1138 }
1139 }
1140
1141 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1142 if self
1143 .undo_stack
1144 .back()
1145 .map_or(true, |e| e.selections != entry.selections)
1146 {
1147 self.undo_stack.push_back(entry);
1148 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1149 self.undo_stack.pop_front();
1150 }
1151 }
1152 }
1153
1154 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1155 if self
1156 .redo_stack
1157 .back()
1158 .map_or(true, |e| e.selections != entry.selections)
1159 {
1160 self.redo_stack.push_back(entry);
1161 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1162 self.redo_stack.pop_front();
1163 }
1164 }
1165 }
1166}
1167
1168#[derive(Clone, Copy)]
1169pub struct RowHighlightOptions {
1170 pub autoscroll: bool,
1171 pub include_gutter: bool,
1172}
1173
1174impl Default for RowHighlightOptions {
1175 fn default() -> Self {
1176 Self {
1177 autoscroll: Default::default(),
1178 include_gutter: true,
1179 }
1180 }
1181}
1182
1183struct RowHighlight {
1184 index: usize,
1185 range: Range<Anchor>,
1186 color: Hsla,
1187 options: RowHighlightOptions,
1188 type_id: TypeId,
1189}
1190
1191#[derive(Clone, Debug)]
1192struct AddSelectionsState {
1193 above: bool,
1194 stack: Vec<usize>,
1195}
1196
1197#[derive(Clone)]
1198struct SelectNextState {
1199 query: AhoCorasick,
1200 wordwise: bool,
1201 done: bool,
1202}
1203
1204impl std::fmt::Debug for SelectNextState {
1205 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1206 f.debug_struct(std::any::type_name::<Self>())
1207 .field("wordwise", &self.wordwise)
1208 .field("done", &self.done)
1209 .finish()
1210 }
1211}
1212
1213#[derive(Debug)]
1214struct AutocloseRegion {
1215 selection_id: usize,
1216 range: Range<Anchor>,
1217 pair: BracketPair,
1218}
1219
1220#[derive(Debug)]
1221struct SnippetState {
1222 ranges: Vec<Vec<Range<Anchor>>>,
1223 active_index: usize,
1224 choices: Vec<Option<Vec<String>>>,
1225}
1226
1227#[doc(hidden)]
1228pub struct RenameState {
1229 pub range: Range<Anchor>,
1230 pub old_name: Arc<str>,
1231 pub editor: Entity<Editor>,
1232 block_id: CustomBlockId,
1233}
1234
1235struct InvalidationStack<T>(Vec<T>);
1236
1237struct RegisteredInlineCompletionProvider {
1238 provider: Arc<dyn InlineCompletionProviderHandle>,
1239 _subscription: Subscription,
1240}
1241
1242#[derive(Debug, PartialEq, Eq)]
1243pub struct ActiveDiagnosticGroup {
1244 pub active_range: Range<Anchor>,
1245 pub active_message: String,
1246 pub group_id: usize,
1247 pub blocks: HashSet<CustomBlockId>,
1248}
1249
1250#[derive(Debug, PartialEq, Eq)]
1251#[allow(clippy::large_enum_variant)]
1252pub(crate) enum ActiveDiagnostic {
1253 None,
1254 All,
1255 Group(ActiveDiagnosticGroup),
1256}
1257
1258#[derive(Serialize, Deserialize, Clone, Debug)]
1259pub struct ClipboardSelection {
1260 /// The number of bytes in this selection.
1261 pub len: usize,
1262 /// Whether this was a full-line selection.
1263 pub is_entire_line: bool,
1264 /// The indentation of the first line when this content was originally copied.
1265 pub first_line_indent: u32,
1266}
1267
1268// selections, scroll behavior, was newest selection reversed
1269type SelectSyntaxNodeHistoryState = (
1270 Box<[Selection<usize>]>,
1271 SelectSyntaxNodeScrollBehavior,
1272 bool,
1273);
1274
1275#[derive(Default)]
1276struct SelectSyntaxNodeHistory {
1277 stack: Vec<SelectSyntaxNodeHistoryState>,
1278 // disable temporarily to allow changing selections without losing the stack
1279 pub disable_clearing: bool,
1280}
1281
1282impl SelectSyntaxNodeHistory {
1283 pub fn try_clear(&mut self) {
1284 if !self.disable_clearing {
1285 self.stack.clear();
1286 }
1287 }
1288
1289 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1290 self.stack.push(selection);
1291 }
1292
1293 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1294 self.stack.pop()
1295 }
1296}
1297
1298enum SelectSyntaxNodeScrollBehavior {
1299 CursorTop,
1300 FitSelection,
1301 CursorBottom,
1302}
1303
1304#[derive(Debug)]
1305pub(crate) struct NavigationData {
1306 cursor_anchor: Anchor,
1307 cursor_position: Point,
1308 scroll_anchor: ScrollAnchor,
1309 scroll_top_row: u32,
1310}
1311
1312#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1313pub enum GotoDefinitionKind {
1314 Symbol,
1315 Declaration,
1316 Type,
1317 Implementation,
1318}
1319
1320#[derive(Debug, Clone)]
1321enum InlayHintRefreshReason {
1322 ModifiersChanged(bool),
1323 Toggle(bool),
1324 SettingsChange(InlayHintSettings),
1325 NewLinesShown,
1326 BufferEdited(HashSet<Arc<Language>>),
1327 RefreshRequested,
1328 ExcerptsRemoved(Vec<ExcerptId>),
1329}
1330
1331impl InlayHintRefreshReason {
1332 fn description(&self) -> &'static str {
1333 match self {
1334 Self::ModifiersChanged(_) => "modifiers changed",
1335 Self::Toggle(_) => "toggle",
1336 Self::SettingsChange(_) => "settings change",
1337 Self::NewLinesShown => "new lines shown",
1338 Self::BufferEdited(_) => "buffer edited",
1339 Self::RefreshRequested => "refresh requested",
1340 Self::ExcerptsRemoved(_) => "excerpts removed",
1341 }
1342 }
1343}
1344
1345pub enum FormatTarget {
1346 Buffers,
1347 Ranges(Vec<Range<MultiBufferPoint>>),
1348}
1349
1350pub(crate) struct FocusedBlock {
1351 id: BlockId,
1352 focus_handle: WeakFocusHandle,
1353}
1354
1355#[derive(Clone)]
1356enum JumpData {
1357 MultiBufferRow {
1358 row: MultiBufferRow,
1359 line_offset_from_top: u32,
1360 },
1361 MultiBufferPoint {
1362 excerpt_id: ExcerptId,
1363 position: Point,
1364 anchor: text::Anchor,
1365 line_offset_from_top: u32,
1366 },
1367}
1368
1369pub enum MultibufferSelectionMode {
1370 First,
1371 All,
1372}
1373
1374#[derive(Clone, Copy, Debug, Default)]
1375pub struct RewrapOptions {
1376 pub override_language_settings: bool,
1377 pub preserve_existing_whitespace: bool,
1378}
1379
1380impl Editor {
1381 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1382 let buffer = cx.new(|cx| Buffer::local("", cx));
1383 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1384 Self::new(
1385 EditorMode::SingleLine { auto_width: false },
1386 buffer,
1387 None,
1388 window,
1389 cx,
1390 )
1391 }
1392
1393 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1394 let buffer = cx.new(|cx| Buffer::local("", cx));
1395 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1396 Self::new(EditorMode::full(), buffer, None, window, cx)
1397 }
1398
1399 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1400 let buffer = cx.new(|cx| Buffer::local("", cx));
1401 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1402 Self::new(
1403 EditorMode::SingleLine { auto_width: true },
1404 buffer,
1405 None,
1406 window,
1407 cx,
1408 )
1409 }
1410
1411 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1412 let buffer = cx.new(|cx| Buffer::local("", cx));
1413 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1414 Self::new(
1415 EditorMode::AutoHeight { max_lines },
1416 buffer,
1417 None,
1418 window,
1419 cx,
1420 )
1421 }
1422
1423 pub fn for_buffer(
1424 buffer: Entity<Buffer>,
1425 project: Option<Entity<Project>>,
1426 window: &mut Window,
1427 cx: &mut Context<Self>,
1428 ) -> Self {
1429 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1430 Self::new(EditorMode::full(), buffer, project, window, cx)
1431 }
1432
1433 pub fn for_multibuffer(
1434 buffer: Entity<MultiBuffer>,
1435 project: Option<Entity<Project>>,
1436 window: &mut Window,
1437 cx: &mut Context<Self>,
1438 ) -> Self {
1439 Self::new(EditorMode::full(), buffer, project, window, cx)
1440 }
1441
1442 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1443 let mut clone = Self::new(
1444 self.mode,
1445 self.buffer.clone(),
1446 self.project.clone(),
1447 window,
1448 cx,
1449 );
1450 self.display_map.update(cx, |display_map, cx| {
1451 let snapshot = display_map.snapshot(cx);
1452 clone.display_map.update(cx, |display_map, cx| {
1453 display_map.set_state(&snapshot, cx);
1454 });
1455 });
1456 clone.folds_did_change(cx);
1457 clone.selections.clone_state(&self.selections);
1458 clone.scroll_manager.clone_state(&self.scroll_manager);
1459 clone.searchable = self.searchable;
1460 clone.read_only = self.read_only;
1461 clone
1462 }
1463
1464 pub fn new(
1465 mode: EditorMode,
1466 buffer: Entity<MultiBuffer>,
1467 project: Option<Entity<Project>>,
1468 window: &mut Window,
1469 cx: &mut Context<Self>,
1470 ) -> Self {
1471 let style = window.text_style();
1472 let font_size = style.font_size.to_pixels(window.rem_size());
1473 let editor = cx.entity().downgrade();
1474 let fold_placeholder = FoldPlaceholder {
1475 constrain_width: true,
1476 render: Arc::new(move |fold_id, fold_range, cx| {
1477 let editor = editor.clone();
1478 div()
1479 .id(fold_id)
1480 .bg(cx.theme().colors().ghost_element_background)
1481 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1482 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1483 .rounded_xs()
1484 .size_full()
1485 .cursor_pointer()
1486 .child("⋯")
1487 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1488 .on_click(move |_, _window, cx| {
1489 editor
1490 .update(cx, |editor, cx| {
1491 editor.unfold_ranges(
1492 &[fold_range.start..fold_range.end],
1493 true,
1494 false,
1495 cx,
1496 );
1497 cx.stop_propagation();
1498 })
1499 .ok();
1500 })
1501 .into_any()
1502 }),
1503 merge_adjacent: true,
1504 ..Default::default()
1505 };
1506 let display_map = cx.new(|cx| {
1507 DisplayMap::new(
1508 buffer.clone(),
1509 style.font(),
1510 font_size,
1511 None,
1512 FILE_HEADER_HEIGHT,
1513 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1514 fold_placeholder,
1515 cx,
1516 )
1517 });
1518
1519 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1520
1521 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1522
1523 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1524 .then(|| language_settings::SoftWrap::None);
1525
1526 let mut project_subscriptions = Vec::new();
1527 if mode.is_full() {
1528 if let Some(project) = project.as_ref() {
1529 project_subscriptions.push(cx.subscribe_in(
1530 project,
1531 window,
1532 |editor, _, event, window, cx| match event {
1533 project::Event::RefreshCodeLens => {
1534 // we always query lens with actions, without storing them, always refreshing them
1535 }
1536 project::Event::RefreshInlayHints => {
1537 editor
1538 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1539 }
1540 project::Event::SnippetEdit(id, snippet_edits) => {
1541 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1542 let focus_handle = editor.focus_handle(cx);
1543 if focus_handle.is_focused(window) {
1544 let snapshot = buffer.read(cx).snapshot();
1545 for (range, snippet) in snippet_edits {
1546 let editor_range =
1547 language::range_from_lsp(*range).to_offset(&snapshot);
1548 editor
1549 .insert_snippet(
1550 &[editor_range],
1551 snippet.clone(),
1552 window,
1553 cx,
1554 )
1555 .ok();
1556 }
1557 }
1558 }
1559 }
1560 _ => {}
1561 },
1562 ));
1563 if let Some(task_inventory) = project
1564 .read(cx)
1565 .task_store()
1566 .read(cx)
1567 .task_inventory()
1568 .cloned()
1569 {
1570 project_subscriptions.push(cx.observe_in(
1571 &task_inventory,
1572 window,
1573 |editor, _, window, cx| {
1574 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1575 },
1576 ));
1577 };
1578
1579 project_subscriptions.push(cx.subscribe_in(
1580 &project.read(cx).breakpoint_store(),
1581 window,
1582 |editor, _, event, window, cx| match event {
1583 BreakpointStoreEvent::ClearDebugLines => {
1584 editor.clear_row_highlights::<ActiveDebugLine>();
1585 editor.refresh_inline_values(cx);
1586 }
1587 BreakpointStoreEvent::SetDebugLine => {
1588 if editor.go_to_active_debug_line(window, cx) {
1589 cx.stop_propagation();
1590 }
1591
1592 editor.refresh_inline_values(cx);
1593 }
1594 _ => {}
1595 },
1596 ));
1597 }
1598 }
1599
1600 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1601
1602 let inlay_hint_settings =
1603 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1604 let focus_handle = cx.focus_handle();
1605 cx.on_focus(&focus_handle, window, Self::handle_focus)
1606 .detach();
1607 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1608 .detach();
1609 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1610 .detach();
1611 cx.on_blur(&focus_handle, window, Self::handle_blur)
1612 .detach();
1613
1614 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1615 Some(false)
1616 } else {
1617 None
1618 };
1619
1620 let breakpoint_store = match (mode, project.as_ref()) {
1621 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1622 _ => None,
1623 };
1624
1625 let mut code_action_providers = Vec::new();
1626 let mut load_uncommitted_diff = None;
1627 if let Some(project) = project.clone() {
1628 load_uncommitted_diff = Some(
1629 get_uncommitted_diff_for_buffer(
1630 &project,
1631 buffer.read(cx).all_buffers(),
1632 buffer.clone(),
1633 cx,
1634 )
1635 .shared(),
1636 );
1637 code_action_providers.push(Rc::new(project) as Rc<_>);
1638 }
1639
1640 let mut this = Self {
1641 focus_handle,
1642 show_cursor_when_unfocused: false,
1643 last_focused_descendant: None,
1644 buffer: buffer.clone(),
1645 display_map: display_map.clone(),
1646 selections,
1647 scroll_manager: ScrollManager::new(cx),
1648 columnar_selection_tail: None,
1649 add_selections_state: None,
1650 select_next_state: None,
1651 select_prev_state: None,
1652 selection_history: Default::default(),
1653 autoclose_regions: Default::default(),
1654 snippet_stack: Default::default(),
1655 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1656 ime_transaction: Default::default(),
1657 active_diagnostics: ActiveDiagnostic::None,
1658 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1659 inline_diagnostics_update: Task::ready(()),
1660 inline_diagnostics: Vec::new(),
1661 soft_wrap_mode_override,
1662 hard_wrap: None,
1663 completion_provider: project.clone().map(|project| Box::new(project) as _),
1664 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1665 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1666 project,
1667 blink_manager: blink_manager.clone(),
1668 show_local_selections: true,
1669 show_scrollbars: true,
1670 mode,
1671 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1672 show_gutter: mode.is_full(),
1673 show_line_numbers: None,
1674 use_relative_line_numbers: None,
1675 disable_expand_excerpt_buttons: false,
1676 show_git_diff_gutter: None,
1677 show_code_actions: None,
1678 show_runnables: None,
1679 show_breakpoints: None,
1680 show_wrap_guides: None,
1681 show_indent_guides,
1682 placeholder_text: None,
1683 highlight_order: 0,
1684 highlighted_rows: HashMap::default(),
1685 background_highlights: Default::default(),
1686 gutter_highlights: TreeMap::default(),
1687 scrollbar_marker_state: ScrollbarMarkerState::default(),
1688 active_indent_guides_state: ActiveIndentGuidesState::default(),
1689 nav_history: None,
1690 context_menu: RefCell::new(None),
1691 context_menu_options: None,
1692 mouse_context_menu: None,
1693 completion_tasks: Default::default(),
1694 inline_blame_popover: Default::default(),
1695 signature_help_state: SignatureHelpState::default(),
1696 auto_signature_help: None,
1697 find_all_references_task_sources: Vec::new(),
1698 next_completion_id: 0,
1699 next_inlay_id: 0,
1700 code_action_providers,
1701 available_code_actions: Default::default(),
1702 code_actions_task: Default::default(),
1703 quick_selection_highlight_task: Default::default(),
1704 debounced_selection_highlight_task: Default::default(),
1705 document_highlights_task: Default::default(),
1706 linked_editing_range_task: Default::default(),
1707 pending_rename: Default::default(),
1708 searchable: true,
1709 cursor_shape: EditorSettings::get_global(cx)
1710 .cursor_shape
1711 .unwrap_or_default(),
1712 current_line_highlight: None,
1713 autoindent_mode: Some(AutoindentMode::EachLine),
1714 collapse_matches: false,
1715 workspace: None,
1716 input_enabled: true,
1717 use_modal_editing: mode.is_full(),
1718 read_only: false,
1719 use_autoclose: true,
1720 use_auto_surround: true,
1721 auto_replace_emoji_shortcode: false,
1722 jsx_tag_auto_close_enabled_in_any_buffer: false,
1723 leader_peer_id: None,
1724 remote_id: None,
1725 hover_state: Default::default(),
1726 pending_mouse_down: None,
1727 hovered_link_state: Default::default(),
1728 edit_prediction_provider: None,
1729 active_inline_completion: None,
1730 stale_inline_completion_in_menu: None,
1731 edit_prediction_preview: EditPredictionPreview::Inactive {
1732 released_too_fast: false,
1733 },
1734 inline_diagnostics_enabled: mode.is_full(),
1735 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1736 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1737
1738 gutter_hovered: false,
1739 pixel_position_of_newest_cursor: None,
1740 last_bounds: None,
1741 last_position_map: None,
1742 expect_bounds_change: None,
1743 gutter_dimensions: GutterDimensions::default(),
1744 style: None,
1745 show_cursor_names: false,
1746 hovered_cursors: Default::default(),
1747 next_editor_action_id: EditorActionId::default(),
1748 editor_actions: Rc::default(),
1749 inline_completions_hidden_for_vim_mode: false,
1750 show_inline_completions_override: None,
1751 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1752 edit_prediction_settings: EditPredictionSettings::Disabled,
1753 edit_prediction_indent_conflict: false,
1754 edit_prediction_requires_modifier_in_indent_conflict: true,
1755 custom_context_menu: None,
1756 show_git_blame_gutter: false,
1757 show_git_blame_inline: false,
1758 show_selection_menu: None,
1759 show_git_blame_inline_delay_task: None,
1760 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1761 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1762 serialize_dirty_buffers: ProjectSettings::get_global(cx)
1763 .session
1764 .restore_unsaved_buffers,
1765 blame: None,
1766 blame_subscription: None,
1767 tasks: Default::default(),
1768
1769 breakpoint_store,
1770 gutter_breakpoint_indicator: (None, None),
1771 _subscriptions: vec![
1772 cx.observe(&buffer, Self::on_buffer_changed),
1773 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1774 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1775 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1776 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1777 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1778 cx.observe_window_activation(window, |editor, window, cx| {
1779 let active = window.is_window_active();
1780 editor.blink_manager.update(cx, |blink_manager, cx| {
1781 if active {
1782 blink_manager.enable(cx);
1783 } else {
1784 blink_manager.disable(cx);
1785 }
1786 });
1787 }),
1788 ],
1789 tasks_update_task: None,
1790 linked_edit_ranges: Default::default(),
1791 in_project_search: false,
1792 previous_search_ranges: None,
1793 breadcrumb_header: None,
1794 focused_block: None,
1795 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1796 addons: HashMap::default(),
1797 registered_buffers: HashMap::default(),
1798 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1799 selection_mark_mode: false,
1800 toggle_fold_multiple_buffers: Task::ready(()),
1801 serialize_selections: Task::ready(()),
1802 serialize_folds: Task::ready(()),
1803 text_style_refinement: None,
1804 load_diff_task: load_uncommitted_diff,
1805 mouse_cursor_hidden: false,
1806 hide_mouse_mode: EditorSettings::get_global(cx)
1807 .hide_mouse
1808 .unwrap_or_default(),
1809 change_list: ChangeList::new(),
1810 };
1811 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1812 this._subscriptions
1813 .push(cx.observe(breakpoints, |_, _, cx| {
1814 cx.notify();
1815 }));
1816 }
1817 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1818 this._subscriptions.extend(project_subscriptions);
1819
1820 this._subscriptions.push(cx.subscribe_in(
1821 &cx.entity(),
1822 window,
1823 |editor, _, e: &EditorEvent, window, cx| match e {
1824 EditorEvent::ScrollPositionChanged { local, .. } => {
1825 if *local {
1826 let new_anchor = editor.scroll_manager.anchor();
1827 let snapshot = editor.snapshot(window, cx);
1828 editor.update_restoration_data(cx, move |data| {
1829 data.scroll_position = (
1830 new_anchor.top_row(&snapshot.buffer_snapshot),
1831 new_anchor.offset,
1832 );
1833 });
1834 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1835 editor.inline_blame_popover.take();
1836 }
1837 }
1838 EditorEvent::Edited { .. } => {
1839 if !vim_enabled(cx) {
1840 let (map, selections) = editor.selections.all_adjusted_display(cx);
1841 let pop_state = editor
1842 .change_list
1843 .last()
1844 .map(|previous| {
1845 previous.len() == selections.len()
1846 && previous.iter().enumerate().all(|(ix, p)| {
1847 p.to_display_point(&map).row()
1848 == selections[ix].head().row()
1849 })
1850 })
1851 .unwrap_or(false);
1852 let new_positions = selections
1853 .into_iter()
1854 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1855 .collect();
1856 editor
1857 .change_list
1858 .push_to_change_list(pop_state, new_positions);
1859 }
1860 }
1861 _ => (),
1862 },
1863 ));
1864
1865 if let Some(dap_store) = this
1866 .project
1867 .as_ref()
1868 .map(|project| project.read(cx).dap_store())
1869 {
1870 let weak_editor = cx.weak_entity();
1871
1872 this._subscriptions
1873 .push(
1874 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1875 let session_entity = cx.entity();
1876 weak_editor
1877 .update(cx, |editor, cx| {
1878 editor._subscriptions.push(
1879 cx.subscribe(&session_entity, Self::on_debug_session_event),
1880 );
1881 })
1882 .ok();
1883 }),
1884 );
1885
1886 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1887 this._subscriptions
1888 .push(cx.subscribe(&session, Self::on_debug_session_event));
1889 }
1890 }
1891
1892 this.end_selection(window, cx);
1893 this.scroll_manager.show_scrollbars(window, cx);
1894 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1895
1896 if mode.is_full() {
1897 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1898 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1899
1900 if this.git_blame_inline_enabled {
1901 this.git_blame_inline_enabled = true;
1902 this.start_git_blame_inline(false, window, cx);
1903 }
1904
1905 this.go_to_active_debug_line(window, cx);
1906
1907 if let Some(buffer) = buffer.read(cx).as_singleton() {
1908 if let Some(project) = this.project.as_ref() {
1909 let handle = project.update(cx, |project, cx| {
1910 project.register_buffer_with_language_servers(&buffer, cx)
1911 });
1912 this.registered_buffers
1913 .insert(buffer.read(cx).remote_id(), handle);
1914 }
1915 }
1916 }
1917
1918 this.report_editor_event("Editor Opened", None, cx);
1919 this
1920 }
1921
1922 pub fn deploy_mouse_context_menu(
1923 &mut self,
1924 position: gpui::Point<Pixels>,
1925 context_menu: Entity<ContextMenu>,
1926 window: &mut Window,
1927 cx: &mut Context<Self>,
1928 ) {
1929 self.mouse_context_menu = Some(MouseContextMenu::new(
1930 self,
1931 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
1932 context_menu,
1933 window,
1934 cx,
1935 ));
1936 }
1937
1938 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
1939 self.mouse_context_menu
1940 .as_ref()
1941 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
1942 }
1943
1944 fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
1945 self.key_context_internal(self.has_active_inline_completion(), window, cx)
1946 }
1947
1948 fn key_context_internal(
1949 &self,
1950 has_active_edit_prediction: bool,
1951 window: &Window,
1952 cx: &App,
1953 ) -> KeyContext {
1954 let mut key_context = KeyContext::new_with_defaults();
1955 key_context.add("Editor");
1956 let mode = match self.mode {
1957 EditorMode::SingleLine { .. } => "single_line",
1958 EditorMode::AutoHeight { .. } => "auto_height",
1959 EditorMode::Full { .. } => "full",
1960 };
1961
1962 if EditorSettings::jupyter_enabled(cx) {
1963 key_context.add("jupyter");
1964 }
1965
1966 key_context.set("mode", mode);
1967 if self.pending_rename.is_some() {
1968 key_context.add("renaming");
1969 }
1970
1971 match self.context_menu.borrow().as_ref() {
1972 Some(CodeContextMenu::Completions(_)) => {
1973 key_context.add("menu");
1974 key_context.add("showing_completions");
1975 }
1976 Some(CodeContextMenu::CodeActions(_)) => {
1977 key_context.add("menu");
1978 key_context.add("showing_code_actions")
1979 }
1980 None => {}
1981 }
1982
1983 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
1984 if !self.focus_handle(cx).contains_focused(window, cx)
1985 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
1986 {
1987 for addon in self.addons.values() {
1988 addon.extend_key_context(&mut key_context, cx)
1989 }
1990 }
1991
1992 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
1993 if let Some(extension) = singleton_buffer
1994 .read(cx)
1995 .file()
1996 .and_then(|file| file.path().extension()?.to_str())
1997 {
1998 key_context.set("extension", extension.to_string());
1999 }
2000 } else {
2001 key_context.add("multibuffer");
2002 }
2003
2004 if has_active_edit_prediction {
2005 if self.edit_prediction_in_conflict() {
2006 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2007 } else {
2008 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2009 key_context.add("copilot_suggestion");
2010 }
2011 }
2012
2013 if self.selection_mark_mode {
2014 key_context.add("selection_mode");
2015 }
2016
2017 key_context
2018 }
2019
2020 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2021 self.mouse_cursor_hidden = match origin {
2022 HideMouseCursorOrigin::TypingAction => {
2023 matches!(
2024 self.hide_mouse_mode,
2025 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2026 )
2027 }
2028 HideMouseCursorOrigin::MovementAction => {
2029 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2030 }
2031 };
2032 }
2033
2034 pub fn edit_prediction_in_conflict(&self) -> bool {
2035 if !self.show_edit_predictions_in_menu() {
2036 return false;
2037 }
2038
2039 let showing_completions = self
2040 .context_menu
2041 .borrow()
2042 .as_ref()
2043 .map_or(false, |context| {
2044 matches!(context, CodeContextMenu::Completions(_))
2045 });
2046
2047 showing_completions
2048 || self.edit_prediction_requires_modifier()
2049 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2050 // bindings to insert tab characters.
2051 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2052 }
2053
2054 pub fn accept_edit_prediction_keybind(
2055 &self,
2056 window: &Window,
2057 cx: &App,
2058 ) -> AcceptEditPredictionBinding {
2059 let key_context = self.key_context_internal(true, window, cx);
2060 let in_conflict = self.edit_prediction_in_conflict();
2061
2062 AcceptEditPredictionBinding(
2063 window
2064 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2065 .into_iter()
2066 .filter(|binding| {
2067 !in_conflict
2068 || binding
2069 .keystrokes()
2070 .first()
2071 .map_or(false, |keystroke| keystroke.modifiers.modified())
2072 })
2073 .rev()
2074 .min_by_key(|binding| {
2075 binding
2076 .keystrokes()
2077 .first()
2078 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2079 }),
2080 )
2081 }
2082
2083 pub fn new_file(
2084 workspace: &mut Workspace,
2085 _: &workspace::NewFile,
2086 window: &mut Window,
2087 cx: &mut Context<Workspace>,
2088 ) {
2089 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2090 "Failed to create buffer",
2091 window,
2092 cx,
2093 |e, _, _| match e.error_code() {
2094 ErrorCode::RemoteUpgradeRequired => Some(format!(
2095 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2096 e.error_tag("required").unwrap_or("the latest version")
2097 )),
2098 _ => None,
2099 },
2100 );
2101 }
2102
2103 pub fn new_in_workspace(
2104 workspace: &mut Workspace,
2105 window: &mut Window,
2106 cx: &mut Context<Workspace>,
2107 ) -> Task<Result<Entity<Editor>>> {
2108 let project = workspace.project().clone();
2109 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2110
2111 cx.spawn_in(window, async move |workspace, cx| {
2112 let buffer = create.await?;
2113 workspace.update_in(cx, |workspace, window, cx| {
2114 let editor =
2115 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2116 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2117 editor
2118 })
2119 })
2120 }
2121
2122 fn new_file_vertical(
2123 workspace: &mut Workspace,
2124 _: &workspace::NewFileSplitVertical,
2125 window: &mut Window,
2126 cx: &mut Context<Workspace>,
2127 ) {
2128 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2129 }
2130
2131 fn new_file_horizontal(
2132 workspace: &mut Workspace,
2133 _: &workspace::NewFileSplitHorizontal,
2134 window: &mut Window,
2135 cx: &mut Context<Workspace>,
2136 ) {
2137 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2138 }
2139
2140 fn new_file_in_direction(
2141 workspace: &mut Workspace,
2142 direction: SplitDirection,
2143 window: &mut Window,
2144 cx: &mut Context<Workspace>,
2145 ) {
2146 let project = workspace.project().clone();
2147 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2148
2149 cx.spawn_in(window, async move |workspace, cx| {
2150 let buffer = create.await?;
2151 workspace.update_in(cx, move |workspace, window, cx| {
2152 workspace.split_item(
2153 direction,
2154 Box::new(
2155 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2156 ),
2157 window,
2158 cx,
2159 )
2160 })?;
2161 anyhow::Ok(())
2162 })
2163 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2164 match e.error_code() {
2165 ErrorCode::RemoteUpgradeRequired => Some(format!(
2166 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2167 e.error_tag("required").unwrap_or("the latest version")
2168 )),
2169 _ => None,
2170 }
2171 });
2172 }
2173
2174 pub fn leader_peer_id(&self) -> Option<PeerId> {
2175 self.leader_peer_id
2176 }
2177
2178 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2179 &self.buffer
2180 }
2181
2182 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2183 self.workspace.as_ref()?.0.upgrade()
2184 }
2185
2186 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2187 self.buffer().read(cx).title(cx)
2188 }
2189
2190 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2191 let git_blame_gutter_max_author_length = self
2192 .render_git_blame_gutter(cx)
2193 .then(|| {
2194 if let Some(blame) = self.blame.as_ref() {
2195 let max_author_length =
2196 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2197 Some(max_author_length)
2198 } else {
2199 None
2200 }
2201 })
2202 .flatten();
2203
2204 EditorSnapshot {
2205 mode: self.mode,
2206 show_gutter: self.show_gutter,
2207 show_line_numbers: self.show_line_numbers,
2208 show_git_diff_gutter: self.show_git_diff_gutter,
2209 show_code_actions: self.show_code_actions,
2210 show_runnables: self.show_runnables,
2211 show_breakpoints: self.show_breakpoints,
2212 git_blame_gutter_max_author_length,
2213 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2214 scroll_anchor: self.scroll_manager.anchor(),
2215 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2216 placeholder_text: self.placeholder_text.clone(),
2217 is_focused: self.focus_handle.is_focused(window),
2218 current_line_highlight: self
2219 .current_line_highlight
2220 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2221 gutter_hovered: self.gutter_hovered,
2222 }
2223 }
2224
2225 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2226 self.buffer.read(cx).language_at(point, cx)
2227 }
2228
2229 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2230 self.buffer.read(cx).read(cx).file_at(point).cloned()
2231 }
2232
2233 pub fn active_excerpt(
2234 &self,
2235 cx: &App,
2236 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2237 self.buffer
2238 .read(cx)
2239 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2240 }
2241
2242 pub fn mode(&self) -> EditorMode {
2243 self.mode
2244 }
2245
2246 pub fn set_mode(&mut self, mode: EditorMode) {
2247 self.mode = mode;
2248 }
2249
2250 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2251 self.collaboration_hub.as_deref()
2252 }
2253
2254 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2255 self.collaboration_hub = Some(hub);
2256 }
2257
2258 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2259 self.in_project_search = in_project_search;
2260 }
2261
2262 pub fn set_custom_context_menu(
2263 &mut self,
2264 f: impl 'static
2265 + Fn(
2266 &mut Self,
2267 DisplayPoint,
2268 &mut Window,
2269 &mut Context<Self>,
2270 ) -> Option<Entity<ui::ContextMenu>>,
2271 ) {
2272 self.custom_context_menu = Some(Box::new(f))
2273 }
2274
2275 pub fn set_completion_provider(&mut self, provider: Option<Box<dyn CompletionProvider>>) {
2276 self.completion_provider = provider;
2277 }
2278
2279 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2280 self.semantics_provider.clone()
2281 }
2282
2283 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2284 self.semantics_provider = provider;
2285 }
2286
2287 pub fn set_edit_prediction_provider<T>(
2288 &mut self,
2289 provider: Option<Entity<T>>,
2290 window: &mut Window,
2291 cx: &mut Context<Self>,
2292 ) where
2293 T: EditPredictionProvider,
2294 {
2295 self.edit_prediction_provider =
2296 provider.map(|provider| RegisteredInlineCompletionProvider {
2297 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2298 if this.focus_handle.is_focused(window) {
2299 this.update_visible_inline_completion(window, cx);
2300 }
2301 }),
2302 provider: Arc::new(provider),
2303 });
2304 self.update_edit_prediction_settings(cx);
2305 self.refresh_inline_completion(false, false, window, cx);
2306 }
2307
2308 pub fn placeholder_text(&self) -> Option<&str> {
2309 self.placeholder_text.as_deref()
2310 }
2311
2312 pub fn set_placeholder_text(
2313 &mut self,
2314 placeholder_text: impl Into<Arc<str>>,
2315 cx: &mut Context<Self>,
2316 ) {
2317 let placeholder_text = Some(placeholder_text.into());
2318 if self.placeholder_text != placeholder_text {
2319 self.placeholder_text = placeholder_text;
2320 cx.notify();
2321 }
2322 }
2323
2324 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2325 self.cursor_shape = cursor_shape;
2326
2327 // Disrupt blink for immediate user feedback that the cursor shape has changed
2328 self.blink_manager.update(cx, BlinkManager::show_cursor);
2329
2330 cx.notify();
2331 }
2332
2333 pub fn set_current_line_highlight(
2334 &mut self,
2335 current_line_highlight: Option<CurrentLineHighlight>,
2336 ) {
2337 self.current_line_highlight = current_line_highlight;
2338 }
2339
2340 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2341 self.collapse_matches = collapse_matches;
2342 }
2343
2344 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2345 let buffers = self.buffer.read(cx).all_buffers();
2346 let Some(project) = self.project.as_ref() else {
2347 return;
2348 };
2349 project.update(cx, |project, cx| {
2350 for buffer in buffers {
2351 self.registered_buffers
2352 .entry(buffer.read(cx).remote_id())
2353 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2354 }
2355 })
2356 }
2357
2358 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2359 if self.collapse_matches {
2360 return range.start..range.start;
2361 }
2362 range.clone()
2363 }
2364
2365 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2366 if self.display_map.read(cx).clip_at_line_ends != clip {
2367 self.display_map
2368 .update(cx, |map, _| map.clip_at_line_ends = clip);
2369 }
2370 }
2371
2372 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2373 self.input_enabled = input_enabled;
2374 }
2375
2376 pub fn set_inline_completions_hidden_for_vim_mode(
2377 &mut self,
2378 hidden: bool,
2379 window: &mut Window,
2380 cx: &mut Context<Self>,
2381 ) {
2382 if hidden != self.inline_completions_hidden_for_vim_mode {
2383 self.inline_completions_hidden_for_vim_mode = hidden;
2384 if hidden {
2385 self.update_visible_inline_completion(window, cx);
2386 } else {
2387 self.refresh_inline_completion(true, false, window, cx);
2388 }
2389 }
2390 }
2391
2392 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2393 self.menu_inline_completions_policy = value;
2394 }
2395
2396 pub fn set_autoindent(&mut self, autoindent: bool) {
2397 if autoindent {
2398 self.autoindent_mode = Some(AutoindentMode::EachLine);
2399 } else {
2400 self.autoindent_mode = None;
2401 }
2402 }
2403
2404 pub fn read_only(&self, cx: &App) -> bool {
2405 self.read_only || self.buffer.read(cx).read_only()
2406 }
2407
2408 pub fn set_read_only(&mut self, read_only: bool) {
2409 self.read_only = read_only;
2410 }
2411
2412 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2413 self.use_autoclose = autoclose;
2414 }
2415
2416 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2417 self.use_auto_surround = auto_surround;
2418 }
2419
2420 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2421 self.auto_replace_emoji_shortcode = auto_replace;
2422 }
2423
2424 pub fn toggle_edit_predictions(
2425 &mut self,
2426 _: &ToggleEditPrediction,
2427 window: &mut Window,
2428 cx: &mut Context<Self>,
2429 ) {
2430 if self.show_inline_completions_override.is_some() {
2431 self.set_show_edit_predictions(None, window, cx);
2432 } else {
2433 let show_edit_predictions = !self.edit_predictions_enabled();
2434 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2435 }
2436 }
2437
2438 pub fn set_show_edit_predictions(
2439 &mut self,
2440 show_edit_predictions: Option<bool>,
2441 window: &mut Window,
2442 cx: &mut Context<Self>,
2443 ) {
2444 self.show_inline_completions_override = show_edit_predictions;
2445 self.update_edit_prediction_settings(cx);
2446
2447 if let Some(false) = show_edit_predictions {
2448 self.discard_inline_completion(false, cx);
2449 } else {
2450 self.refresh_inline_completion(false, true, window, cx);
2451 }
2452 }
2453
2454 fn inline_completions_disabled_in_scope(
2455 &self,
2456 buffer: &Entity<Buffer>,
2457 buffer_position: language::Anchor,
2458 cx: &App,
2459 ) -> bool {
2460 let snapshot = buffer.read(cx).snapshot();
2461 let settings = snapshot.settings_at(buffer_position, cx);
2462
2463 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2464 return false;
2465 };
2466
2467 scope.override_name().map_or(false, |scope_name| {
2468 settings
2469 .edit_predictions_disabled_in
2470 .iter()
2471 .any(|s| s == scope_name)
2472 })
2473 }
2474
2475 pub fn set_use_modal_editing(&mut self, to: bool) {
2476 self.use_modal_editing = to;
2477 }
2478
2479 pub fn use_modal_editing(&self) -> bool {
2480 self.use_modal_editing
2481 }
2482
2483 fn selections_did_change(
2484 &mut self,
2485 local: bool,
2486 old_cursor_position: &Anchor,
2487 show_completions: bool,
2488 window: &mut Window,
2489 cx: &mut Context<Self>,
2490 ) {
2491 window.invalidate_character_coordinates();
2492
2493 // Copy selections to primary selection buffer
2494 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2495 if local {
2496 let selections = self.selections.all::<usize>(cx);
2497 let buffer_handle = self.buffer.read(cx).read(cx);
2498
2499 let mut text = String::new();
2500 for (index, selection) in selections.iter().enumerate() {
2501 let text_for_selection = buffer_handle
2502 .text_for_range(selection.start..selection.end)
2503 .collect::<String>();
2504
2505 text.push_str(&text_for_selection);
2506 if index != selections.len() - 1 {
2507 text.push('\n');
2508 }
2509 }
2510
2511 if !text.is_empty() {
2512 cx.write_to_primary(ClipboardItem::new_string(text));
2513 }
2514 }
2515
2516 if self.focus_handle.is_focused(window) && self.leader_peer_id.is_none() {
2517 self.buffer.update(cx, |buffer, cx| {
2518 buffer.set_active_selections(
2519 &self.selections.disjoint_anchors(),
2520 self.selections.line_mode,
2521 self.cursor_shape,
2522 cx,
2523 )
2524 });
2525 }
2526 let display_map = self
2527 .display_map
2528 .update(cx, |display_map, cx| display_map.snapshot(cx));
2529 let buffer = &display_map.buffer_snapshot;
2530 self.add_selections_state = None;
2531 self.select_next_state = None;
2532 self.select_prev_state = None;
2533 self.select_syntax_node_history.try_clear();
2534 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2535 self.snippet_stack
2536 .invalidate(&self.selections.disjoint_anchors(), buffer);
2537 self.take_rename(false, window, cx);
2538
2539 let new_cursor_position = self.selections.newest_anchor().head();
2540
2541 self.push_to_nav_history(
2542 *old_cursor_position,
2543 Some(new_cursor_position.to_point(buffer)),
2544 false,
2545 cx,
2546 );
2547
2548 if local {
2549 let new_cursor_position = self.selections.newest_anchor().head();
2550 let mut context_menu = self.context_menu.borrow_mut();
2551 let completion_menu = match context_menu.as_ref() {
2552 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2553 _ => {
2554 *context_menu = None;
2555 None
2556 }
2557 };
2558 if let Some(buffer_id) = new_cursor_position.buffer_id {
2559 if !self.registered_buffers.contains_key(&buffer_id) {
2560 if let Some(project) = self.project.as_ref() {
2561 project.update(cx, |project, cx| {
2562 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2563 return;
2564 };
2565 self.registered_buffers.insert(
2566 buffer_id,
2567 project.register_buffer_with_language_servers(&buffer, cx),
2568 );
2569 })
2570 }
2571 }
2572 }
2573
2574 if let Some(completion_menu) = completion_menu {
2575 let cursor_position = new_cursor_position.to_offset(buffer);
2576 let (word_range, kind) =
2577 buffer.surrounding_word(completion_menu.initial_position, true);
2578 if kind == Some(CharKind::Word)
2579 && word_range.to_inclusive().contains(&cursor_position)
2580 {
2581 let mut completion_menu = completion_menu.clone();
2582 drop(context_menu);
2583
2584 let query = Self::completion_query(buffer, cursor_position);
2585 cx.spawn(async move |this, cx| {
2586 completion_menu
2587 .filter(query.as_deref(), cx.background_executor().clone())
2588 .await;
2589
2590 this.update(cx, |this, cx| {
2591 let mut context_menu = this.context_menu.borrow_mut();
2592 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2593 else {
2594 return;
2595 };
2596
2597 if menu.id > completion_menu.id {
2598 return;
2599 }
2600
2601 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2602 drop(context_menu);
2603 cx.notify();
2604 })
2605 })
2606 .detach();
2607
2608 if show_completions {
2609 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2610 }
2611 } else {
2612 drop(context_menu);
2613 self.hide_context_menu(window, cx);
2614 }
2615 } else {
2616 drop(context_menu);
2617 }
2618
2619 hide_hover(self, cx);
2620
2621 if old_cursor_position.to_display_point(&display_map).row()
2622 != new_cursor_position.to_display_point(&display_map).row()
2623 {
2624 self.available_code_actions.take();
2625 }
2626 self.refresh_code_actions(window, cx);
2627 self.refresh_document_highlights(cx);
2628 self.refresh_selected_text_highlights(false, window, cx);
2629 refresh_matching_bracket_highlights(self, window, cx);
2630 self.update_visible_inline_completion(window, cx);
2631 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2632 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2633 self.inline_blame_popover.take();
2634 if self.git_blame_inline_enabled {
2635 self.start_inline_blame_timer(window, cx);
2636 }
2637 }
2638
2639 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2640 cx.emit(EditorEvent::SelectionsChanged { local });
2641
2642 let selections = &self.selections.disjoint;
2643 if selections.len() == 1 {
2644 cx.emit(SearchEvent::ActiveMatchChanged)
2645 }
2646 if local {
2647 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2648 let inmemory_selections = selections
2649 .iter()
2650 .map(|s| {
2651 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2652 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2653 })
2654 .collect();
2655 self.update_restoration_data(cx, |data| {
2656 data.selections = inmemory_selections;
2657 });
2658
2659 if WorkspaceSettings::get(None, cx).restore_on_startup
2660 != RestoreOnStartupBehavior::None
2661 {
2662 if let Some(workspace_id) =
2663 self.workspace.as_ref().and_then(|workspace| workspace.1)
2664 {
2665 let snapshot = self.buffer().read(cx).snapshot(cx);
2666 let selections = selections.clone();
2667 let background_executor = cx.background_executor().clone();
2668 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2669 self.serialize_selections = cx.background_spawn(async move {
2670 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2671 let db_selections = selections
2672 .iter()
2673 .map(|selection| {
2674 (
2675 selection.start.to_offset(&snapshot),
2676 selection.end.to_offset(&snapshot),
2677 )
2678 })
2679 .collect();
2680
2681 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2682 .await
2683 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2684 .log_err();
2685 });
2686 }
2687 }
2688 }
2689 }
2690
2691 cx.notify();
2692 }
2693
2694 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2695 use text::ToOffset as _;
2696 use text::ToPoint as _;
2697
2698 if WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None {
2699 return;
2700 }
2701
2702 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2703 return;
2704 };
2705
2706 let snapshot = singleton.read(cx).snapshot();
2707 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2708 let display_snapshot = display_map.snapshot(cx);
2709
2710 display_snapshot
2711 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2712 .map(|fold| {
2713 fold.range.start.text_anchor.to_point(&snapshot)
2714 ..fold.range.end.text_anchor.to_point(&snapshot)
2715 })
2716 .collect()
2717 });
2718 self.update_restoration_data(cx, |data| {
2719 data.folds = inmemory_folds;
2720 });
2721
2722 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2723 return;
2724 };
2725 let background_executor = cx.background_executor().clone();
2726 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2727 let db_folds = self.display_map.update(cx, |display_map, cx| {
2728 display_map
2729 .snapshot(cx)
2730 .folds_in_range(0..snapshot.len())
2731 .map(|fold| {
2732 (
2733 fold.range.start.text_anchor.to_offset(&snapshot),
2734 fold.range.end.text_anchor.to_offset(&snapshot),
2735 )
2736 })
2737 .collect()
2738 });
2739 self.serialize_folds = cx.background_spawn(async move {
2740 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2741 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2742 .await
2743 .with_context(|| {
2744 format!(
2745 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2746 )
2747 })
2748 .log_err();
2749 });
2750 }
2751
2752 pub fn sync_selections(
2753 &mut self,
2754 other: Entity<Editor>,
2755 cx: &mut Context<Self>,
2756 ) -> gpui::Subscription {
2757 let other_selections = other.read(cx).selections.disjoint.to_vec();
2758 self.selections.change_with(cx, |selections| {
2759 selections.select_anchors(other_selections);
2760 });
2761
2762 let other_subscription =
2763 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2764 EditorEvent::SelectionsChanged { local: true } => {
2765 let other_selections = other.read(cx).selections.disjoint.to_vec();
2766 if other_selections.is_empty() {
2767 return;
2768 }
2769 this.selections.change_with(cx, |selections| {
2770 selections.select_anchors(other_selections);
2771 });
2772 }
2773 _ => {}
2774 });
2775
2776 let this_subscription =
2777 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2778 EditorEvent::SelectionsChanged { local: true } => {
2779 let these_selections = this.selections.disjoint.to_vec();
2780 if these_selections.is_empty() {
2781 return;
2782 }
2783 other.update(cx, |other_editor, cx| {
2784 other_editor.selections.change_with(cx, |selections| {
2785 selections.select_anchors(these_selections);
2786 })
2787 });
2788 }
2789 _ => {}
2790 });
2791
2792 Subscription::join(other_subscription, this_subscription)
2793 }
2794
2795 pub fn change_selections<R>(
2796 &mut self,
2797 autoscroll: Option<Autoscroll>,
2798 window: &mut Window,
2799 cx: &mut Context<Self>,
2800 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2801 ) -> R {
2802 self.change_selections_inner(autoscroll, true, window, cx, change)
2803 }
2804
2805 fn change_selections_inner<R>(
2806 &mut self,
2807 autoscroll: Option<Autoscroll>,
2808 request_completions: bool,
2809 window: &mut Window,
2810 cx: &mut Context<Self>,
2811 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2812 ) -> R {
2813 let old_cursor_position = self.selections.newest_anchor().head();
2814 self.push_to_selection_history();
2815
2816 let (changed, result) = self.selections.change_with(cx, change);
2817
2818 if changed {
2819 if let Some(autoscroll) = autoscroll {
2820 self.request_autoscroll(autoscroll, cx);
2821 }
2822 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2823
2824 if self.should_open_signature_help_automatically(
2825 &old_cursor_position,
2826 self.signature_help_state.backspace_pressed(),
2827 cx,
2828 ) {
2829 self.show_signature_help(&ShowSignatureHelp, window, cx);
2830 }
2831 self.signature_help_state.set_backspace_pressed(false);
2832 }
2833
2834 result
2835 }
2836
2837 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2838 where
2839 I: IntoIterator<Item = (Range<S>, T)>,
2840 S: ToOffset,
2841 T: Into<Arc<str>>,
2842 {
2843 if self.read_only(cx) {
2844 return;
2845 }
2846
2847 self.buffer
2848 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2849 }
2850
2851 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2852 where
2853 I: IntoIterator<Item = (Range<S>, T)>,
2854 S: ToOffset,
2855 T: Into<Arc<str>>,
2856 {
2857 if self.read_only(cx) {
2858 return;
2859 }
2860
2861 self.buffer.update(cx, |buffer, cx| {
2862 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2863 });
2864 }
2865
2866 pub fn edit_with_block_indent<I, S, T>(
2867 &mut self,
2868 edits: I,
2869 original_indent_columns: Vec<Option<u32>>,
2870 cx: &mut Context<Self>,
2871 ) where
2872 I: IntoIterator<Item = (Range<S>, T)>,
2873 S: ToOffset,
2874 T: Into<Arc<str>>,
2875 {
2876 if self.read_only(cx) {
2877 return;
2878 }
2879
2880 self.buffer.update(cx, |buffer, cx| {
2881 buffer.edit(
2882 edits,
2883 Some(AutoindentMode::Block {
2884 original_indent_columns,
2885 }),
2886 cx,
2887 )
2888 });
2889 }
2890
2891 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2892 self.hide_context_menu(window, cx);
2893
2894 match phase {
2895 SelectPhase::Begin {
2896 position,
2897 add,
2898 click_count,
2899 } => self.begin_selection(position, add, click_count, window, cx),
2900 SelectPhase::BeginColumnar {
2901 position,
2902 goal_column,
2903 reset,
2904 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
2905 SelectPhase::Extend {
2906 position,
2907 click_count,
2908 } => self.extend_selection(position, click_count, window, cx),
2909 SelectPhase::Update {
2910 position,
2911 goal_column,
2912 scroll_delta,
2913 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
2914 SelectPhase::End => self.end_selection(window, cx),
2915 }
2916 }
2917
2918 fn extend_selection(
2919 &mut self,
2920 position: DisplayPoint,
2921 click_count: usize,
2922 window: &mut Window,
2923 cx: &mut Context<Self>,
2924 ) {
2925 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2926 let tail = self.selections.newest::<usize>(cx).tail();
2927 self.begin_selection(position, false, click_count, window, cx);
2928
2929 let position = position.to_offset(&display_map, Bias::Left);
2930 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
2931
2932 let mut pending_selection = self
2933 .selections
2934 .pending_anchor()
2935 .expect("extend_selection not called with pending selection");
2936 if position >= tail {
2937 pending_selection.start = tail_anchor;
2938 } else {
2939 pending_selection.end = tail_anchor;
2940 pending_selection.reversed = true;
2941 }
2942
2943 let mut pending_mode = self.selections.pending_mode().unwrap();
2944 match &mut pending_mode {
2945 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
2946 _ => {}
2947 }
2948
2949 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
2950 s.set_pending(pending_selection, pending_mode)
2951 });
2952 }
2953
2954 fn begin_selection(
2955 &mut self,
2956 position: DisplayPoint,
2957 add: bool,
2958 click_count: usize,
2959 window: &mut Window,
2960 cx: &mut Context<Self>,
2961 ) {
2962 if !self.focus_handle.is_focused(window) {
2963 self.last_focused_descendant = None;
2964 window.focus(&self.focus_handle);
2965 }
2966
2967 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
2968 let buffer = &display_map.buffer_snapshot;
2969 let newest_selection = self.selections.newest_anchor().clone();
2970 let position = display_map.clip_point(position, Bias::Left);
2971
2972 let start;
2973 let end;
2974 let mode;
2975 let mut auto_scroll;
2976 match click_count {
2977 1 => {
2978 start = buffer.anchor_before(position.to_point(&display_map));
2979 end = start;
2980 mode = SelectMode::Character;
2981 auto_scroll = true;
2982 }
2983 2 => {
2984 let range = movement::surrounding_word(&display_map, position);
2985 start = buffer.anchor_before(range.start.to_point(&display_map));
2986 end = buffer.anchor_before(range.end.to_point(&display_map));
2987 mode = SelectMode::Word(start..end);
2988 auto_scroll = true;
2989 }
2990 3 => {
2991 let position = display_map
2992 .clip_point(position, Bias::Left)
2993 .to_point(&display_map);
2994 let line_start = display_map.prev_line_boundary(position).0;
2995 let next_line_start = buffer.clip_point(
2996 display_map.next_line_boundary(position).0 + Point::new(1, 0),
2997 Bias::Left,
2998 );
2999 start = buffer.anchor_before(line_start);
3000 end = buffer.anchor_before(next_line_start);
3001 mode = SelectMode::Line(start..end);
3002 auto_scroll = true;
3003 }
3004 _ => {
3005 start = buffer.anchor_before(0);
3006 end = buffer.anchor_before(buffer.len());
3007 mode = SelectMode::All;
3008 auto_scroll = false;
3009 }
3010 }
3011 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3012
3013 let point_to_delete: Option<usize> = {
3014 let selected_points: Vec<Selection<Point>> =
3015 self.selections.disjoint_in_range(start..end, cx);
3016
3017 if !add || click_count > 1 {
3018 None
3019 } else if !selected_points.is_empty() {
3020 Some(selected_points[0].id)
3021 } else {
3022 let clicked_point_already_selected =
3023 self.selections.disjoint.iter().find(|selection| {
3024 selection.start.to_point(buffer) == start.to_point(buffer)
3025 || selection.end.to_point(buffer) == end.to_point(buffer)
3026 });
3027
3028 clicked_point_already_selected.map(|selection| selection.id)
3029 }
3030 };
3031
3032 let selections_count = self.selections.count();
3033
3034 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3035 if let Some(point_to_delete) = point_to_delete {
3036 s.delete(point_to_delete);
3037
3038 if selections_count == 1 {
3039 s.set_pending_anchor_range(start..end, mode);
3040 }
3041 } else {
3042 if !add {
3043 s.clear_disjoint();
3044 } else if click_count > 1 {
3045 s.delete(newest_selection.id)
3046 }
3047
3048 s.set_pending_anchor_range(start..end, mode);
3049 }
3050 });
3051 }
3052
3053 fn begin_columnar_selection(
3054 &mut self,
3055 position: DisplayPoint,
3056 goal_column: u32,
3057 reset: bool,
3058 window: &mut Window,
3059 cx: &mut Context<Self>,
3060 ) {
3061 if !self.focus_handle.is_focused(window) {
3062 self.last_focused_descendant = None;
3063 window.focus(&self.focus_handle);
3064 }
3065
3066 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3067
3068 if reset {
3069 let pointer_position = display_map
3070 .buffer_snapshot
3071 .anchor_before(position.to_point(&display_map));
3072
3073 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3074 s.clear_disjoint();
3075 s.set_pending_anchor_range(
3076 pointer_position..pointer_position,
3077 SelectMode::Character,
3078 );
3079 });
3080 }
3081
3082 let tail = self.selections.newest::<Point>(cx).tail();
3083 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3084
3085 if !reset {
3086 self.select_columns(
3087 tail.to_display_point(&display_map),
3088 position,
3089 goal_column,
3090 &display_map,
3091 window,
3092 cx,
3093 );
3094 }
3095 }
3096
3097 fn update_selection(
3098 &mut self,
3099 position: DisplayPoint,
3100 goal_column: u32,
3101 scroll_delta: gpui::Point<f32>,
3102 window: &mut Window,
3103 cx: &mut Context<Self>,
3104 ) {
3105 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3106
3107 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3108 let tail = tail.to_display_point(&display_map);
3109 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3110 } else if let Some(mut pending) = self.selections.pending_anchor() {
3111 let buffer = self.buffer.read(cx).snapshot(cx);
3112 let head;
3113 let tail;
3114 let mode = self.selections.pending_mode().unwrap();
3115 match &mode {
3116 SelectMode::Character => {
3117 head = position.to_point(&display_map);
3118 tail = pending.tail().to_point(&buffer);
3119 }
3120 SelectMode::Word(original_range) => {
3121 let original_display_range = original_range.start.to_display_point(&display_map)
3122 ..original_range.end.to_display_point(&display_map);
3123 let original_buffer_range = original_display_range.start.to_point(&display_map)
3124 ..original_display_range.end.to_point(&display_map);
3125 if movement::is_inside_word(&display_map, position)
3126 || original_display_range.contains(&position)
3127 {
3128 let word_range = movement::surrounding_word(&display_map, position);
3129 if word_range.start < original_display_range.start {
3130 head = word_range.start.to_point(&display_map);
3131 } else {
3132 head = word_range.end.to_point(&display_map);
3133 }
3134 } else {
3135 head = position.to_point(&display_map);
3136 }
3137
3138 if head <= original_buffer_range.start {
3139 tail = original_buffer_range.end;
3140 } else {
3141 tail = original_buffer_range.start;
3142 }
3143 }
3144 SelectMode::Line(original_range) => {
3145 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3146
3147 let position = display_map
3148 .clip_point(position, Bias::Left)
3149 .to_point(&display_map);
3150 let line_start = display_map.prev_line_boundary(position).0;
3151 let next_line_start = buffer.clip_point(
3152 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3153 Bias::Left,
3154 );
3155
3156 if line_start < original_range.start {
3157 head = line_start
3158 } else {
3159 head = next_line_start
3160 }
3161
3162 if head <= original_range.start {
3163 tail = original_range.end;
3164 } else {
3165 tail = original_range.start;
3166 }
3167 }
3168 SelectMode::All => {
3169 return;
3170 }
3171 };
3172
3173 if head < tail {
3174 pending.start = buffer.anchor_before(head);
3175 pending.end = buffer.anchor_before(tail);
3176 pending.reversed = true;
3177 } else {
3178 pending.start = buffer.anchor_before(tail);
3179 pending.end = buffer.anchor_before(head);
3180 pending.reversed = false;
3181 }
3182
3183 self.change_selections(None, window, cx, |s| {
3184 s.set_pending(pending, mode);
3185 });
3186 } else {
3187 log::error!("update_selection dispatched with no pending selection");
3188 return;
3189 }
3190
3191 self.apply_scroll_delta(scroll_delta, window, cx);
3192 cx.notify();
3193 }
3194
3195 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3196 self.columnar_selection_tail.take();
3197 if self.selections.pending_anchor().is_some() {
3198 let selections = self.selections.all::<usize>(cx);
3199 self.change_selections(None, window, cx, |s| {
3200 s.select(selections);
3201 s.clear_pending();
3202 });
3203 }
3204 }
3205
3206 fn select_columns(
3207 &mut self,
3208 tail: DisplayPoint,
3209 head: DisplayPoint,
3210 goal_column: u32,
3211 display_map: &DisplaySnapshot,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 let start_row = cmp::min(tail.row(), head.row());
3216 let end_row = cmp::max(tail.row(), head.row());
3217 let start_column = cmp::min(tail.column(), goal_column);
3218 let end_column = cmp::max(tail.column(), goal_column);
3219 let reversed = start_column < tail.column();
3220
3221 let selection_ranges = (start_row.0..=end_row.0)
3222 .map(DisplayRow)
3223 .filter_map(|row| {
3224 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3225 let start = display_map
3226 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3227 .to_point(display_map);
3228 let end = display_map
3229 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3230 .to_point(display_map);
3231 if reversed {
3232 Some(end..start)
3233 } else {
3234 Some(start..end)
3235 }
3236 } else {
3237 None
3238 }
3239 })
3240 .collect::<Vec<_>>();
3241
3242 self.change_selections(None, window, cx, |s| {
3243 s.select_ranges(selection_ranges);
3244 });
3245 cx.notify();
3246 }
3247
3248 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3249 self.selections
3250 .all_adjusted(cx)
3251 .iter()
3252 .any(|selection| !selection.is_empty())
3253 }
3254
3255 pub fn has_pending_nonempty_selection(&self) -> bool {
3256 let pending_nonempty_selection = match self.selections.pending_anchor() {
3257 Some(Selection { start, end, .. }) => start != end,
3258 None => false,
3259 };
3260
3261 pending_nonempty_selection
3262 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3263 }
3264
3265 pub fn has_pending_selection(&self) -> bool {
3266 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3267 }
3268
3269 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3270 self.selection_mark_mode = false;
3271
3272 if self.clear_expanded_diff_hunks(cx) {
3273 cx.notify();
3274 return;
3275 }
3276 if self.dismiss_menus_and_popups(true, window, cx) {
3277 return;
3278 }
3279
3280 if self.mode.is_full()
3281 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3282 {
3283 return;
3284 }
3285
3286 cx.propagate();
3287 }
3288
3289 pub fn dismiss_menus_and_popups(
3290 &mut self,
3291 is_user_requested: bool,
3292 window: &mut Window,
3293 cx: &mut Context<Self>,
3294 ) -> bool {
3295 if self.take_rename(false, window, cx).is_some() {
3296 return true;
3297 }
3298
3299 if hide_hover(self, cx) {
3300 return true;
3301 }
3302
3303 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3304 return true;
3305 }
3306
3307 if self.hide_context_menu(window, cx).is_some() {
3308 return true;
3309 }
3310
3311 if self.mouse_context_menu.take().is_some() {
3312 return true;
3313 }
3314
3315 if is_user_requested && self.discard_inline_completion(true, cx) {
3316 return true;
3317 }
3318
3319 if self.snippet_stack.pop().is_some() {
3320 return true;
3321 }
3322
3323 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3324 self.dismiss_diagnostics(cx);
3325 return true;
3326 }
3327
3328 false
3329 }
3330
3331 fn linked_editing_ranges_for(
3332 &self,
3333 selection: Range<text::Anchor>,
3334 cx: &App,
3335 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3336 if self.linked_edit_ranges.is_empty() {
3337 return None;
3338 }
3339 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3340 selection.end.buffer_id.and_then(|end_buffer_id| {
3341 if selection.start.buffer_id != Some(end_buffer_id) {
3342 return None;
3343 }
3344 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3345 let snapshot = buffer.read(cx).snapshot();
3346 self.linked_edit_ranges
3347 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3348 .map(|ranges| (ranges, snapshot, buffer))
3349 })?;
3350 use text::ToOffset as TO;
3351 // find offset from the start of current range to current cursor position
3352 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3353
3354 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3355 let start_difference = start_offset - start_byte_offset;
3356 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3357 let end_difference = end_offset - start_byte_offset;
3358 // Current range has associated linked ranges.
3359 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3360 for range in linked_ranges.iter() {
3361 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3362 let end_offset = start_offset + end_difference;
3363 let start_offset = start_offset + start_difference;
3364 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3365 continue;
3366 }
3367 if self.selections.disjoint_anchor_ranges().any(|s| {
3368 if s.start.buffer_id != selection.start.buffer_id
3369 || s.end.buffer_id != selection.end.buffer_id
3370 {
3371 return false;
3372 }
3373 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3374 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3375 }) {
3376 continue;
3377 }
3378 let start = buffer_snapshot.anchor_after(start_offset);
3379 let end = buffer_snapshot.anchor_after(end_offset);
3380 linked_edits
3381 .entry(buffer.clone())
3382 .or_default()
3383 .push(start..end);
3384 }
3385 Some(linked_edits)
3386 }
3387
3388 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3389 let text: Arc<str> = text.into();
3390
3391 if self.read_only(cx) {
3392 return;
3393 }
3394
3395 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3396
3397 let selections = self.selections.all_adjusted(cx);
3398 let mut bracket_inserted = false;
3399 let mut edits = Vec::new();
3400 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3401 let mut new_selections = Vec::with_capacity(selections.len());
3402 let mut new_autoclose_regions = Vec::new();
3403 let snapshot = self.buffer.read(cx).read(cx);
3404 let mut clear_linked_edit_ranges = false;
3405
3406 for (selection, autoclose_region) in
3407 self.selections_with_autoclose_regions(selections, &snapshot)
3408 {
3409 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3410 // Determine if the inserted text matches the opening or closing
3411 // bracket of any of this language's bracket pairs.
3412 let mut bracket_pair = None;
3413 let mut is_bracket_pair_start = false;
3414 let mut is_bracket_pair_end = false;
3415 if !text.is_empty() {
3416 let mut bracket_pair_matching_end = None;
3417 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3418 // and they are removing the character that triggered IME popup.
3419 for (pair, enabled) in scope.brackets() {
3420 if !pair.close && !pair.surround {
3421 continue;
3422 }
3423
3424 if enabled && pair.start.ends_with(text.as_ref()) {
3425 let prefix_len = pair.start.len() - text.len();
3426 let preceding_text_matches_prefix = prefix_len == 0
3427 || (selection.start.column >= (prefix_len as u32)
3428 && snapshot.contains_str_at(
3429 Point::new(
3430 selection.start.row,
3431 selection.start.column - (prefix_len as u32),
3432 ),
3433 &pair.start[..prefix_len],
3434 ));
3435 if preceding_text_matches_prefix {
3436 bracket_pair = Some(pair.clone());
3437 is_bracket_pair_start = true;
3438 break;
3439 }
3440 }
3441 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3442 {
3443 // take first bracket pair matching end, but don't break in case a later bracket
3444 // pair matches start
3445 bracket_pair_matching_end = Some(pair.clone());
3446 }
3447 }
3448 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3449 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3450 is_bracket_pair_end = true;
3451 }
3452 }
3453
3454 if let Some(bracket_pair) = bracket_pair {
3455 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3456 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3457 let auto_surround =
3458 self.use_auto_surround && snapshot_settings.use_auto_surround;
3459 if selection.is_empty() {
3460 if is_bracket_pair_start {
3461 // If the inserted text is a suffix of an opening bracket and the
3462 // selection is preceded by the rest of the opening bracket, then
3463 // insert the closing bracket.
3464 let following_text_allows_autoclose = snapshot
3465 .chars_at(selection.start)
3466 .next()
3467 .map_or(true, |c| scope.should_autoclose_before(c));
3468
3469 let preceding_text_allows_autoclose = selection.start.column == 0
3470 || snapshot.reversed_chars_at(selection.start).next().map_or(
3471 true,
3472 |c| {
3473 bracket_pair.start != bracket_pair.end
3474 || !snapshot
3475 .char_classifier_at(selection.start)
3476 .is_word(c)
3477 },
3478 );
3479
3480 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3481 && bracket_pair.start.len() == 1
3482 {
3483 let target = bracket_pair.start.chars().next().unwrap();
3484 let current_line_count = snapshot
3485 .reversed_chars_at(selection.start)
3486 .take_while(|&c| c != '\n')
3487 .filter(|&c| c == target)
3488 .count();
3489 current_line_count % 2 == 1
3490 } else {
3491 false
3492 };
3493
3494 if autoclose
3495 && bracket_pair.close
3496 && following_text_allows_autoclose
3497 && preceding_text_allows_autoclose
3498 && !is_closing_quote
3499 {
3500 let anchor = snapshot.anchor_before(selection.end);
3501 new_selections.push((selection.map(|_| anchor), text.len()));
3502 new_autoclose_regions.push((
3503 anchor,
3504 text.len(),
3505 selection.id,
3506 bracket_pair.clone(),
3507 ));
3508 edits.push((
3509 selection.range(),
3510 format!("{}{}", text, bracket_pair.end).into(),
3511 ));
3512 bracket_inserted = true;
3513 continue;
3514 }
3515 }
3516
3517 if let Some(region) = autoclose_region {
3518 // If the selection is followed by an auto-inserted closing bracket,
3519 // then don't insert that closing bracket again; just move the selection
3520 // past the closing bracket.
3521 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3522 && text.as_ref() == region.pair.end.as_str();
3523 if should_skip {
3524 let anchor = snapshot.anchor_after(selection.end);
3525 new_selections
3526 .push((selection.map(|_| anchor), region.pair.end.len()));
3527 continue;
3528 }
3529 }
3530
3531 let always_treat_brackets_as_autoclosed = snapshot
3532 .language_settings_at(selection.start, cx)
3533 .always_treat_brackets_as_autoclosed;
3534 if always_treat_brackets_as_autoclosed
3535 && is_bracket_pair_end
3536 && snapshot.contains_str_at(selection.end, text.as_ref())
3537 {
3538 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3539 // and the inserted text is a closing bracket and the selection is followed
3540 // by the closing bracket then move the selection past the closing bracket.
3541 let anchor = snapshot.anchor_after(selection.end);
3542 new_selections.push((selection.map(|_| anchor), text.len()));
3543 continue;
3544 }
3545 }
3546 // If an opening bracket is 1 character long and is typed while
3547 // text is selected, then surround that text with the bracket pair.
3548 else if auto_surround
3549 && bracket_pair.surround
3550 && is_bracket_pair_start
3551 && bracket_pair.start.chars().count() == 1
3552 {
3553 edits.push((selection.start..selection.start, text.clone()));
3554 edits.push((
3555 selection.end..selection.end,
3556 bracket_pair.end.as_str().into(),
3557 ));
3558 bracket_inserted = true;
3559 new_selections.push((
3560 Selection {
3561 id: selection.id,
3562 start: snapshot.anchor_after(selection.start),
3563 end: snapshot.anchor_before(selection.end),
3564 reversed: selection.reversed,
3565 goal: selection.goal,
3566 },
3567 0,
3568 ));
3569 continue;
3570 }
3571 }
3572 }
3573
3574 if self.auto_replace_emoji_shortcode
3575 && selection.is_empty()
3576 && text.as_ref().ends_with(':')
3577 {
3578 if let Some(possible_emoji_short_code) =
3579 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3580 {
3581 if !possible_emoji_short_code.is_empty() {
3582 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3583 let emoji_shortcode_start = Point::new(
3584 selection.start.row,
3585 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3586 );
3587
3588 // Remove shortcode from buffer
3589 edits.push((
3590 emoji_shortcode_start..selection.start,
3591 "".to_string().into(),
3592 ));
3593 new_selections.push((
3594 Selection {
3595 id: selection.id,
3596 start: snapshot.anchor_after(emoji_shortcode_start),
3597 end: snapshot.anchor_before(selection.start),
3598 reversed: selection.reversed,
3599 goal: selection.goal,
3600 },
3601 0,
3602 ));
3603
3604 // Insert emoji
3605 let selection_start_anchor = snapshot.anchor_after(selection.start);
3606 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3607 edits.push((selection.start..selection.end, emoji.to_string().into()));
3608
3609 continue;
3610 }
3611 }
3612 }
3613 }
3614
3615 // If not handling any auto-close operation, then just replace the selected
3616 // text with the given input and move the selection to the end of the
3617 // newly inserted text.
3618 let anchor = snapshot.anchor_after(selection.end);
3619 if !self.linked_edit_ranges.is_empty() {
3620 let start_anchor = snapshot.anchor_before(selection.start);
3621
3622 let is_word_char = text.chars().next().map_or(true, |char| {
3623 let classifier = snapshot.char_classifier_at(start_anchor.to_offset(&snapshot));
3624 classifier.is_word(char)
3625 });
3626
3627 if is_word_char {
3628 if let Some(ranges) = self
3629 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3630 {
3631 for (buffer, edits) in ranges {
3632 linked_edits
3633 .entry(buffer.clone())
3634 .or_default()
3635 .extend(edits.into_iter().map(|range| (range, text.clone())));
3636 }
3637 }
3638 } else {
3639 clear_linked_edit_ranges = true;
3640 }
3641 }
3642
3643 new_selections.push((selection.map(|_| anchor), 0));
3644 edits.push((selection.start..selection.end, text.clone()));
3645 }
3646
3647 drop(snapshot);
3648
3649 self.transact(window, cx, |this, window, cx| {
3650 if clear_linked_edit_ranges {
3651 this.linked_edit_ranges.clear();
3652 }
3653 let initial_buffer_versions =
3654 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3655
3656 this.buffer.update(cx, |buffer, cx| {
3657 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3658 });
3659 for (buffer, edits) in linked_edits {
3660 buffer.update(cx, |buffer, cx| {
3661 let snapshot = buffer.snapshot();
3662 let edits = edits
3663 .into_iter()
3664 .map(|(range, text)| {
3665 use text::ToPoint as TP;
3666 let end_point = TP::to_point(&range.end, &snapshot);
3667 let start_point = TP::to_point(&range.start, &snapshot);
3668 (start_point..end_point, text)
3669 })
3670 .sorted_by_key(|(range, _)| range.start);
3671 buffer.edit(edits, None, cx);
3672 })
3673 }
3674 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3675 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3676 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3677 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3678 .zip(new_selection_deltas)
3679 .map(|(selection, delta)| Selection {
3680 id: selection.id,
3681 start: selection.start + delta,
3682 end: selection.end + delta,
3683 reversed: selection.reversed,
3684 goal: SelectionGoal::None,
3685 })
3686 .collect::<Vec<_>>();
3687
3688 let mut i = 0;
3689 for (position, delta, selection_id, pair) in new_autoclose_regions {
3690 let position = position.to_offset(&map.buffer_snapshot) + delta;
3691 let start = map.buffer_snapshot.anchor_before(position);
3692 let end = map.buffer_snapshot.anchor_after(position);
3693 while let Some(existing_state) = this.autoclose_regions.get(i) {
3694 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3695 Ordering::Less => i += 1,
3696 Ordering::Greater => break,
3697 Ordering::Equal => {
3698 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3699 Ordering::Less => i += 1,
3700 Ordering::Equal => break,
3701 Ordering::Greater => break,
3702 }
3703 }
3704 }
3705 }
3706 this.autoclose_regions.insert(
3707 i,
3708 AutocloseRegion {
3709 selection_id,
3710 range: start..end,
3711 pair,
3712 },
3713 );
3714 }
3715
3716 let had_active_inline_completion = this.has_active_inline_completion();
3717 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3718 s.select(new_selections)
3719 });
3720
3721 if !bracket_inserted {
3722 if let Some(on_type_format_task) =
3723 this.trigger_on_type_formatting(text.to_string(), window, cx)
3724 {
3725 on_type_format_task.detach_and_log_err(cx);
3726 }
3727 }
3728
3729 let editor_settings = EditorSettings::get_global(cx);
3730 if bracket_inserted
3731 && (editor_settings.auto_signature_help
3732 || editor_settings.show_signature_help_after_edits)
3733 {
3734 this.show_signature_help(&ShowSignatureHelp, window, cx);
3735 }
3736
3737 let trigger_in_words =
3738 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3739 if this.hard_wrap.is_some() {
3740 let latest: Range<Point> = this.selections.newest(cx).range();
3741 if latest.is_empty()
3742 && this
3743 .buffer()
3744 .read(cx)
3745 .snapshot(cx)
3746 .line_len(MultiBufferRow(latest.start.row))
3747 == latest.start.column
3748 {
3749 this.rewrap_impl(
3750 RewrapOptions {
3751 override_language_settings: true,
3752 preserve_existing_whitespace: true,
3753 },
3754 cx,
3755 )
3756 }
3757 }
3758 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3759 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3760 this.refresh_inline_completion(true, false, window, cx);
3761 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3762 });
3763 }
3764
3765 fn find_possible_emoji_shortcode_at_position(
3766 snapshot: &MultiBufferSnapshot,
3767 position: Point,
3768 ) -> Option<String> {
3769 let mut chars = Vec::new();
3770 let mut found_colon = false;
3771 for char in snapshot.reversed_chars_at(position).take(100) {
3772 // Found a possible emoji shortcode in the middle of the buffer
3773 if found_colon {
3774 if char.is_whitespace() {
3775 chars.reverse();
3776 return Some(chars.iter().collect());
3777 }
3778 // If the previous character is not a whitespace, we are in the middle of a word
3779 // and we only want to complete the shortcode if the word is made up of other emojis
3780 let mut containing_word = String::new();
3781 for ch in snapshot
3782 .reversed_chars_at(position)
3783 .skip(chars.len() + 1)
3784 .take(100)
3785 {
3786 if ch.is_whitespace() {
3787 break;
3788 }
3789 containing_word.push(ch);
3790 }
3791 let containing_word = containing_word.chars().rev().collect::<String>();
3792 if util::word_consists_of_emojis(containing_word.as_str()) {
3793 chars.reverse();
3794 return Some(chars.iter().collect());
3795 }
3796 }
3797
3798 if char.is_whitespace() || !char.is_ascii() {
3799 return None;
3800 }
3801 if char == ':' {
3802 found_colon = true;
3803 } else {
3804 chars.push(char);
3805 }
3806 }
3807 // Found a possible emoji shortcode at the beginning of the buffer
3808 chars.reverse();
3809 Some(chars.iter().collect())
3810 }
3811
3812 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3813 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3814 self.transact(window, cx, |this, window, cx| {
3815 let (edits, selection_fixup_info): (Vec<_>, Vec<_>) = {
3816 let selections = this.selections.all::<usize>(cx);
3817 let multi_buffer = this.buffer.read(cx);
3818 let buffer = multi_buffer.snapshot(cx);
3819 selections
3820 .iter()
3821 .map(|selection| {
3822 let start_point = selection.start.to_point(&buffer);
3823 let mut indent =
3824 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3825 indent.len = cmp::min(indent.len, start_point.column);
3826 let start = selection.start;
3827 let end = selection.end;
3828 let selection_is_empty = start == end;
3829 let language_scope = buffer.language_scope_at(start);
3830 let (comment_delimiter, insert_extra_newline) = if let Some(language) =
3831 &language_scope
3832 {
3833 let insert_extra_newline =
3834 insert_extra_newline_brackets(&buffer, start..end, language)
3835 || insert_extra_newline_tree_sitter(&buffer, start..end);
3836
3837 // Comment extension on newline is allowed only for cursor selections
3838 let comment_delimiter = maybe!({
3839 if !selection_is_empty {
3840 return None;
3841 }
3842
3843 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3844 return None;
3845 }
3846
3847 let delimiters = language.line_comment_prefixes();
3848 let max_len_of_delimiter =
3849 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3850 let (snapshot, range) =
3851 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3852
3853 let mut index_of_first_non_whitespace = 0;
3854 let comment_candidate = snapshot
3855 .chars_for_range(range)
3856 .skip_while(|c| {
3857 let should_skip = c.is_whitespace();
3858 if should_skip {
3859 index_of_first_non_whitespace += 1;
3860 }
3861 should_skip
3862 })
3863 .take(max_len_of_delimiter)
3864 .collect::<String>();
3865 let comment_prefix = delimiters.iter().find(|comment_prefix| {
3866 comment_candidate.starts_with(comment_prefix.as_ref())
3867 })?;
3868 let cursor_is_placed_after_comment_marker =
3869 index_of_first_non_whitespace + comment_prefix.len()
3870 <= start_point.column as usize;
3871 if cursor_is_placed_after_comment_marker {
3872 Some(comment_prefix.clone())
3873 } else {
3874 None
3875 }
3876 });
3877 (comment_delimiter, insert_extra_newline)
3878 } else {
3879 (None, false)
3880 };
3881
3882 let capacity_for_delimiter = comment_delimiter
3883 .as_deref()
3884 .map(str::len)
3885 .unwrap_or_default();
3886 let mut new_text =
3887 String::with_capacity(1 + capacity_for_delimiter + indent.len as usize);
3888 new_text.push('\n');
3889 new_text.extend(indent.chars());
3890 if let Some(delimiter) = &comment_delimiter {
3891 new_text.push_str(delimiter);
3892 }
3893 if insert_extra_newline {
3894 new_text = new_text.repeat(2);
3895 }
3896
3897 let anchor = buffer.anchor_after(end);
3898 let new_selection = selection.map(|_| anchor);
3899 (
3900 (start..end, new_text),
3901 (insert_extra_newline, new_selection),
3902 )
3903 })
3904 .unzip()
3905 };
3906
3907 this.edit_with_autoindent(edits, cx);
3908 let buffer = this.buffer.read(cx).snapshot(cx);
3909 let new_selections = selection_fixup_info
3910 .into_iter()
3911 .map(|(extra_newline_inserted, new_selection)| {
3912 let mut cursor = new_selection.end.to_point(&buffer);
3913 if extra_newline_inserted {
3914 cursor.row -= 1;
3915 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
3916 }
3917 new_selection.map(|_| cursor)
3918 })
3919 .collect();
3920
3921 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3922 s.select(new_selections)
3923 });
3924 this.refresh_inline_completion(true, false, window, cx);
3925 });
3926 }
3927
3928 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
3929 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3930
3931 let buffer = self.buffer.read(cx);
3932 let snapshot = buffer.snapshot(cx);
3933
3934 let mut edits = Vec::new();
3935 let mut rows = Vec::new();
3936
3937 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
3938 let cursor = selection.head();
3939 let row = cursor.row;
3940
3941 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
3942
3943 let newline = "\n".to_string();
3944 edits.push((start_of_line..start_of_line, newline));
3945
3946 rows.push(row + rows_inserted as u32);
3947 }
3948
3949 self.transact(window, cx, |editor, window, cx| {
3950 editor.edit(edits, cx);
3951
3952 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
3953 let mut index = 0;
3954 s.move_cursors_with(|map, _, _| {
3955 let row = rows[index];
3956 index += 1;
3957
3958 let point = Point::new(row, 0);
3959 let boundary = map.next_line_boundary(point).1;
3960 let clipped = map.clip_point(boundary, Bias::Left);
3961
3962 (clipped, SelectionGoal::None)
3963 });
3964 });
3965
3966 let mut indent_edits = Vec::new();
3967 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
3968 for row in rows {
3969 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
3970 for (row, indent) in indents {
3971 if indent.len == 0 {
3972 continue;
3973 }
3974
3975 let text = match indent.kind {
3976 IndentKind::Space => " ".repeat(indent.len as usize),
3977 IndentKind::Tab => "\t".repeat(indent.len as usize),
3978 };
3979 let point = Point::new(row.0, 0);
3980 indent_edits.push((point..point, text));
3981 }
3982 }
3983 editor.edit(indent_edits, cx);
3984 });
3985 }
3986
3987 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
3988 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3989
3990 let buffer = self.buffer.read(cx);
3991 let snapshot = buffer.snapshot(cx);
3992
3993 let mut edits = Vec::new();
3994 let mut rows = Vec::new();
3995 let mut rows_inserted = 0;
3996
3997 for selection in self.selections.all_adjusted(cx) {
3998 let cursor = selection.head();
3999 let row = cursor.row;
4000
4001 let point = Point::new(row + 1, 0);
4002 let start_of_line = snapshot.clip_point(point, Bias::Left);
4003
4004 let newline = "\n".to_string();
4005 edits.push((start_of_line..start_of_line, newline));
4006
4007 rows_inserted += 1;
4008 rows.push(row + rows_inserted);
4009 }
4010
4011 self.transact(window, cx, |editor, window, cx| {
4012 editor.edit(edits, cx);
4013
4014 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4015 let mut index = 0;
4016 s.move_cursors_with(|map, _, _| {
4017 let row = rows[index];
4018 index += 1;
4019
4020 let point = Point::new(row, 0);
4021 let boundary = map.next_line_boundary(point).1;
4022 let clipped = map.clip_point(boundary, Bias::Left);
4023
4024 (clipped, SelectionGoal::None)
4025 });
4026 });
4027
4028 let mut indent_edits = Vec::new();
4029 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4030 for row in rows {
4031 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4032 for (row, indent) in indents {
4033 if indent.len == 0 {
4034 continue;
4035 }
4036
4037 let text = match indent.kind {
4038 IndentKind::Space => " ".repeat(indent.len as usize),
4039 IndentKind::Tab => "\t".repeat(indent.len as usize),
4040 };
4041 let point = Point::new(row.0, 0);
4042 indent_edits.push((point..point, text));
4043 }
4044 }
4045 editor.edit(indent_edits, cx);
4046 });
4047 }
4048
4049 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4050 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4051 original_indent_columns: Vec::new(),
4052 });
4053 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4054 }
4055
4056 fn insert_with_autoindent_mode(
4057 &mut self,
4058 text: &str,
4059 autoindent_mode: Option<AutoindentMode>,
4060 window: &mut Window,
4061 cx: &mut Context<Self>,
4062 ) {
4063 if self.read_only(cx) {
4064 return;
4065 }
4066
4067 let text: Arc<str> = text.into();
4068 self.transact(window, cx, |this, window, cx| {
4069 let old_selections = this.selections.all_adjusted(cx);
4070 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4071 let anchors = {
4072 let snapshot = buffer.read(cx);
4073 old_selections
4074 .iter()
4075 .map(|s| {
4076 let anchor = snapshot.anchor_after(s.head());
4077 s.map(|_| anchor)
4078 })
4079 .collect::<Vec<_>>()
4080 };
4081 buffer.edit(
4082 old_selections
4083 .iter()
4084 .map(|s| (s.start..s.end, text.clone())),
4085 autoindent_mode,
4086 cx,
4087 );
4088 anchors
4089 });
4090
4091 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4092 s.select_anchors(selection_anchors);
4093 });
4094
4095 cx.notify();
4096 });
4097 }
4098
4099 fn trigger_completion_on_input(
4100 &mut self,
4101 text: &str,
4102 trigger_in_words: bool,
4103 window: &mut Window,
4104 cx: &mut Context<Self>,
4105 ) {
4106 let ignore_completion_provider = self
4107 .context_menu
4108 .borrow()
4109 .as_ref()
4110 .map(|menu| match menu {
4111 CodeContextMenu::Completions(completions_menu) => {
4112 completions_menu.ignore_completion_provider
4113 }
4114 CodeContextMenu::CodeActions(_) => false,
4115 })
4116 .unwrap_or(false);
4117
4118 if ignore_completion_provider {
4119 self.show_word_completions(&ShowWordCompletions, window, cx);
4120 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4121 self.show_completions(
4122 &ShowCompletions {
4123 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4124 },
4125 window,
4126 cx,
4127 );
4128 } else {
4129 self.hide_context_menu(window, cx);
4130 }
4131 }
4132
4133 fn is_completion_trigger(
4134 &self,
4135 text: &str,
4136 trigger_in_words: bool,
4137 cx: &mut Context<Self>,
4138 ) -> bool {
4139 let position = self.selections.newest_anchor().head();
4140 let multibuffer = self.buffer.read(cx);
4141 let Some(buffer) = position
4142 .buffer_id
4143 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4144 else {
4145 return false;
4146 };
4147
4148 if let Some(completion_provider) = &self.completion_provider {
4149 completion_provider.is_completion_trigger(
4150 &buffer,
4151 position.text_anchor,
4152 text,
4153 trigger_in_words,
4154 cx,
4155 )
4156 } else {
4157 false
4158 }
4159 }
4160
4161 /// If any empty selections is touching the start of its innermost containing autoclose
4162 /// region, expand it to select the brackets.
4163 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4164 let selections = self.selections.all::<usize>(cx);
4165 let buffer = self.buffer.read(cx).read(cx);
4166 let new_selections = self
4167 .selections_with_autoclose_regions(selections, &buffer)
4168 .map(|(mut selection, region)| {
4169 if !selection.is_empty() {
4170 return selection;
4171 }
4172
4173 if let Some(region) = region {
4174 let mut range = region.range.to_offset(&buffer);
4175 if selection.start == range.start && range.start >= region.pair.start.len() {
4176 range.start -= region.pair.start.len();
4177 if buffer.contains_str_at(range.start, ®ion.pair.start)
4178 && buffer.contains_str_at(range.end, ®ion.pair.end)
4179 {
4180 range.end += region.pair.end.len();
4181 selection.start = range.start;
4182 selection.end = range.end;
4183
4184 return selection;
4185 }
4186 }
4187 }
4188
4189 let always_treat_brackets_as_autoclosed = buffer
4190 .language_settings_at(selection.start, cx)
4191 .always_treat_brackets_as_autoclosed;
4192
4193 if !always_treat_brackets_as_autoclosed {
4194 return selection;
4195 }
4196
4197 if let Some(scope) = buffer.language_scope_at(selection.start) {
4198 for (pair, enabled) in scope.brackets() {
4199 if !enabled || !pair.close {
4200 continue;
4201 }
4202
4203 if buffer.contains_str_at(selection.start, &pair.end) {
4204 let pair_start_len = pair.start.len();
4205 if buffer.contains_str_at(
4206 selection.start.saturating_sub(pair_start_len),
4207 &pair.start,
4208 ) {
4209 selection.start -= pair_start_len;
4210 selection.end += pair.end.len();
4211
4212 return selection;
4213 }
4214 }
4215 }
4216 }
4217
4218 selection
4219 })
4220 .collect();
4221
4222 drop(buffer);
4223 self.change_selections(None, window, cx, |selections| {
4224 selections.select(new_selections)
4225 });
4226 }
4227
4228 /// Iterate the given selections, and for each one, find the smallest surrounding
4229 /// autoclose region. This uses the ordering of the selections and the autoclose
4230 /// regions to avoid repeated comparisons.
4231 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4232 &'a self,
4233 selections: impl IntoIterator<Item = Selection<D>>,
4234 buffer: &'a MultiBufferSnapshot,
4235 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4236 let mut i = 0;
4237 let mut regions = self.autoclose_regions.as_slice();
4238 selections.into_iter().map(move |selection| {
4239 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4240
4241 let mut enclosing = None;
4242 while let Some(pair_state) = regions.get(i) {
4243 if pair_state.range.end.to_offset(buffer) < range.start {
4244 regions = ®ions[i + 1..];
4245 i = 0;
4246 } else if pair_state.range.start.to_offset(buffer) > range.end {
4247 break;
4248 } else {
4249 if pair_state.selection_id == selection.id {
4250 enclosing = Some(pair_state);
4251 }
4252 i += 1;
4253 }
4254 }
4255
4256 (selection, enclosing)
4257 })
4258 }
4259
4260 /// Remove any autoclose regions that no longer contain their selection.
4261 fn invalidate_autoclose_regions(
4262 &mut self,
4263 mut selections: &[Selection<Anchor>],
4264 buffer: &MultiBufferSnapshot,
4265 ) {
4266 self.autoclose_regions.retain(|state| {
4267 let mut i = 0;
4268 while let Some(selection) = selections.get(i) {
4269 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4270 selections = &selections[1..];
4271 continue;
4272 }
4273 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4274 break;
4275 }
4276 if selection.id == state.selection_id {
4277 return true;
4278 } else {
4279 i += 1;
4280 }
4281 }
4282 false
4283 });
4284 }
4285
4286 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4287 let offset = position.to_offset(buffer);
4288 let (word_range, kind) = buffer.surrounding_word(offset, true);
4289 if offset > word_range.start && kind == Some(CharKind::Word) {
4290 Some(
4291 buffer
4292 .text_for_range(word_range.start..offset)
4293 .collect::<String>(),
4294 )
4295 } else {
4296 None
4297 }
4298 }
4299
4300 pub fn toggle_inline_values(
4301 &mut self,
4302 _: &ToggleInlineValues,
4303 _: &mut Window,
4304 cx: &mut Context<Self>,
4305 ) {
4306 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4307
4308 self.refresh_inline_values(cx);
4309 }
4310
4311 pub fn toggle_inlay_hints(
4312 &mut self,
4313 _: &ToggleInlayHints,
4314 _: &mut Window,
4315 cx: &mut Context<Self>,
4316 ) {
4317 self.refresh_inlay_hints(
4318 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4319 cx,
4320 );
4321 }
4322
4323 pub fn inlay_hints_enabled(&self) -> bool {
4324 self.inlay_hint_cache.enabled
4325 }
4326
4327 pub fn inline_values_enabled(&self) -> bool {
4328 self.inline_value_cache.enabled
4329 }
4330
4331 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4332 if self.semantics_provider.is_none() || !self.mode.is_full() {
4333 return;
4334 }
4335
4336 let reason_description = reason.description();
4337 let ignore_debounce = matches!(
4338 reason,
4339 InlayHintRefreshReason::SettingsChange(_)
4340 | InlayHintRefreshReason::Toggle(_)
4341 | InlayHintRefreshReason::ExcerptsRemoved(_)
4342 | InlayHintRefreshReason::ModifiersChanged(_)
4343 );
4344 let (invalidate_cache, required_languages) = match reason {
4345 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4346 match self.inlay_hint_cache.modifiers_override(enabled) {
4347 Some(enabled) => {
4348 if enabled {
4349 (InvalidationStrategy::RefreshRequested, None)
4350 } else {
4351 self.splice_inlays(
4352 &self
4353 .visible_inlay_hints(cx)
4354 .iter()
4355 .map(|inlay| inlay.id)
4356 .collect::<Vec<InlayId>>(),
4357 Vec::new(),
4358 cx,
4359 );
4360 return;
4361 }
4362 }
4363 None => return,
4364 }
4365 }
4366 InlayHintRefreshReason::Toggle(enabled) => {
4367 if self.inlay_hint_cache.toggle(enabled) {
4368 if enabled {
4369 (InvalidationStrategy::RefreshRequested, None)
4370 } else {
4371 self.splice_inlays(
4372 &self
4373 .visible_inlay_hints(cx)
4374 .iter()
4375 .map(|inlay| inlay.id)
4376 .collect::<Vec<InlayId>>(),
4377 Vec::new(),
4378 cx,
4379 );
4380 return;
4381 }
4382 } else {
4383 return;
4384 }
4385 }
4386 InlayHintRefreshReason::SettingsChange(new_settings) => {
4387 match self.inlay_hint_cache.update_settings(
4388 &self.buffer,
4389 new_settings,
4390 self.visible_inlay_hints(cx),
4391 cx,
4392 ) {
4393 ControlFlow::Break(Some(InlaySplice {
4394 to_remove,
4395 to_insert,
4396 })) => {
4397 self.splice_inlays(&to_remove, to_insert, cx);
4398 return;
4399 }
4400 ControlFlow::Break(None) => return,
4401 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4402 }
4403 }
4404 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4405 if let Some(InlaySplice {
4406 to_remove,
4407 to_insert,
4408 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4409 {
4410 self.splice_inlays(&to_remove, to_insert, cx);
4411 }
4412 self.display_map.update(cx, |display_map, _| {
4413 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4414 });
4415 return;
4416 }
4417 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4418 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4419 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4420 }
4421 InlayHintRefreshReason::RefreshRequested => {
4422 (InvalidationStrategy::RefreshRequested, None)
4423 }
4424 };
4425
4426 if let Some(InlaySplice {
4427 to_remove,
4428 to_insert,
4429 }) = self.inlay_hint_cache.spawn_hint_refresh(
4430 reason_description,
4431 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4432 invalidate_cache,
4433 ignore_debounce,
4434 cx,
4435 ) {
4436 self.splice_inlays(&to_remove, to_insert, cx);
4437 }
4438 }
4439
4440 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4441 self.display_map
4442 .read(cx)
4443 .current_inlays()
4444 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4445 .cloned()
4446 .collect()
4447 }
4448
4449 pub fn excerpts_for_inlay_hints_query(
4450 &self,
4451 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4452 cx: &mut Context<Editor>,
4453 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4454 let Some(project) = self.project.as_ref() else {
4455 return HashMap::default();
4456 };
4457 let project = project.read(cx);
4458 let multi_buffer = self.buffer().read(cx);
4459 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4460 let multi_buffer_visible_start = self
4461 .scroll_manager
4462 .anchor()
4463 .anchor
4464 .to_point(&multi_buffer_snapshot);
4465 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4466 multi_buffer_visible_start
4467 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4468 Bias::Left,
4469 );
4470 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4471 multi_buffer_snapshot
4472 .range_to_buffer_ranges(multi_buffer_visible_range)
4473 .into_iter()
4474 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4475 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4476 let buffer_file = project::File::from_dyn(buffer.file())?;
4477 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4478 let worktree_entry = buffer_worktree
4479 .read(cx)
4480 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4481 if worktree_entry.is_ignored {
4482 return None;
4483 }
4484
4485 let language = buffer.language()?;
4486 if let Some(restrict_to_languages) = restrict_to_languages {
4487 if !restrict_to_languages.contains(language) {
4488 return None;
4489 }
4490 }
4491 Some((
4492 excerpt_id,
4493 (
4494 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4495 buffer.version().clone(),
4496 excerpt_visible_range,
4497 ),
4498 ))
4499 })
4500 .collect()
4501 }
4502
4503 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4504 TextLayoutDetails {
4505 text_system: window.text_system().clone(),
4506 editor_style: self.style.clone().unwrap(),
4507 rem_size: window.rem_size(),
4508 scroll_anchor: self.scroll_manager.anchor(),
4509 visible_rows: self.visible_line_count(),
4510 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4511 }
4512 }
4513
4514 pub fn splice_inlays(
4515 &self,
4516 to_remove: &[InlayId],
4517 to_insert: Vec<Inlay>,
4518 cx: &mut Context<Self>,
4519 ) {
4520 self.display_map.update(cx, |display_map, cx| {
4521 display_map.splice_inlays(to_remove, to_insert, cx)
4522 });
4523 cx.notify();
4524 }
4525
4526 fn trigger_on_type_formatting(
4527 &self,
4528 input: String,
4529 window: &mut Window,
4530 cx: &mut Context<Self>,
4531 ) -> Option<Task<Result<()>>> {
4532 if input.len() != 1 {
4533 return None;
4534 }
4535
4536 let project = self.project.as_ref()?;
4537 let position = self.selections.newest_anchor().head();
4538 let (buffer, buffer_position) = self
4539 .buffer
4540 .read(cx)
4541 .text_anchor_for_position(position, cx)?;
4542
4543 let settings = language_settings::language_settings(
4544 buffer
4545 .read(cx)
4546 .language_at(buffer_position)
4547 .map(|l| l.name()),
4548 buffer.read(cx).file(),
4549 cx,
4550 );
4551 if !settings.use_on_type_format {
4552 return None;
4553 }
4554
4555 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4556 // hence we do LSP request & edit on host side only — add formats to host's history.
4557 let push_to_lsp_host_history = true;
4558 // If this is not the host, append its history with new edits.
4559 let push_to_client_history = project.read(cx).is_via_collab();
4560
4561 let on_type_formatting = project.update(cx, |project, cx| {
4562 project.on_type_format(
4563 buffer.clone(),
4564 buffer_position,
4565 input,
4566 push_to_lsp_host_history,
4567 cx,
4568 )
4569 });
4570 Some(cx.spawn_in(window, async move |editor, cx| {
4571 if let Some(transaction) = on_type_formatting.await? {
4572 if push_to_client_history {
4573 buffer
4574 .update(cx, |buffer, _| {
4575 buffer.push_transaction(transaction, Instant::now());
4576 buffer.finalize_last_transaction();
4577 })
4578 .ok();
4579 }
4580 editor.update(cx, |editor, cx| {
4581 editor.refresh_document_highlights(cx);
4582 })?;
4583 }
4584 Ok(())
4585 }))
4586 }
4587
4588 pub fn show_word_completions(
4589 &mut self,
4590 _: &ShowWordCompletions,
4591 window: &mut Window,
4592 cx: &mut Context<Self>,
4593 ) {
4594 self.open_completions_menu(true, None, window, cx);
4595 }
4596
4597 pub fn show_completions(
4598 &mut self,
4599 options: &ShowCompletions,
4600 window: &mut Window,
4601 cx: &mut Context<Self>,
4602 ) {
4603 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4604 }
4605
4606 fn open_completions_menu(
4607 &mut self,
4608 ignore_completion_provider: bool,
4609 trigger: Option<&str>,
4610 window: &mut Window,
4611 cx: &mut Context<Self>,
4612 ) {
4613 if self.pending_rename.is_some() {
4614 return;
4615 }
4616 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4617 return;
4618 }
4619
4620 let position = self.selections.newest_anchor().head();
4621 if position.diff_base_anchor.is_some() {
4622 return;
4623 }
4624 let (buffer, buffer_position) =
4625 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4626 output
4627 } else {
4628 return;
4629 };
4630 let buffer_snapshot = buffer.read(cx).snapshot();
4631 let show_completion_documentation = buffer_snapshot
4632 .settings_at(buffer_position, cx)
4633 .show_completion_documentation;
4634
4635 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4636
4637 let trigger_kind = match trigger {
4638 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4639 CompletionTriggerKind::TRIGGER_CHARACTER
4640 }
4641 _ => CompletionTriggerKind::INVOKED,
4642 };
4643 let completion_context = CompletionContext {
4644 trigger_character: trigger.and_then(|trigger| {
4645 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4646 Some(String::from(trigger))
4647 } else {
4648 None
4649 }
4650 }),
4651 trigger_kind,
4652 };
4653
4654 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4655 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4656 let word_to_exclude = buffer_snapshot
4657 .text_for_range(old_range.clone())
4658 .collect::<String>();
4659 (
4660 buffer_snapshot.anchor_before(old_range.start)
4661 ..buffer_snapshot.anchor_after(old_range.end),
4662 Some(word_to_exclude),
4663 )
4664 } else {
4665 (buffer_position..buffer_position, None)
4666 };
4667
4668 let completion_settings = language_settings(
4669 buffer_snapshot
4670 .language_at(buffer_position)
4671 .map(|language| language.name()),
4672 buffer_snapshot.file(),
4673 cx,
4674 )
4675 .completions;
4676
4677 // The document can be large, so stay in reasonable bounds when searching for words,
4678 // otherwise completion pop-up might be slow to appear.
4679 const WORD_LOOKUP_ROWS: u32 = 5_000;
4680 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4681 let min_word_search = buffer_snapshot.clip_point(
4682 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4683 Bias::Left,
4684 );
4685 let max_word_search = buffer_snapshot.clip_point(
4686 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4687 Bias::Right,
4688 );
4689 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4690 ..buffer_snapshot.point_to_offset(max_word_search);
4691
4692 let provider = self
4693 .completion_provider
4694 .as_ref()
4695 .filter(|_| !ignore_completion_provider);
4696 let skip_digits = query
4697 .as_ref()
4698 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4699
4700 let (mut words, provided_completions) = match provider {
4701 Some(provider) => {
4702 let completions = provider.completions(
4703 position.excerpt_id,
4704 &buffer,
4705 buffer_position,
4706 completion_context,
4707 window,
4708 cx,
4709 );
4710
4711 let words = match completion_settings.words {
4712 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4713 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4714 .background_spawn(async move {
4715 buffer_snapshot.words_in_range(WordsQuery {
4716 fuzzy_contents: None,
4717 range: word_search_range,
4718 skip_digits,
4719 })
4720 }),
4721 };
4722
4723 (words, completions)
4724 }
4725 None => (
4726 cx.background_spawn(async move {
4727 buffer_snapshot.words_in_range(WordsQuery {
4728 fuzzy_contents: None,
4729 range: word_search_range,
4730 skip_digits,
4731 })
4732 }),
4733 Task::ready(Ok(None)),
4734 ),
4735 };
4736
4737 let sort_completions = provider
4738 .as_ref()
4739 .map_or(false, |provider| provider.sort_completions());
4740
4741 let filter_completions = provider
4742 .as_ref()
4743 .map_or(true, |provider| provider.filter_completions());
4744
4745 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
4746
4747 let id = post_inc(&mut self.next_completion_id);
4748 let task = cx.spawn_in(window, async move |editor, cx| {
4749 async move {
4750 editor.update(cx, |this, _| {
4751 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
4752 })?;
4753
4754 let mut completions = Vec::new();
4755 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
4756 completions.extend(provided_completions);
4757 if completion_settings.words == WordsCompletionMode::Fallback {
4758 words = Task::ready(BTreeMap::default());
4759 }
4760 }
4761
4762 let mut words = words.await;
4763 if let Some(word_to_exclude) = &word_to_exclude {
4764 words.remove(word_to_exclude);
4765 }
4766 for lsp_completion in &completions {
4767 words.remove(&lsp_completion.new_text);
4768 }
4769 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
4770 replace_range: old_range.clone(),
4771 new_text: word.clone(),
4772 label: CodeLabel::plain(word, None),
4773 icon_path: None,
4774 documentation: None,
4775 source: CompletionSource::BufferWord {
4776 word_range,
4777 resolved: false,
4778 },
4779 insert_text_mode: Some(InsertTextMode::AS_IS),
4780 confirm: None,
4781 }));
4782
4783 let menu = if completions.is_empty() {
4784 None
4785 } else {
4786 let mut menu = CompletionsMenu::new(
4787 id,
4788 sort_completions,
4789 show_completion_documentation,
4790 ignore_completion_provider,
4791 position,
4792 buffer.clone(),
4793 completions.into(),
4794 snippet_sort_order,
4795 );
4796
4797 menu.filter(
4798 if filter_completions {
4799 query.as_deref()
4800 } else {
4801 None
4802 },
4803 cx.background_executor().clone(),
4804 )
4805 .await;
4806
4807 menu.visible().then_some(menu)
4808 };
4809
4810 editor.update_in(cx, |editor, window, cx| {
4811 match editor.context_menu.borrow().as_ref() {
4812 None => {}
4813 Some(CodeContextMenu::Completions(prev_menu)) => {
4814 if prev_menu.id > id {
4815 return;
4816 }
4817 }
4818 _ => return,
4819 }
4820
4821 if editor.focus_handle.is_focused(window) && menu.is_some() {
4822 let mut menu = menu.unwrap();
4823 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
4824
4825 *editor.context_menu.borrow_mut() =
4826 Some(CodeContextMenu::Completions(menu));
4827
4828 if editor.show_edit_predictions_in_menu() {
4829 editor.update_visible_inline_completion(window, cx);
4830 } else {
4831 editor.discard_inline_completion(false, cx);
4832 }
4833
4834 cx.notify();
4835 } else if editor.completion_tasks.len() <= 1 {
4836 // If there are no more completion tasks and the last menu was
4837 // empty, we should hide it.
4838 let was_hidden = editor.hide_context_menu(window, cx).is_none();
4839 // If it was already hidden and we don't show inline
4840 // completions in the menu, we should also show the
4841 // inline-completion when available.
4842 if was_hidden && editor.show_edit_predictions_in_menu() {
4843 editor.update_visible_inline_completion(window, cx);
4844 }
4845 }
4846 })?;
4847
4848 anyhow::Ok(())
4849 }
4850 .log_err()
4851 .await
4852 });
4853
4854 self.completion_tasks.push((id, task));
4855 }
4856
4857 #[cfg(feature = "test-support")]
4858 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
4859 let menu = self.context_menu.borrow();
4860 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
4861 let completions = menu.completions.borrow();
4862 Some(completions.to_vec())
4863 } else {
4864 None
4865 }
4866 }
4867
4868 pub fn confirm_completion(
4869 &mut self,
4870 action: &ConfirmCompletion,
4871 window: &mut Window,
4872 cx: &mut Context<Self>,
4873 ) -> Option<Task<Result<()>>> {
4874 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4875 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
4876 }
4877
4878 pub fn confirm_completion_insert(
4879 &mut self,
4880 _: &ConfirmCompletionInsert,
4881 window: &mut Window,
4882 cx: &mut Context<Self>,
4883 ) -> Option<Task<Result<()>>> {
4884 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4885 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
4886 }
4887
4888 pub fn confirm_completion_replace(
4889 &mut self,
4890 _: &ConfirmCompletionReplace,
4891 window: &mut Window,
4892 cx: &mut Context<Self>,
4893 ) -> Option<Task<Result<()>>> {
4894 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4895 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
4896 }
4897
4898 pub fn compose_completion(
4899 &mut self,
4900 action: &ComposeCompletion,
4901 window: &mut Window,
4902 cx: &mut Context<Self>,
4903 ) -> Option<Task<Result<()>>> {
4904 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4905 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
4906 }
4907
4908 fn do_completion(
4909 &mut self,
4910 item_ix: Option<usize>,
4911 intent: CompletionIntent,
4912 window: &mut Window,
4913 cx: &mut Context<Editor>,
4914 ) -> Option<Task<Result<()>>> {
4915 use language::ToOffset as _;
4916
4917 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
4918 else {
4919 return None;
4920 };
4921
4922 let candidate_id = {
4923 let entries = completions_menu.entries.borrow();
4924 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
4925 if self.show_edit_predictions_in_menu() {
4926 self.discard_inline_completion(true, cx);
4927 }
4928 mat.candidate_id
4929 };
4930
4931 let buffer_handle = completions_menu.buffer;
4932 let completion = completions_menu
4933 .completions
4934 .borrow()
4935 .get(candidate_id)?
4936 .clone();
4937 cx.stop_propagation();
4938
4939 let snippet;
4940 let new_text;
4941 if completion.is_snippet() {
4942 snippet = Some(Snippet::parse(&completion.new_text).log_err()?);
4943 new_text = snippet.as_ref().unwrap().text.clone();
4944 } else {
4945 snippet = None;
4946 new_text = completion.new_text.clone();
4947 };
4948
4949 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
4950 let buffer = buffer_handle.read(cx);
4951 let snapshot = self.buffer.read(cx).snapshot(cx);
4952 let replace_range_multibuffer = {
4953 let excerpt = snapshot
4954 .excerpt_containing(self.selections.newest_anchor().range())
4955 .unwrap();
4956 let multibuffer_anchor = snapshot
4957 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
4958 .unwrap()
4959 ..snapshot
4960 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
4961 .unwrap();
4962 multibuffer_anchor.start.to_offset(&snapshot)
4963 ..multibuffer_anchor.end.to_offset(&snapshot)
4964 };
4965 let newest_anchor = self.selections.newest_anchor();
4966 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
4967 return None;
4968 }
4969
4970 let old_text = buffer
4971 .text_for_range(replace_range.clone())
4972 .collect::<String>();
4973 let lookbehind = newest_anchor
4974 .start
4975 .text_anchor
4976 .to_offset(buffer)
4977 .saturating_sub(replace_range.start);
4978 let lookahead = replace_range
4979 .end
4980 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
4981 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
4982 let suffix = &old_text[lookbehind.min(old_text.len())..];
4983
4984 let selections = self.selections.all::<usize>(cx);
4985 let mut ranges = Vec::new();
4986 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4987
4988 for selection in &selections {
4989 let range = if selection.id == newest_anchor.id {
4990 replace_range_multibuffer.clone()
4991 } else {
4992 let mut range = selection.range();
4993
4994 // if prefix is present, don't duplicate it
4995 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
4996 range.start = range.start.saturating_sub(lookbehind);
4997
4998 // if suffix is also present, mimic the newest cursor and replace it
4999 if selection.id != newest_anchor.id
5000 && snapshot.contains_str_at(range.end, suffix)
5001 {
5002 range.end += lookahead;
5003 }
5004 }
5005 range
5006 };
5007
5008 ranges.push(range.clone());
5009
5010 if !self.linked_edit_ranges.is_empty() {
5011 let start_anchor = snapshot.anchor_before(range.start);
5012 let end_anchor = snapshot.anchor_after(range.end);
5013 if let Some(ranges) = self
5014 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5015 {
5016 for (buffer, edits) in ranges {
5017 linked_edits
5018 .entry(buffer.clone())
5019 .or_default()
5020 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5021 }
5022 }
5023 }
5024 }
5025
5026 cx.emit(EditorEvent::InputHandled {
5027 utf16_range_to_replace: None,
5028 text: new_text.clone().into(),
5029 });
5030
5031 self.transact(window, cx, |this, window, cx| {
5032 if let Some(mut snippet) = snippet {
5033 snippet.text = new_text.to_string();
5034 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5035 } else {
5036 this.buffer.update(cx, |buffer, cx| {
5037 let auto_indent = match completion.insert_text_mode {
5038 Some(InsertTextMode::AS_IS) => None,
5039 _ => this.autoindent_mode.clone(),
5040 };
5041 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5042 buffer.edit(edits, auto_indent, cx);
5043 });
5044 }
5045 for (buffer, edits) in linked_edits {
5046 buffer.update(cx, |buffer, cx| {
5047 let snapshot = buffer.snapshot();
5048 let edits = edits
5049 .into_iter()
5050 .map(|(range, text)| {
5051 use text::ToPoint as TP;
5052 let end_point = TP::to_point(&range.end, &snapshot);
5053 let start_point = TP::to_point(&range.start, &snapshot);
5054 (start_point..end_point, text)
5055 })
5056 .sorted_by_key(|(range, _)| range.start);
5057 buffer.edit(edits, None, cx);
5058 })
5059 }
5060
5061 this.refresh_inline_completion(true, false, window, cx);
5062 });
5063
5064 let show_new_completions_on_confirm = completion
5065 .confirm
5066 .as_ref()
5067 .map_or(false, |confirm| confirm(intent, window, cx));
5068 if show_new_completions_on_confirm {
5069 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5070 }
5071
5072 let provider = self.completion_provider.as_ref()?;
5073 drop(completion);
5074 let apply_edits = provider.apply_additional_edits_for_completion(
5075 buffer_handle,
5076 completions_menu.completions.clone(),
5077 candidate_id,
5078 true,
5079 cx,
5080 );
5081
5082 let editor_settings = EditorSettings::get_global(cx);
5083 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5084 // After the code completion is finished, users often want to know what signatures are needed.
5085 // so we should automatically call signature_help
5086 self.show_signature_help(&ShowSignatureHelp, window, cx);
5087 }
5088
5089 Some(cx.foreground_executor().spawn(async move {
5090 apply_edits.await?;
5091 Ok(())
5092 }))
5093 }
5094
5095 pub fn toggle_code_actions(
5096 &mut self,
5097 action: &ToggleCodeActions,
5098 window: &mut Window,
5099 cx: &mut Context<Self>,
5100 ) {
5101 let quick_launch = action.quick_launch;
5102 let mut context_menu = self.context_menu.borrow_mut();
5103 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5104 if code_actions.deployed_from_indicator == action.deployed_from_indicator {
5105 // Toggle if we're selecting the same one
5106 *context_menu = None;
5107 cx.notify();
5108 return;
5109 } else {
5110 // Otherwise, clear it and start a new one
5111 *context_menu = None;
5112 cx.notify();
5113 }
5114 }
5115 drop(context_menu);
5116 let snapshot = self.snapshot(window, cx);
5117 let deployed_from_indicator = action.deployed_from_indicator;
5118 let mut task = self.code_actions_task.take();
5119 let action = action.clone();
5120 cx.spawn_in(window, async move |editor, cx| {
5121 while let Some(prev_task) = task {
5122 prev_task.await.log_err();
5123 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5124 }
5125
5126 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5127 if editor.focus_handle.is_focused(window) {
5128 let multibuffer_point = action
5129 .deployed_from_indicator
5130 .map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
5131 .unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
5132 let (buffer, buffer_row) = snapshot
5133 .buffer_snapshot
5134 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5135 .and_then(|(buffer_snapshot, range)| {
5136 editor
5137 .buffer
5138 .read(cx)
5139 .buffer(buffer_snapshot.remote_id())
5140 .map(|buffer| (buffer, range.start.row))
5141 })?;
5142 let (_, code_actions) = editor
5143 .available_code_actions
5144 .clone()
5145 .and_then(|(location, code_actions)| {
5146 let snapshot = location.buffer.read(cx).snapshot();
5147 let point_range = location.range.to_point(&snapshot);
5148 let point_range = point_range.start.row..=point_range.end.row;
5149 if point_range.contains(&buffer_row) {
5150 Some((location, code_actions))
5151 } else {
5152 None
5153 }
5154 })
5155 .unzip();
5156 let buffer_id = buffer.read(cx).remote_id();
5157 let tasks = editor
5158 .tasks
5159 .get(&(buffer_id, buffer_row))
5160 .map(|t| Arc::new(t.to_owned()));
5161 if tasks.is_none() && code_actions.is_none() {
5162 return None;
5163 }
5164
5165 editor.completion_tasks.clear();
5166 editor.discard_inline_completion(false, cx);
5167 let task_context =
5168 tasks
5169 .as_ref()
5170 .zip(editor.project.clone())
5171 .map(|(tasks, project)| {
5172 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5173 });
5174
5175 Some(cx.spawn_in(window, async move |editor, cx| {
5176 let task_context = match task_context {
5177 Some(task_context) => task_context.await,
5178 None => None,
5179 };
5180 let resolved_tasks =
5181 tasks
5182 .zip(task_context.clone())
5183 .map(|(tasks, task_context)| ResolvedTasks {
5184 templates: tasks.resolve(&task_context).collect(),
5185 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5186 multibuffer_point.row,
5187 tasks.column,
5188 )),
5189 });
5190 let spawn_straight_away = quick_launch
5191 && resolved_tasks
5192 .as_ref()
5193 .map_or(false, |tasks| tasks.templates.len() == 1)
5194 && code_actions
5195 .as_ref()
5196 .map_or(true, |actions| actions.is_empty());
5197 let debug_scenarios = editor.update(cx, |editor, cx| {
5198 if cx.has_flag::<DebuggerFeatureFlag>() {
5199 maybe!({
5200 let project = editor.project.as_ref()?;
5201 let dap_store = project.read(cx).dap_store();
5202 let mut scenarios = vec![];
5203 let resolved_tasks = resolved_tasks.as_ref()?;
5204 let debug_adapter: SharedString = buffer
5205 .read(cx)
5206 .language()?
5207 .context_provider()?
5208 .debug_adapter()?
5209 .into();
5210 dap_store.update(cx, |this, cx| {
5211 for (_, task) in &resolved_tasks.templates {
5212 if let Some(scenario) = this
5213 .debug_scenario_for_build_task(
5214 task.resolved.clone(),
5215 SharedString::from(
5216 task.original_task().label.clone(),
5217 ),
5218 debug_adapter.clone(),
5219 cx,
5220 )
5221 {
5222 scenarios.push(scenario);
5223 }
5224 }
5225 });
5226 Some(scenarios)
5227 })
5228 .unwrap_or_default()
5229 } else {
5230 vec![]
5231 }
5232 })?;
5233 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5234 *editor.context_menu.borrow_mut() =
5235 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5236 buffer,
5237 actions: CodeActionContents::new(
5238 resolved_tasks,
5239 code_actions,
5240 debug_scenarios,
5241 task_context.unwrap_or_default(),
5242 ),
5243 selected_item: Default::default(),
5244 scroll_handle: UniformListScrollHandle::default(),
5245 deployed_from_indicator,
5246 }));
5247 if spawn_straight_away {
5248 if let Some(task) = editor.confirm_code_action(
5249 &ConfirmCodeAction { item_ix: Some(0) },
5250 window,
5251 cx,
5252 ) {
5253 cx.notify();
5254 return task;
5255 }
5256 }
5257 cx.notify();
5258 Task::ready(Ok(()))
5259 }) {
5260 task.await
5261 } else {
5262 Ok(())
5263 }
5264 }))
5265 } else {
5266 Some(Task::ready(Ok(())))
5267 }
5268 })?;
5269 if let Some(task) = spawned_test_task {
5270 task.await?;
5271 }
5272
5273 Ok::<_, anyhow::Error>(())
5274 })
5275 .detach_and_log_err(cx);
5276 }
5277
5278 pub fn confirm_code_action(
5279 &mut self,
5280 action: &ConfirmCodeAction,
5281 window: &mut Window,
5282 cx: &mut Context<Self>,
5283 ) -> Option<Task<Result<()>>> {
5284 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5285
5286 let actions_menu =
5287 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5288 menu
5289 } else {
5290 return None;
5291 };
5292
5293 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5294 let action = actions_menu.actions.get(action_ix)?;
5295 let title = action.label();
5296 let buffer = actions_menu.buffer;
5297 let workspace = self.workspace()?;
5298
5299 match action {
5300 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5301 workspace.update(cx, |workspace, cx| {
5302 workspace.schedule_resolved_task(
5303 task_source_kind,
5304 resolved_task,
5305 false,
5306 window,
5307 cx,
5308 );
5309
5310 Some(Task::ready(Ok(())))
5311 })
5312 }
5313 CodeActionsItem::CodeAction {
5314 excerpt_id,
5315 action,
5316 provider,
5317 } => {
5318 let apply_code_action =
5319 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5320 let workspace = workspace.downgrade();
5321 Some(cx.spawn_in(window, async move |editor, cx| {
5322 let project_transaction = apply_code_action.await?;
5323 Self::open_project_transaction(
5324 &editor,
5325 workspace,
5326 project_transaction,
5327 title,
5328 cx,
5329 )
5330 .await
5331 }))
5332 }
5333 CodeActionsItem::DebugScenario(scenario) => {
5334 let context = actions_menu.actions.context.clone();
5335
5336 workspace.update(cx, |workspace, cx| {
5337 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5338 });
5339 Some(Task::ready(Ok(())))
5340 }
5341 }
5342 }
5343
5344 pub async fn open_project_transaction(
5345 this: &WeakEntity<Editor>,
5346 workspace: WeakEntity<Workspace>,
5347 transaction: ProjectTransaction,
5348 title: String,
5349 cx: &mut AsyncWindowContext,
5350 ) -> Result<()> {
5351 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5352 cx.update(|_, cx| {
5353 entries.sort_unstable_by_key(|(buffer, _)| {
5354 buffer.read(cx).file().map(|f| f.path().clone())
5355 });
5356 })?;
5357
5358 // If the project transaction's edits are all contained within this editor, then
5359 // avoid opening a new editor to display them.
5360
5361 if let Some((buffer, transaction)) = entries.first() {
5362 if entries.len() == 1 {
5363 let excerpt = this.update(cx, |editor, cx| {
5364 editor
5365 .buffer()
5366 .read(cx)
5367 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5368 })?;
5369 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5370 if excerpted_buffer == *buffer {
5371 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5372 let excerpt_range = excerpt_range.to_offset(buffer);
5373 buffer
5374 .edited_ranges_for_transaction::<usize>(transaction)
5375 .all(|range| {
5376 excerpt_range.start <= range.start
5377 && excerpt_range.end >= range.end
5378 })
5379 })?;
5380
5381 if all_edits_within_excerpt {
5382 return Ok(());
5383 }
5384 }
5385 }
5386 }
5387 } else {
5388 return Ok(());
5389 }
5390
5391 let mut ranges_to_highlight = Vec::new();
5392 let excerpt_buffer = cx.new(|cx| {
5393 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5394 for (buffer_handle, transaction) in &entries {
5395 let edited_ranges = buffer_handle
5396 .read(cx)
5397 .edited_ranges_for_transaction::<Point>(transaction)
5398 .collect::<Vec<_>>();
5399 let (ranges, _) = multibuffer.set_excerpts_for_path(
5400 PathKey::for_buffer(buffer_handle, cx),
5401 buffer_handle.clone(),
5402 edited_ranges,
5403 DEFAULT_MULTIBUFFER_CONTEXT,
5404 cx,
5405 );
5406
5407 ranges_to_highlight.extend(ranges);
5408 }
5409 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5410 multibuffer
5411 })?;
5412
5413 workspace.update_in(cx, |workspace, window, cx| {
5414 let project = workspace.project().clone();
5415 let editor =
5416 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5417 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5418 editor.update(cx, |editor, cx| {
5419 editor.highlight_background::<Self>(
5420 &ranges_to_highlight,
5421 |theme| theme.editor_highlighted_line_background,
5422 cx,
5423 );
5424 });
5425 })?;
5426
5427 Ok(())
5428 }
5429
5430 pub fn clear_code_action_providers(&mut self) {
5431 self.code_action_providers.clear();
5432 self.available_code_actions.take();
5433 }
5434
5435 pub fn add_code_action_provider(
5436 &mut self,
5437 provider: Rc<dyn CodeActionProvider>,
5438 window: &mut Window,
5439 cx: &mut Context<Self>,
5440 ) {
5441 if self
5442 .code_action_providers
5443 .iter()
5444 .any(|existing_provider| existing_provider.id() == provider.id())
5445 {
5446 return;
5447 }
5448
5449 self.code_action_providers.push(provider);
5450 self.refresh_code_actions(window, cx);
5451 }
5452
5453 pub fn remove_code_action_provider(
5454 &mut self,
5455 id: Arc<str>,
5456 window: &mut Window,
5457 cx: &mut Context<Self>,
5458 ) {
5459 self.code_action_providers
5460 .retain(|provider| provider.id() != id);
5461 self.refresh_code_actions(window, cx);
5462 }
5463
5464 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5465 let newest_selection = self.selections.newest_anchor().clone();
5466 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5467 let buffer = self.buffer.read(cx);
5468 if newest_selection.head().diff_base_anchor.is_some() {
5469 return None;
5470 }
5471 let (start_buffer, start) =
5472 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5473 let (end_buffer, end) =
5474 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5475 if start_buffer != end_buffer {
5476 return None;
5477 }
5478
5479 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5480 cx.background_executor()
5481 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5482 .await;
5483
5484 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5485 let providers = this.code_action_providers.clone();
5486 let tasks = this
5487 .code_action_providers
5488 .iter()
5489 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5490 .collect::<Vec<_>>();
5491 (providers, tasks)
5492 })?;
5493
5494 let mut actions = Vec::new();
5495 for (provider, provider_actions) in
5496 providers.into_iter().zip(future::join_all(tasks).await)
5497 {
5498 if let Some(provider_actions) = provider_actions.log_err() {
5499 actions.extend(provider_actions.into_iter().map(|action| {
5500 AvailableCodeAction {
5501 excerpt_id: newest_selection.start.excerpt_id,
5502 action,
5503 provider: provider.clone(),
5504 }
5505 }));
5506 }
5507 }
5508
5509 this.update(cx, |this, cx| {
5510 this.available_code_actions = if actions.is_empty() {
5511 None
5512 } else {
5513 Some((
5514 Location {
5515 buffer: start_buffer,
5516 range: start..end,
5517 },
5518 actions.into(),
5519 ))
5520 };
5521 cx.notify();
5522 })
5523 }));
5524 None
5525 }
5526
5527 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5528 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5529 self.show_git_blame_inline = false;
5530
5531 self.show_git_blame_inline_delay_task =
5532 Some(cx.spawn_in(window, async move |this, cx| {
5533 cx.background_executor().timer(delay).await;
5534
5535 this.update(cx, |this, cx| {
5536 this.show_git_blame_inline = true;
5537 cx.notify();
5538 })
5539 .log_err();
5540 }));
5541 }
5542 }
5543
5544 fn show_blame_popover(
5545 &mut self,
5546 blame_entry: &BlameEntry,
5547 position: gpui::Point<Pixels>,
5548 cx: &mut Context<Self>,
5549 ) {
5550 if let Some(state) = &mut self.inline_blame_popover {
5551 state.hide_task.take();
5552 cx.notify();
5553 } else {
5554 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5555 let show_task = cx.spawn(async move |editor, cx| {
5556 cx.background_executor()
5557 .timer(std::time::Duration::from_millis(delay))
5558 .await;
5559 editor
5560 .update(cx, |editor, cx| {
5561 if let Some(state) = &mut editor.inline_blame_popover {
5562 state.show_task = None;
5563 cx.notify();
5564 }
5565 })
5566 .ok();
5567 });
5568 let Some(blame) = self.blame.as_ref() else {
5569 return;
5570 };
5571 let blame = blame.read(cx);
5572 let details = blame.details_for_entry(&blame_entry);
5573 let markdown = cx.new(|cx| {
5574 Markdown::new(
5575 details
5576 .as_ref()
5577 .map(|message| message.message.clone())
5578 .unwrap_or_default(),
5579 None,
5580 None,
5581 cx,
5582 )
5583 });
5584 self.inline_blame_popover = Some(InlineBlamePopover {
5585 position,
5586 show_task: Some(show_task),
5587 hide_task: None,
5588 popover_bounds: None,
5589 popover_state: InlineBlamePopoverState {
5590 scroll_handle: ScrollHandle::new(),
5591 commit_message: details,
5592 markdown,
5593 },
5594 });
5595 }
5596 }
5597
5598 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5599 if let Some(state) = &mut self.inline_blame_popover {
5600 if state.show_task.is_some() {
5601 self.inline_blame_popover.take();
5602 cx.notify();
5603 } else {
5604 let hide_task = cx.spawn(async move |editor, cx| {
5605 cx.background_executor()
5606 .timer(std::time::Duration::from_millis(100))
5607 .await;
5608 editor
5609 .update(cx, |editor, cx| {
5610 editor.inline_blame_popover.take();
5611 cx.notify();
5612 })
5613 .ok();
5614 });
5615 state.hide_task = Some(hide_task);
5616 }
5617 }
5618 }
5619
5620 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5621 if self.pending_rename.is_some() {
5622 return None;
5623 }
5624
5625 let provider = self.semantics_provider.clone()?;
5626 let buffer = self.buffer.read(cx);
5627 let newest_selection = self.selections.newest_anchor().clone();
5628 let cursor_position = newest_selection.head();
5629 let (cursor_buffer, cursor_buffer_position) =
5630 buffer.text_anchor_for_position(cursor_position, cx)?;
5631 let (tail_buffer, _) = buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5632 if cursor_buffer != tail_buffer {
5633 return None;
5634 }
5635 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
5636 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
5637 cx.background_executor()
5638 .timer(Duration::from_millis(debounce))
5639 .await;
5640
5641 let highlights = if let Some(highlights) = cx
5642 .update(|cx| {
5643 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
5644 })
5645 .ok()
5646 .flatten()
5647 {
5648 highlights.await.log_err()
5649 } else {
5650 None
5651 };
5652
5653 if let Some(highlights) = highlights {
5654 this.update(cx, |this, cx| {
5655 if this.pending_rename.is_some() {
5656 return;
5657 }
5658
5659 let buffer_id = cursor_position.buffer_id;
5660 let buffer = this.buffer.read(cx);
5661 if !buffer
5662 .text_anchor_for_position(cursor_position, cx)
5663 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
5664 {
5665 return;
5666 }
5667
5668 let cursor_buffer_snapshot = cursor_buffer.read(cx);
5669 let mut write_ranges = Vec::new();
5670 let mut read_ranges = Vec::new();
5671 for highlight in highlights {
5672 for (excerpt_id, excerpt_range) in
5673 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
5674 {
5675 let start = highlight
5676 .range
5677 .start
5678 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
5679 let end = highlight
5680 .range
5681 .end
5682 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
5683 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
5684 continue;
5685 }
5686
5687 let range = Anchor {
5688 buffer_id,
5689 excerpt_id,
5690 text_anchor: start,
5691 diff_base_anchor: None,
5692 }..Anchor {
5693 buffer_id,
5694 excerpt_id,
5695 text_anchor: end,
5696 diff_base_anchor: None,
5697 };
5698 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
5699 write_ranges.push(range);
5700 } else {
5701 read_ranges.push(range);
5702 }
5703 }
5704 }
5705
5706 this.highlight_background::<DocumentHighlightRead>(
5707 &read_ranges,
5708 |theme| theme.editor_document_highlight_read_background,
5709 cx,
5710 );
5711 this.highlight_background::<DocumentHighlightWrite>(
5712 &write_ranges,
5713 |theme| theme.editor_document_highlight_write_background,
5714 cx,
5715 );
5716 cx.notify();
5717 })
5718 .log_err();
5719 }
5720 }));
5721 None
5722 }
5723
5724 fn prepare_highlight_query_from_selection(
5725 &mut self,
5726 cx: &mut Context<Editor>,
5727 ) -> Option<(String, Range<Anchor>)> {
5728 if matches!(self.mode, EditorMode::SingleLine { .. }) {
5729 return None;
5730 }
5731 if !EditorSettings::get_global(cx).selection_highlight {
5732 return None;
5733 }
5734 if self.selections.count() != 1 || self.selections.line_mode {
5735 return None;
5736 }
5737 let selection = self.selections.newest::<Point>(cx);
5738 if selection.is_empty() || selection.start.row != selection.end.row {
5739 return None;
5740 }
5741 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5742 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
5743 let query = multi_buffer_snapshot
5744 .text_for_range(selection_anchor_range.clone())
5745 .collect::<String>();
5746 if query.trim().is_empty() {
5747 return None;
5748 }
5749 Some((query, selection_anchor_range))
5750 }
5751
5752 fn update_selection_occurrence_highlights(
5753 &mut self,
5754 query_text: String,
5755 query_range: Range<Anchor>,
5756 multi_buffer_range_to_query: Range<Point>,
5757 use_debounce: bool,
5758 window: &mut Window,
5759 cx: &mut Context<Editor>,
5760 ) -> Task<()> {
5761 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5762 cx.spawn_in(window, async move |editor, cx| {
5763 if use_debounce {
5764 cx.background_executor()
5765 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
5766 .await;
5767 }
5768 let match_task = cx.background_spawn(async move {
5769 let buffer_ranges = multi_buffer_snapshot
5770 .range_to_buffer_ranges(multi_buffer_range_to_query)
5771 .into_iter()
5772 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
5773 let mut match_ranges = Vec::new();
5774 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
5775 match_ranges.extend(
5776 project::search::SearchQuery::text(
5777 query_text.clone(),
5778 false,
5779 false,
5780 false,
5781 Default::default(),
5782 Default::default(),
5783 false,
5784 None,
5785 )
5786 .unwrap()
5787 .search(&buffer_snapshot, Some(search_range.clone()))
5788 .await
5789 .into_iter()
5790 .filter_map(|match_range| {
5791 let match_start = buffer_snapshot
5792 .anchor_after(search_range.start + match_range.start);
5793 let match_end =
5794 buffer_snapshot.anchor_before(search_range.start + match_range.end);
5795 let match_anchor_range = Anchor::range_in_buffer(
5796 excerpt_id,
5797 buffer_snapshot.remote_id(),
5798 match_start..match_end,
5799 );
5800 (match_anchor_range != query_range).then_some(match_anchor_range)
5801 }),
5802 );
5803 }
5804 match_ranges
5805 });
5806 let match_ranges = match_task.await;
5807 editor
5808 .update_in(cx, |editor, _, cx| {
5809 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
5810 if !match_ranges.is_empty() {
5811 editor.highlight_background::<SelectedTextHighlight>(
5812 &match_ranges,
5813 |theme| theme.editor_document_highlight_bracket_background,
5814 cx,
5815 )
5816 }
5817 })
5818 .log_err();
5819 })
5820 }
5821
5822 fn refresh_selected_text_highlights(
5823 &mut self,
5824 on_buffer_edit: bool,
5825 window: &mut Window,
5826 cx: &mut Context<Editor>,
5827 ) {
5828 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
5829 else {
5830 self.clear_background_highlights::<SelectedTextHighlight>(cx);
5831 self.quick_selection_highlight_task.take();
5832 self.debounced_selection_highlight_task.take();
5833 return;
5834 };
5835 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
5836 if on_buffer_edit
5837 || self
5838 .quick_selection_highlight_task
5839 .as_ref()
5840 .map_or(true, |(prev_anchor_range, _)| {
5841 prev_anchor_range != &query_range
5842 })
5843 {
5844 let multi_buffer_visible_start = self
5845 .scroll_manager
5846 .anchor()
5847 .anchor
5848 .to_point(&multi_buffer_snapshot);
5849 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5850 multi_buffer_visible_start
5851 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5852 Bias::Left,
5853 );
5854 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5855 self.quick_selection_highlight_task = Some((
5856 query_range.clone(),
5857 self.update_selection_occurrence_highlights(
5858 query_text.clone(),
5859 query_range.clone(),
5860 multi_buffer_visible_range,
5861 false,
5862 window,
5863 cx,
5864 ),
5865 ));
5866 }
5867 if on_buffer_edit
5868 || self
5869 .debounced_selection_highlight_task
5870 .as_ref()
5871 .map_or(true, |(prev_anchor_range, _)| {
5872 prev_anchor_range != &query_range
5873 })
5874 {
5875 let multi_buffer_start = multi_buffer_snapshot
5876 .anchor_before(0)
5877 .to_point(&multi_buffer_snapshot);
5878 let multi_buffer_end = multi_buffer_snapshot
5879 .anchor_after(multi_buffer_snapshot.len())
5880 .to_point(&multi_buffer_snapshot);
5881 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
5882 self.debounced_selection_highlight_task = Some((
5883 query_range.clone(),
5884 self.update_selection_occurrence_highlights(
5885 query_text,
5886 query_range,
5887 multi_buffer_full_range,
5888 true,
5889 window,
5890 cx,
5891 ),
5892 ));
5893 }
5894 }
5895
5896 pub fn refresh_inline_completion(
5897 &mut self,
5898 debounce: bool,
5899 user_requested: bool,
5900 window: &mut Window,
5901 cx: &mut Context<Self>,
5902 ) -> Option<()> {
5903 let provider = self.edit_prediction_provider()?;
5904 let cursor = self.selections.newest_anchor().head();
5905 let (buffer, cursor_buffer_position) =
5906 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
5907
5908 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
5909 self.discard_inline_completion(false, cx);
5910 return None;
5911 }
5912
5913 if !user_requested
5914 && (!self.should_show_edit_predictions()
5915 || !self.is_focused(window)
5916 || buffer.read(cx).is_empty())
5917 {
5918 self.discard_inline_completion(false, cx);
5919 return None;
5920 }
5921
5922 self.update_visible_inline_completion(window, cx);
5923 provider.refresh(
5924 self.project.clone(),
5925 buffer,
5926 cursor_buffer_position,
5927 debounce,
5928 cx,
5929 );
5930 Some(())
5931 }
5932
5933 fn show_edit_predictions_in_menu(&self) -> bool {
5934 match self.edit_prediction_settings {
5935 EditPredictionSettings::Disabled => false,
5936 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
5937 }
5938 }
5939
5940 pub fn edit_predictions_enabled(&self) -> bool {
5941 match self.edit_prediction_settings {
5942 EditPredictionSettings::Disabled => false,
5943 EditPredictionSettings::Enabled { .. } => true,
5944 }
5945 }
5946
5947 fn edit_prediction_requires_modifier(&self) -> bool {
5948 match self.edit_prediction_settings {
5949 EditPredictionSettings::Disabled => false,
5950 EditPredictionSettings::Enabled {
5951 preview_requires_modifier,
5952 ..
5953 } => preview_requires_modifier,
5954 }
5955 }
5956
5957 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
5958 if self.edit_prediction_provider.is_none() {
5959 self.edit_prediction_settings = EditPredictionSettings::Disabled;
5960 } else {
5961 let selection = self.selections.newest_anchor();
5962 let cursor = selection.head();
5963
5964 if let Some((buffer, cursor_buffer_position)) =
5965 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
5966 {
5967 self.edit_prediction_settings =
5968 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
5969 }
5970 }
5971 }
5972
5973 fn edit_prediction_settings_at_position(
5974 &self,
5975 buffer: &Entity<Buffer>,
5976 buffer_position: language::Anchor,
5977 cx: &App,
5978 ) -> EditPredictionSettings {
5979 if !self.mode.is_full()
5980 || !self.show_inline_completions_override.unwrap_or(true)
5981 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
5982 {
5983 return EditPredictionSettings::Disabled;
5984 }
5985
5986 let buffer = buffer.read(cx);
5987
5988 let file = buffer.file();
5989
5990 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
5991 return EditPredictionSettings::Disabled;
5992 };
5993
5994 let by_provider = matches!(
5995 self.menu_inline_completions_policy,
5996 MenuInlineCompletionsPolicy::ByProvider
5997 );
5998
5999 let show_in_menu = by_provider
6000 && self
6001 .edit_prediction_provider
6002 .as_ref()
6003 .map_or(false, |provider| {
6004 provider.provider.show_completions_in_menu()
6005 });
6006
6007 let preview_requires_modifier =
6008 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6009
6010 EditPredictionSettings::Enabled {
6011 show_in_menu,
6012 preview_requires_modifier,
6013 }
6014 }
6015
6016 fn should_show_edit_predictions(&self) -> bool {
6017 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6018 }
6019
6020 pub fn edit_prediction_preview_is_active(&self) -> bool {
6021 matches!(
6022 self.edit_prediction_preview,
6023 EditPredictionPreview::Active { .. }
6024 )
6025 }
6026
6027 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6028 let cursor = self.selections.newest_anchor().head();
6029 if let Some((buffer, cursor_position)) =
6030 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6031 {
6032 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6033 } else {
6034 false
6035 }
6036 }
6037
6038 fn edit_predictions_enabled_in_buffer(
6039 &self,
6040 buffer: &Entity<Buffer>,
6041 buffer_position: language::Anchor,
6042 cx: &App,
6043 ) -> bool {
6044 maybe!({
6045 if self.read_only(cx) {
6046 return Some(false);
6047 }
6048 let provider = self.edit_prediction_provider()?;
6049 if !provider.is_enabled(&buffer, buffer_position, cx) {
6050 return Some(false);
6051 }
6052 let buffer = buffer.read(cx);
6053 let Some(file) = buffer.file() else {
6054 return Some(true);
6055 };
6056 let settings = all_language_settings(Some(file), cx);
6057 Some(settings.edit_predictions_enabled_for_file(file, cx))
6058 })
6059 .unwrap_or(false)
6060 }
6061
6062 fn cycle_inline_completion(
6063 &mut self,
6064 direction: Direction,
6065 window: &mut Window,
6066 cx: &mut Context<Self>,
6067 ) -> Option<()> {
6068 let provider = self.edit_prediction_provider()?;
6069 let cursor = self.selections.newest_anchor().head();
6070 let (buffer, cursor_buffer_position) =
6071 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6072 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6073 return None;
6074 }
6075
6076 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6077 self.update_visible_inline_completion(window, cx);
6078
6079 Some(())
6080 }
6081
6082 pub fn show_inline_completion(
6083 &mut self,
6084 _: &ShowEditPrediction,
6085 window: &mut Window,
6086 cx: &mut Context<Self>,
6087 ) {
6088 if !self.has_active_inline_completion() {
6089 self.refresh_inline_completion(false, true, window, cx);
6090 return;
6091 }
6092
6093 self.update_visible_inline_completion(window, cx);
6094 }
6095
6096 pub fn display_cursor_names(
6097 &mut self,
6098 _: &DisplayCursorNames,
6099 window: &mut Window,
6100 cx: &mut Context<Self>,
6101 ) {
6102 self.show_cursor_names(window, cx);
6103 }
6104
6105 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6106 self.show_cursor_names = true;
6107 cx.notify();
6108 cx.spawn_in(window, async move |this, cx| {
6109 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6110 this.update(cx, |this, cx| {
6111 this.show_cursor_names = false;
6112 cx.notify()
6113 })
6114 .ok()
6115 })
6116 .detach();
6117 }
6118
6119 pub fn next_edit_prediction(
6120 &mut self,
6121 _: &NextEditPrediction,
6122 window: &mut Window,
6123 cx: &mut Context<Self>,
6124 ) {
6125 if self.has_active_inline_completion() {
6126 self.cycle_inline_completion(Direction::Next, window, cx);
6127 } else {
6128 let is_copilot_disabled = self
6129 .refresh_inline_completion(false, true, window, cx)
6130 .is_none();
6131 if is_copilot_disabled {
6132 cx.propagate();
6133 }
6134 }
6135 }
6136
6137 pub fn previous_edit_prediction(
6138 &mut self,
6139 _: &PreviousEditPrediction,
6140 window: &mut Window,
6141 cx: &mut Context<Self>,
6142 ) {
6143 if self.has_active_inline_completion() {
6144 self.cycle_inline_completion(Direction::Prev, window, cx);
6145 } else {
6146 let is_copilot_disabled = self
6147 .refresh_inline_completion(false, true, window, cx)
6148 .is_none();
6149 if is_copilot_disabled {
6150 cx.propagate();
6151 }
6152 }
6153 }
6154
6155 pub fn accept_edit_prediction(
6156 &mut self,
6157 _: &AcceptEditPrediction,
6158 window: &mut Window,
6159 cx: &mut Context<Self>,
6160 ) {
6161 if self.show_edit_predictions_in_menu() {
6162 self.hide_context_menu(window, cx);
6163 }
6164
6165 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6166 return;
6167 };
6168
6169 self.report_inline_completion_event(
6170 active_inline_completion.completion_id.clone(),
6171 true,
6172 cx,
6173 );
6174
6175 match &active_inline_completion.completion {
6176 InlineCompletion::Move { target, .. } => {
6177 let target = *target;
6178
6179 if let Some(position_map) = &self.last_position_map {
6180 if position_map
6181 .visible_row_range
6182 .contains(&target.to_display_point(&position_map.snapshot).row())
6183 || !self.edit_prediction_requires_modifier()
6184 {
6185 self.unfold_ranges(&[target..target], true, false, cx);
6186 // Note that this is also done in vim's handler of the Tab action.
6187 self.change_selections(
6188 Some(Autoscroll::newest()),
6189 window,
6190 cx,
6191 |selections| {
6192 selections.select_anchor_ranges([target..target]);
6193 },
6194 );
6195 self.clear_row_highlights::<EditPredictionPreview>();
6196
6197 self.edit_prediction_preview
6198 .set_previous_scroll_position(None);
6199 } else {
6200 self.edit_prediction_preview
6201 .set_previous_scroll_position(Some(
6202 position_map.snapshot.scroll_anchor,
6203 ));
6204
6205 self.highlight_rows::<EditPredictionPreview>(
6206 target..target,
6207 cx.theme().colors().editor_highlighted_line_background,
6208 RowHighlightOptions {
6209 autoscroll: true,
6210 ..Default::default()
6211 },
6212 cx,
6213 );
6214 self.request_autoscroll(Autoscroll::fit(), cx);
6215 }
6216 }
6217 }
6218 InlineCompletion::Edit { edits, .. } => {
6219 if let Some(provider) = self.edit_prediction_provider() {
6220 provider.accept(cx);
6221 }
6222
6223 let snapshot = self.buffer.read(cx).snapshot(cx);
6224 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6225
6226 self.buffer.update(cx, |buffer, cx| {
6227 buffer.edit(edits.iter().cloned(), None, cx)
6228 });
6229
6230 self.change_selections(None, window, cx, |s| {
6231 s.select_anchor_ranges([last_edit_end..last_edit_end])
6232 });
6233
6234 self.update_visible_inline_completion(window, cx);
6235 if self.active_inline_completion.is_none() {
6236 self.refresh_inline_completion(true, true, window, cx);
6237 }
6238
6239 cx.notify();
6240 }
6241 }
6242
6243 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6244 }
6245
6246 pub fn accept_partial_inline_completion(
6247 &mut self,
6248 _: &AcceptPartialEditPrediction,
6249 window: &mut Window,
6250 cx: &mut Context<Self>,
6251 ) {
6252 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6253 return;
6254 };
6255 if self.selections.count() != 1 {
6256 return;
6257 }
6258
6259 self.report_inline_completion_event(
6260 active_inline_completion.completion_id.clone(),
6261 true,
6262 cx,
6263 );
6264
6265 match &active_inline_completion.completion {
6266 InlineCompletion::Move { target, .. } => {
6267 let target = *target;
6268 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6269 selections.select_anchor_ranges([target..target]);
6270 });
6271 }
6272 InlineCompletion::Edit { edits, .. } => {
6273 // Find an insertion that starts at the cursor position.
6274 let snapshot = self.buffer.read(cx).snapshot(cx);
6275 let cursor_offset = self.selections.newest::<usize>(cx).head();
6276 let insertion = edits.iter().find_map(|(range, text)| {
6277 let range = range.to_offset(&snapshot);
6278 if range.is_empty() && range.start == cursor_offset {
6279 Some(text)
6280 } else {
6281 None
6282 }
6283 });
6284
6285 if let Some(text) = insertion {
6286 let mut partial_completion = text
6287 .chars()
6288 .by_ref()
6289 .take_while(|c| c.is_alphabetic())
6290 .collect::<String>();
6291 if partial_completion.is_empty() {
6292 partial_completion = text
6293 .chars()
6294 .by_ref()
6295 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6296 .collect::<String>();
6297 }
6298
6299 cx.emit(EditorEvent::InputHandled {
6300 utf16_range_to_replace: None,
6301 text: partial_completion.clone().into(),
6302 });
6303
6304 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6305
6306 self.refresh_inline_completion(true, true, window, cx);
6307 cx.notify();
6308 } else {
6309 self.accept_edit_prediction(&Default::default(), window, cx);
6310 }
6311 }
6312 }
6313 }
6314
6315 fn discard_inline_completion(
6316 &mut self,
6317 should_report_inline_completion_event: bool,
6318 cx: &mut Context<Self>,
6319 ) -> bool {
6320 if should_report_inline_completion_event {
6321 let completion_id = self
6322 .active_inline_completion
6323 .as_ref()
6324 .and_then(|active_completion| active_completion.completion_id.clone());
6325
6326 self.report_inline_completion_event(completion_id, false, cx);
6327 }
6328
6329 if let Some(provider) = self.edit_prediction_provider() {
6330 provider.discard(cx);
6331 }
6332
6333 self.take_active_inline_completion(cx)
6334 }
6335
6336 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6337 let Some(provider) = self.edit_prediction_provider() else {
6338 return;
6339 };
6340
6341 let Some((_, buffer, _)) = self
6342 .buffer
6343 .read(cx)
6344 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6345 else {
6346 return;
6347 };
6348
6349 let extension = buffer
6350 .read(cx)
6351 .file()
6352 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6353
6354 let event_type = match accepted {
6355 true => "Edit Prediction Accepted",
6356 false => "Edit Prediction Discarded",
6357 };
6358 telemetry::event!(
6359 event_type,
6360 provider = provider.name(),
6361 prediction_id = id,
6362 suggestion_accepted = accepted,
6363 file_extension = extension,
6364 );
6365 }
6366
6367 pub fn has_active_inline_completion(&self) -> bool {
6368 self.active_inline_completion.is_some()
6369 }
6370
6371 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6372 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6373 return false;
6374 };
6375
6376 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6377 self.clear_highlights::<InlineCompletionHighlight>(cx);
6378 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6379 true
6380 }
6381
6382 /// Returns true when we're displaying the edit prediction popover below the cursor
6383 /// like we are not previewing and the LSP autocomplete menu is visible
6384 /// or we are in `when_holding_modifier` mode.
6385 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6386 if self.edit_prediction_preview_is_active()
6387 || !self.show_edit_predictions_in_menu()
6388 || !self.edit_predictions_enabled()
6389 {
6390 return false;
6391 }
6392
6393 if self.has_visible_completions_menu() {
6394 return true;
6395 }
6396
6397 has_completion && self.edit_prediction_requires_modifier()
6398 }
6399
6400 fn handle_modifiers_changed(
6401 &mut self,
6402 modifiers: Modifiers,
6403 position_map: &PositionMap,
6404 window: &mut Window,
6405 cx: &mut Context<Self>,
6406 ) {
6407 if self.show_edit_predictions_in_menu() {
6408 self.update_edit_prediction_preview(&modifiers, window, cx);
6409 }
6410
6411 self.update_selection_mode(&modifiers, position_map, window, cx);
6412
6413 let mouse_position = window.mouse_position();
6414 if !position_map.text_hitbox.is_hovered(window) {
6415 return;
6416 }
6417
6418 self.update_hovered_link(
6419 position_map.point_for_position(mouse_position),
6420 &position_map.snapshot,
6421 modifiers,
6422 window,
6423 cx,
6424 )
6425 }
6426
6427 fn update_selection_mode(
6428 &mut self,
6429 modifiers: &Modifiers,
6430 position_map: &PositionMap,
6431 window: &mut Window,
6432 cx: &mut Context<Self>,
6433 ) {
6434 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6435 return;
6436 }
6437
6438 let mouse_position = window.mouse_position();
6439 let point_for_position = position_map.point_for_position(mouse_position);
6440 let position = point_for_position.previous_valid;
6441
6442 self.select(
6443 SelectPhase::BeginColumnar {
6444 position,
6445 reset: false,
6446 goal_column: point_for_position.exact_unclipped.column(),
6447 },
6448 window,
6449 cx,
6450 );
6451 }
6452
6453 fn update_edit_prediction_preview(
6454 &mut self,
6455 modifiers: &Modifiers,
6456 window: &mut Window,
6457 cx: &mut Context<Self>,
6458 ) {
6459 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6460 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6461 return;
6462 };
6463
6464 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6465 if matches!(
6466 self.edit_prediction_preview,
6467 EditPredictionPreview::Inactive { .. }
6468 ) {
6469 self.edit_prediction_preview = EditPredictionPreview::Active {
6470 previous_scroll_position: None,
6471 since: Instant::now(),
6472 };
6473
6474 self.update_visible_inline_completion(window, cx);
6475 cx.notify();
6476 }
6477 } else if let EditPredictionPreview::Active {
6478 previous_scroll_position,
6479 since,
6480 } = self.edit_prediction_preview
6481 {
6482 if let (Some(previous_scroll_position), Some(position_map)) =
6483 (previous_scroll_position, self.last_position_map.as_ref())
6484 {
6485 self.set_scroll_position(
6486 previous_scroll_position
6487 .scroll_position(&position_map.snapshot.display_snapshot),
6488 window,
6489 cx,
6490 );
6491 }
6492
6493 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6494 released_too_fast: since.elapsed() < Duration::from_millis(200),
6495 };
6496 self.clear_row_highlights::<EditPredictionPreview>();
6497 self.update_visible_inline_completion(window, cx);
6498 cx.notify();
6499 }
6500 }
6501
6502 fn update_visible_inline_completion(
6503 &mut self,
6504 _window: &mut Window,
6505 cx: &mut Context<Self>,
6506 ) -> Option<()> {
6507 let selection = self.selections.newest_anchor();
6508 let cursor = selection.head();
6509 let multibuffer = self.buffer.read(cx).snapshot(cx);
6510 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6511 let excerpt_id = cursor.excerpt_id;
6512
6513 let show_in_menu = self.show_edit_predictions_in_menu();
6514 let completions_menu_has_precedence = !show_in_menu
6515 && (self.context_menu.borrow().is_some()
6516 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6517
6518 if completions_menu_has_precedence
6519 || !offset_selection.is_empty()
6520 || self
6521 .active_inline_completion
6522 .as_ref()
6523 .map_or(false, |completion| {
6524 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6525 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6526 !invalidation_range.contains(&offset_selection.head())
6527 })
6528 {
6529 self.discard_inline_completion(false, cx);
6530 return None;
6531 }
6532
6533 self.take_active_inline_completion(cx);
6534 let Some(provider) = self.edit_prediction_provider() else {
6535 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6536 return None;
6537 };
6538
6539 let (buffer, cursor_buffer_position) =
6540 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6541
6542 self.edit_prediction_settings =
6543 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6544
6545 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6546
6547 if self.edit_prediction_indent_conflict {
6548 let cursor_point = cursor.to_point(&multibuffer);
6549
6550 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6551
6552 if let Some((_, indent)) = indents.iter().next() {
6553 if indent.len == cursor_point.column {
6554 self.edit_prediction_indent_conflict = false;
6555 }
6556 }
6557 }
6558
6559 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6560 let edits = inline_completion
6561 .edits
6562 .into_iter()
6563 .flat_map(|(range, new_text)| {
6564 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6565 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6566 Some((start..end, new_text))
6567 })
6568 .collect::<Vec<_>>();
6569 if edits.is_empty() {
6570 return None;
6571 }
6572
6573 let first_edit_start = edits.first().unwrap().0.start;
6574 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6575 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6576
6577 let last_edit_end = edits.last().unwrap().0.end;
6578 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6579 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6580
6581 let cursor_row = cursor.to_point(&multibuffer).row;
6582
6583 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6584
6585 let mut inlay_ids = Vec::new();
6586 let invalidation_row_range;
6587 let move_invalidation_row_range = if cursor_row < edit_start_row {
6588 Some(cursor_row..edit_end_row)
6589 } else if cursor_row > edit_end_row {
6590 Some(edit_start_row..cursor_row)
6591 } else {
6592 None
6593 };
6594 let is_move =
6595 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6596 let completion = if is_move {
6597 invalidation_row_range =
6598 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6599 let target = first_edit_start;
6600 InlineCompletion::Move { target, snapshot }
6601 } else {
6602 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6603 && !self.inline_completions_hidden_for_vim_mode;
6604
6605 if show_completions_in_buffer {
6606 if edits
6607 .iter()
6608 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
6609 {
6610 let mut inlays = Vec::new();
6611 for (range, new_text) in &edits {
6612 let inlay = Inlay::inline_completion(
6613 post_inc(&mut self.next_inlay_id),
6614 range.start,
6615 new_text.as_str(),
6616 );
6617 inlay_ids.push(inlay.id);
6618 inlays.push(inlay);
6619 }
6620
6621 self.splice_inlays(&[], inlays, cx);
6622 } else {
6623 let background_color = cx.theme().status().deleted_background;
6624 self.highlight_text::<InlineCompletionHighlight>(
6625 edits.iter().map(|(range, _)| range.clone()).collect(),
6626 HighlightStyle {
6627 background_color: Some(background_color),
6628 ..Default::default()
6629 },
6630 cx,
6631 );
6632 }
6633 }
6634
6635 invalidation_row_range = edit_start_row..edit_end_row;
6636
6637 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
6638 if provider.show_tab_accept_marker() {
6639 EditDisplayMode::TabAccept
6640 } else {
6641 EditDisplayMode::Inline
6642 }
6643 } else {
6644 EditDisplayMode::DiffPopover
6645 };
6646
6647 InlineCompletion::Edit {
6648 edits,
6649 edit_preview: inline_completion.edit_preview,
6650 display_mode,
6651 snapshot,
6652 }
6653 };
6654
6655 let invalidation_range = multibuffer
6656 .anchor_before(Point::new(invalidation_row_range.start, 0))
6657 ..multibuffer.anchor_after(Point::new(
6658 invalidation_row_range.end,
6659 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
6660 ));
6661
6662 self.stale_inline_completion_in_menu = None;
6663 self.active_inline_completion = Some(InlineCompletionState {
6664 inlay_ids,
6665 completion,
6666 completion_id: inline_completion.id,
6667 invalidation_range,
6668 });
6669
6670 cx.notify();
6671
6672 Some(())
6673 }
6674
6675 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
6676 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
6677 }
6678
6679 fn render_code_actions_indicator(
6680 &self,
6681 _style: &EditorStyle,
6682 row: DisplayRow,
6683 is_active: bool,
6684 breakpoint: Option<&(Anchor, Breakpoint)>,
6685 cx: &mut Context<Self>,
6686 ) -> Option<IconButton> {
6687 let color = Color::Muted;
6688 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
6689 let show_tooltip = !self.context_menu_visible();
6690
6691 if self.available_code_actions.is_some() {
6692 Some(
6693 IconButton::new("code_actions_indicator", ui::IconName::Bolt)
6694 .shape(ui::IconButtonShape::Square)
6695 .icon_size(IconSize::XSmall)
6696 .icon_color(color)
6697 .toggle_state(is_active)
6698 .when(show_tooltip, |this| {
6699 this.tooltip({
6700 let focus_handle = self.focus_handle.clone();
6701 move |window, cx| {
6702 Tooltip::for_action_in(
6703 "Toggle Code Actions",
6704 &ToggleCodeActions {
6705 deployed_from_indicator: None,
6706 quick_launch: false,
6707 },
6708 &focus_handle,
6709 window,
6710 cx,
6711 )
6712 }
6713 })
6714 })
6715 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
6716 let quick_launch = e.down.button == MouseButton::Left;
6717 window.focus(&editor.focus_handle(cx));
6718 editor.toggle_code_actions(
6719 &ToggleCodeActions {
6720 deployed_from_indicator: Some(row),
6721 quick_launch,
6722 },
6723 window,
6724 cx,
6725 );
6726 }))
6727 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
6728 editor.set_breakpoint_context_menu(
6729 row,
6730 position,
6731 event.down.position,
6732 window,
6733 cx,
6734 );
6735 })),
6736 )
6737 } else {
6738 None
6739 }
6740 }
6741
6742 fn clear_tasks(&mut self) {
6743 self.tasks.clear()
6744 }
6745
6746 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
6747 if self.tasks.insert(key, value).is_some() {
6748 // This case should hopefully be rare, but just in case...
6749 log::error!(
6750 "multiple different run targets found on a single line, only the last target will be rendered"
6751 )
6752 }
6753 }
6754
6755 /// Get all display points of breakpoints that will be rendered within editor
6756 ///
6757 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
6758 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
6759 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
6760 fn active_breakpoints(
6761 &self,
6762 range: Range<DisplayRow>,
6763 window: &mut Window,
6764 cx: &mut Context<Self>,
6765 ) -> HashMap<DisplayRow, (Anchor, Breakpoint)> {
6766 let mut breakpoint_display_points = HashMap::default();
6767
6768 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
6769 return breakpoint_display_points;
6770 };
6771
6772 let snapshot = self.snapshot(window, cx);
6773
6774 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
6775 let Some(project) = self.project.as_ref() else {
6776 return breakpoint_display_points;
6777 };
6778
6779 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
6780 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
6781
6782 for (buffer_snapshot, range, excerpt_id) in
6783 multi_buffer_snapshot.range_to_buffer_ranges(range)
6784 {
6785 let Some(buffer) = project.read_with(cx, |this, cx| {
6786 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
6787 }) else {
6788 continue;
6789 };
6790 let breakpoints = breakpoint_store.read(cx).breakpoints(
6791 &buffer,
6792 Some(
6793 buffer_snapshot.anchor_before(range.start)
6794 ..buffer_snapshot.anchor_after(range.end),
6795 ),
6796 buffer_snapshot,
6797 cx,
6798 );
6799 for (anchor, breakpoint) in breakpoints {
6800 let multi_buffer_anchor =
6801 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), *anchor);
6802 let position = multi_buffer_anchor
6803 .to_point(&multi_buffer_snapshot)
6804 .to_display_point(&snapshot);
6805
6806 breakpoint_display_points
6807 .insert(position.row(), (multi_buffer_anchor, breakpoint.clone()));
6808 }
6809 }
6810
6811 breakpoint_display_points
6812 }
6813
6814 fn breakpoint_context_menu(
6815 &self,
6816 anchor: Anchor,
6817 window: &mut Window,
6818 cx: &mut Context<Self>,
6819 ) -> Entity<ui::ContextMenu> {
6820 let weak_editor = cx.weak_entity();
6821 let focus_handle = self.focus_handle(cx);
6822
6823 let row = self
6824 .buffer
6825 .read(cx)
6826 .snapshot(cx)
6827 .summary_for_anchor::<Point>(&anchor)
6828 .row;
6829
6830 let breakpoint = self
6831 .breakpoint_at_row(row, window, cx)
6832 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
6833
6834 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
6835 "Edit Log Breakpoint"
6836 } else {
6837 "Set Log Breakpoint"
6838 };
6839
6840 let condition_breakpoint_msg = if breakpoint
6841 .as_ref()
6842 .is_some_and(|bp| bp.1.condition.is_some())
6843 {
6844 "Edit Condition Breakpoint"
6845 } else {
6846 "Set Condition Breakpoint"
6847 };
6848
6849 let hit_condition_breakpoint_msg = if breakpoint
6850 .as_ref()
6851 .is_some_and(|bp| bp.1.hit_condition.is_some())
6852 {
6853 "Edit Hit Condition Breakpoint"
6854 } else {
6855 "Set Hit Condition Breakpoint"
6856 };
6857
6858 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
6859 "Unset Breakpoint"
6860 } else {
6861 "Set Breakpoint"
6862 };
6863
6864 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
6865 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
6866
6867 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
6868 BreakpointState::Enabled => Some("Disable"),
6869 BreakpointState::Disabled => Some("Enable"),
6870 });
6871
6872 let (anchor, breakpoint) =
6873 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
6874
6875 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
6876 menu.on_blur_subscription(Subscription::new(|| {}))
6877 .context(focus_handle)
6878 .when(run_to_cursor, |this| {
6879 let weak_editor = weak_editor.clone();
6880 this.entry("Run to cursor", None, move |window, cx| {
6881 weak_editor
6882 .update(cx, |editor, cx| {
6883 editor.change_selections(None, window, cx, |s| {
6884 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
6885 });
6886 })
6887 .ok();
6888
6889 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
6890 })
6891 .separator()
6892 })
6893 .when_some(toggle_state_msg, |this, msg| {
6894 this.entry(msg, None, {
6895 let weak_editor = weak_editor.clone();
6896 let breakpoint = breakpoint.clone();
6897 move |_window, cx| {
6898 weak_editor
6899 .update(cx, |this, cx| {
6900 this.edit_breakpoint_at_anchor(
6901 anchor,
6902 breakpoint.as_ref().clone(),
6903 BreakpointEditAction::InvertState,
6904 cx,
6905 );
6906 })
6907 .log_err();
6908 }
6909 })
6910 })
6911 .entry(set_breakpoint_msg, None, {
6912 let weak_editor = weak_editor.clone();
6913 let breakpoint = breakpoint.clone();
6914 move |_window, cx| {
6915 weak_editor
6916 .update(cx, |this, cx| {
6917 this.edit_breakpoint_at_anchor(
6918 anchor,
6919 breakpoint.as_ref().clone(),
6920 BreakpointEditAction::Toggle,
6921 cx,
6922 );
6923 })
6924 .log_err();
6925 }
6926 })
6927 .entry(log_breakpoint_msg, None, {
6928 let breakpoint = breakpoint.clone();
6929 let weak_editor = weak_editor.clone();
6930 move |window, cx| {
6931 weak_editor
6932 .update(cx, |this, cx| {
6933 this.add_edit_breakpoint_block(
6934 anchor,
6935 breakpoint.as_ref(),
6936 BreakpointPromptEditAction::Log,
6937 window,
6938 cx,
6939 );
6940 })
6941 .log_err();
6942 }
6943 })
6944 .entry(condition_breakpoint_msg, None, {
6945 let breakpoint = breakpoint.clone();
6946 let weak_editor = weak_editor.clone();
6947 move |window, cx| {
6948 weak_editor
6949 .update(cx, |this, cx| {
6950 this.add_edit_breakpoint_block(
6951 anchor,
6952 breakpoint.as_ref(),
6953 BreakpointPromptEditAction::Condition,
6954 window,
6955 cx,
6956 );
6957 })
6958 .log_err();
6959 }
6960 })
6961 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
6962 weak_editor
6963 .update(cx, |this, cx| {
6964 this.add_edit_breakpoint_block(
6965 anchor,
6966 breakpoint.as_ref(),
6967 BreakpointPromptEditAction::HitCondition,
6968 window,
6969 cx,
6970 );
6971 })
6972 .log_err();
6973 })
6974 })
6975 }
6976
6977 fn render_breakpoint(
6978 &self,
6979 position: Anchor,
6980 row: DisplayRow,
6981 breakpoint: &Breakpoint,
6982 cx: &mut Context<Self>,
6983 ) -> IconButton {
6984 // Is it a breakpoint that shows up when hovering over gutter?
6985 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
6986 (false, false),
6987 |PhantomBreakpointIndicator {
6988 is_active,
6989 display_row,
6990 collides_with_existing_breakpoint,
6991 }| {
6992 (
6993 is_active && display_row == row,
6994 collides_with_existing_breakpoint,
6995 )
6996 },
6997 );
6998
6999 let (color, icon) = {
7000 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7001 (false, false) => ui::IconName::DebugBreakpoint,
7002 (true, false) => ui::IconName::DebugLogBreakpoint,
7003 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7004 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7005 };
7006
7007 let color = if is_phantom {
7008 Color::Hint
7009 } else {
7010 Color::Debugger
7011 };
7012
7013 (color, icon)
7014 };
7015
7016 let breakpoint = Arc::from(breakpoint.clone());
7017
7018 let alt_as_text = gpui::Keystroke {
7019 modifiers: Modifiers::secondary_key(),
7020 ..Default::default()
7021 };
7022 let primary_action_text = if breakpoint.is_disabled() {
7023 "enable"
7024 } else if is_phantom && !collides_with_existing {
7025 "set"
7026 } else {
7027 "unset"
7028 };
7029 let mut primary_text = format!("Click to {primary_action_text}");
7030 if collides_with_existing && !breakpoint.is_disabled() {
7031 use std::fmt::Write;
7032 write!(primary_text, ", {alt_as_text}-click to disable").ok();
7033 }
7034 let primary_text = SharedString::from(primary_text);
7035 let focus_handle = self.focus_handle.clone();
7036 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7037 .icon_size(IconSize::XSmall)
7038 .size(ui::ButtonSize::None)
7039 .icon_color(color)
7040 .style(ButtonStyle::Transparent)
7041 .on_click(cx.listener({
7042 let breakpoint = breakpoint.clone();
7043
7044 move |editor, event: &ClickEvent, window, cx| {
7045 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7046 BreakpointEditAction::InvertState
7047 } else {
7048 BreakpointEditAction::Toggle
7049 };
7050
7051 window.focus(&editor.focus_handle(cx));
7052 editor.edit_breakpoint_at_anchor(
7053 position,
7054 breakpoint.as_ref().clone(),
7055 edit_action,
7056 cx,
7057 );
7058 }
7059 }))
7060 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7061 editor.set_breakpoint_context_menu(
7062 row,
7063 Some(position),
7064 event.down.position,
7065 window,
7066 cx,
7067 );
7068 }))
7069 .tooltip(move |window, cx| {
7070 Tooltip::with_meta_in(
7071 primary_text.clone(),
7072 None,
7073 "Right-click for more options",
7074 &focus_handle,
7075 window,
7076 cx,
7077 )
7078 })
7079 }
7080
7081 fn build_tasks_context(
7082 project: &Entity<Project>,
7083 buffer: &Entity<Buffer>,
7084 buffer_row: u32,
7085 tasks: &Arc<RunnableTasks>,
7086 cx: &mut Context<Self>,
7087 ) -> Task<Option<task::TaskContext>> {
7088 let position = Point::new(buffer_row, tasks.column);
7089 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7090 let location = Location {
7091 buffer: buffer.clone(),
7092 range: range_start..range_start,
7093 };
7094 // Fill in the environmental variables from the tree-sitter captures
7095 let mut captured_task_variables = TaskVariables::default();
7096 for (capture_name, value) in tasks.extra_variables.clone() {
7097 captured_task_variables.insert(
7098 task::VariableName::Custom(capture_name.into()),
7099 value.clone(),
7100 );
7101 }
7102 project.update(cx, |project, cx| {
7103 project.task_store().update(cx, |task_store, cx| {
7104 task_store.task_context_for_location(captured_task_variables, location, cx)
7105 })
7106 })
7107 }
7108
7109 pub fn spawn_nearest_task(
7110 &mut self,
7111 action: &SpawnNearestTask,
7112 window: &mut Window,
7113 cx: &mut Context<Self>,
7114 ) {
7115 let Some((workspace, _)) = self.workspace.clone() else {
7116 return;
7117 };
7118 let Some(project) = self.project.clone() else {
7119 return;
7120 };
7121
7122 // Try to find a closest, enclosing node using tree-sitter that has a
7123 // task
7124 let Some((buffer, buffer_row, tasks)) = self
7125 .find_enclosing_node_task(cx)
7126 // Or find the task that's closest in row-distance.
7127 .or_else(|| self.find_closest_task(cx))
7128 else {
7129 return;
7130 };
7131
7132 let reveal_strategy = action.reveal;
7133 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7134 cx.spawn_in(window, async move |_, cx| {
7135 let context = task_context.await?;
7136 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7137
7138 let resolved = &mut resolved_task.resolved;
7139 resolved.reveal = reveal_strategy;
7140
7141 workspace
7142 .update_in(cx, |workspace, window, cx| {
7143 workspace.schedule_resolved_task(
7144 task_source_kind,
7145 resolved_task,
7146 false,
7147 window,
7148 cx,
7149 );
7150 })
7151 .ok()
7152 })
7153 .detach();
7154 }
7155
7156 fn find_closest_task(
7157 &mut self,
7158 cx: &mut Context<Self>,
7159 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7160 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7161
7162 let ((buffer_id, row), tasks) = self
7163 .tasks
7164 .iter()
7165 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7166
7167 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7168 let tasks = Arc::new(tasks.to_owned());
7169 Some((buffer, *row, tasks))
7170 }
7171
7172 fn find_enclosing_node_task(
7173 &mut self,
7174 cx: &mut Context<Self>,
7175 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7176 let snapshot = self.buffer.read(cx).snapshot(cx);
7177 let offset = self.selections.newest::<usize>(cx).head();
7178 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7179 let buffer_id = excerpt.buffer().remote_id();
7180
7181 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7182 let mut cursor = layer.node().walk();
7183
7184 while cursor.goto_first_child_for_byte(offset).is_some() {
7185 if cursor.node().end_byte() == offset {
7186 cursor.goto_next_sibling();
7187 }
7188 }
7189
7190 // Ascend to the smallest ancestor that contains the range and has a task.
7191 loop {
7192 let node = cursor.node();
7193 let node_range = node.byte_range();
7194 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7195
7196 // Check if this node contains our offset
7197 if node_range.start <= offset && node_range.end >= offset {
7198 // If it contains offset, check for task
7199 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7200 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7201 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7202 }
7203 }
7204
7205 if !cursor.goto_parent() {
7206 break;
7207 }
7208 }
7209 None
7210 }
7211
7212 fn render_run_indicator(
7213 &self,
7214 _style: &EditorStyle,
7215 is_active: bool,
7216 row: DisplayRow,
7217 breakpoint: Option<(Anchor, Breakpoint)>,
7218 cx: &mut Context<Self>,
7219 ) -> IconButton {
7220 let color = Color::Muted;
7221 let position = breakpoint.as_ref().map(|(anchor, _)| *anchor);
7222
7223 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7224 .shape(ui::IconButtonShape::Square)
7225 .icon_size(IconSize::XSmall)
7226 .icon_color(color)
7227 .toggle_state(is_active)
7228 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7229 let quick_launch = e.down.button == MouseButton::Left;
7230 window.focus(&editor.focus_handle(cx));
7231 editor.toggle_code_actions(
7232 &ToggleCodeActions {
7233 deployed_from_indicator: Some(row),
7234 quick_launch,
7235 },
7236 window,
7237 cx,
7238 );
7239 }))
7240 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7241 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7242 }))
7243 }
7244
7245 pub fn context_menu_visible(&self) -> bool {
7246 !self.edit_prediction_preview_is_active()
7247 && self
7248 .context_menu
7249 .borrow()
7250 .as_ref()
7251 .map_or(false, |menu| menu.visible())
7252 }
7253
7254 fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7255 self.context_menu
7256 .borrow()
7257 .as_ref()
7258 .map(|menu| menu.origin())
7259 }
7260
7261 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7262 self.context_menu_options = Some(options);
7263 }
7264
7265 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7266 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7267
7268 fn render_edit_prediction_popover(
7269 &mut self,
7270 text_bounds: &Bounds<Pixels>,
7271 content_origin: gpui::Point<Pixels>,
7272 editor_snapshot: &EditorSnapshot,
7273 visible_row_range: Range<DisplayRow>,
7274 scroll_top: f32,
7275 scroll_bottom: f32,
7276 line_layouts: &[LineWithInvisibles],
7277 line_height: Pixels,
7278 scroll_pixel_position: gpui::Point<Pixels>,
7279 newest_selection_head: Option<DisplayPoint>,
7280 editor_width: Pixels,
7281 style: &EditorStyle,
7282 window: &mut Window,
7283 cx: &mut App,
7284 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7285 let active_inline_completion = self.active_inline_completion.as_ref()?;
7286
7287 if self.edit_prediction_visible_in_cursor_popover(true) {
7288 return None;
7289 }
7290
7291 match &active_inline_completion.completion {
7292 InlineCompletion::Move { target, .. } => {
7293 let target_display_point = target.to_display_point(editor_snapshot);
7294
7295 if self.edit_prediction_requires_modifier() {
7296 if !self.edit_prediction_preview_is_active() {
7297 return None;
7298 }
7299
7300 self.render_edit_prediction_modifier_jump_popover(
7301 text_bounds,
7302 content_origin,
7303 visible_row_range,
7304 line_layouts,
7305 line_height,
7306 scroll_pixel_position,
7307 newest_selection_head,
7308 target_display_point,
7309 window,
7310 cx,
7311 )
7312 } else {
7313 self.render_edit_prediction_eager_jump_popover(
7314 text_bounds,
7315 content_origin,
7316 editor_snapshot,
7317 visible_row_range,
7318 scroll_top,
7319 scroll_bottom,
7320 line_height,
7321 scroll_pixel_position,
7322 target_display_point,
7323 editor_width,
7324 window,
7325 cx,
7326 )
7327 }
7328 }
7329 InlineCompletion::Edit {
7330 display_mode: EditDisplayMode::Inline,
7331 ..
7332 } => None,
7333 InlineCompletion::Edit {
7334 display_mode: EditDisplayMode::TabAccept,
7335 edits,
7336 ..
7337 } => {
7338 let range = &edits.first()?.0;
7339 let target_display_point = range.end.to_display_point(editor_snapshot);
7340
7341 self.render_edit_prediction_end_of_line_popover(
7342 "Accept",
7343 editor_snapshot,
7344 visible_row_range,
7345 target_display_point,
7346 line_height,
7347 scroll_pixel_position,
7348 content_origin,
7349 editor_width,
7350 window,
7351 cx,
7352 )
7353 }
7354 InlineCompletion::Edit {
7355 edits,
7356 edit_preview,
7357 display_mode: EditDisplayMode::DiffPopover,
7358 snapshot,
7359 } => self.render_edit_prediction_diff_popover(
7360 text_bounds,
7361 content_origin,
7362 editor_snapshot,
7363 visible_row_range,
7364 line_layouts,
7365 line_height,
7366 scroll_pixel_position,
7367 newest_selection_head,
7368 editor_width,
7369 style,
7370 edits,
7371 edit_preview,
7372 snapshot,
7373 window,
7374 cx,
7375 ),
7376 }
7377 }
7378
7379 fn render_edit_prediction_modifier_jump_popover(
7380 &mut self,
7381 text_bounds: &Bounds<Pixels>,
7382 content_origin: gpui::Point<Pixels>,
7383 visible_row_range: Range<DisplayRow>,
7384 line_layouts: &[LineWithInvisibles],
7385 line_height: Pixels,
7386 scroll_pixel_position: gpui::Point<Pixels>,
7387 newest_selection_head: Option<DisplayPoint>,
7388 target_display_point: DisplayPoint,
7389 window: &mut Window,
7390 cx: &mut App,
7391 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7392 let scrolled_content_origin =
7393 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7394
7395 const SCROLL_PADDING_Y: Pixels = px(12.);
7396
7397 if target_display_point.row() < visible_row_range.start {
7398 return self.render_edit_prediction_scroll_popover(
7399 |_| SCROLL_PADDING_Y,
7400 IconName::ArrowUp,
7401 visible_row_range,
7402 line_layouts,
7403 newest_selection_head,
7404 scrolled_content_origin,
7405 window,
7406 cx,
7407 );
7408 } else if target_display_point.row() >= visible_row_range.end {
7409 return self.render_edit_prediction_scroll_popover(
7410 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7411 IconName::ArrowDown,
7412 visible_row_range,
7413 line_layouts,
7414 newest_selection_head,
7415 scrolled_content_origin,
7416 window,
7417 cx,
7418 );
7419 }
7420
7421 const POLE_WIDTH: Pixels = px(2.);
7422
7423 let line_layout =
7424 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7425 let target_column = target_display_point.column() as usize;
7426
7427 let target_x = line_layout.x_for_index(target_column);
7428 let target_y =
7429 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7430
7431 let flag_on_right = target_x < text_bounds.size.width / 2.;
7432
7433 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7434 border_color.l += 0.001;
7435
7436 let mut element = v_flex()
7437 .items_end()
7438 .when(flag_on_right, |el| el.items_start())
7439 .child(if flag_on_right {
7440 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7441 .rounded_bl(px(0.))
7442 .rounded_tl(px(0.))
7443 .border_l_2()
7444 .border_color(border_color)
7445 } else {
7446 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7447 .rounded_br(px(0.))
7448 .rounded_tr(px(0.))
7449 .border_r_2()
7450 .border_color(border_color)
7451 })
7452 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7453 .into_any();
7454
7455 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7456
7457 let mut origin = scrolled_content_origin + point(target_x, target_y)
7458 - point(
7459 if flag_on_right {
7460 POLE_WIDTH
7461 } else {
7462 size.width - POLE_WIDTH
7463 },
7464 size.height - line_height,
7465 );
7466
7467 origin.x = origin.x.max(content_origin.x);
7468
7469 element.prepaint_at(origin, window, cx);
7470
7471 Some((element, origin))
7472 }
7473
7474 fn render_edit_prediction_scroll_popover(
7475 &mut self,
7476 to_y: impl Fn(Size<Pixels>) -> Pixels,
7477 scroll_icon: IconName,
7478 visible_row_range: Range<DisplayRow>,
7479 line_layouts: &[LineWithInvisibles],
7480 newest_selection_head: Option<DisplayPoint>,
7481 scrolled_content_origin: gpui::Point<Pixels>,
7482 window: &mut Window,
7483 cx: &mut App,
7484 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7485 let mut element = self
7486 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7487 .into_any();
7488
7489 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7490
7491 let cursor = newest_selection_head?;
7492 let cursor_row_layout =
7493 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7494 let cursor_column = cursor.column() as usize;
7495
7496 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7497
7498 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7499
7500 element.prepaint_at(origin, window, cx);
7501 Some((element, origin))
7502 }
7503
7504 fn render_edit_prediction_eager_jump_popover(
7505 &mut self,
7506 text_bounds: &Bounds<Pixels>,
7507 content_origin: gpui::Point<Pixels>,
7508 editor_snapshot: &EditorSnapshot,
7509 visible_row_range: Range<DisplayRow>,
7510 scroll_top: f32,
7511 scroll_bottom: f32,
7512 line_height: Pixels,
7513 scroll_pixel_position: gpui::Point<Pixels>,
7514 target_display_point: DisplayPoint,
7515 editor_width: Pixels,
7516 window: &mut Window,
7517 cx: &mut App,
7518 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7519 if target_display_point.row().as_f32() < scroll_top {
7520 let mut element = self
7521 .render_edit_prediction_line_popover(
7522 "Jump to Edit",
7523 Some(IconName::ArrowUp),
7524 window,
7525 cx,
7526 )?
7527 .into_any();
7528
7529 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7530 let offset = point(
7531 (text_bounds.size.width - size.width) / 2.,
7532 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7533 );
7534
7535 let origin = text_bounds.origin + offset;
7536 element.prepaint_at(origin, window, cx);
7537 Some((element, origin))
7538 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7539 let mut element = self
7540 .render_edit_prediction_line_popover(
7541 "Jump to Edit",
7542 Some(IconName::ArrowDown),
7543 window,
7544 cx,
7545 )?
7546 .into_any();
7547
7548 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7549 let offset = point(
7550 (text_bounds.size.width - size.width) / 2.,
7551 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7552 );
7553
7554 let origin = text_bounds.origin + offset;
7555 element.prepaint_at(origin, window, cx);
7556 Some((element, origin))
7557 } else {
7558 self.render_edit_prediction_end_of_line_popover(
7559 "Jump to Edit",
7560 editor_snapshot,
7561 visible_row_range,
7562 target_display_point,
7563 line_height,
7564 scroll_pixel_position,
7565 content_origin,
7566 editor_width,
7567 window,
7568 cx,
7569 )
7570 }
7571 }
7572
7573 fn render_edit_prediction_end_of_line_popover(
7574 self: &mut Editor,
7575 label: &'static str,
7576 editor_snapshot: &EditorSnapshot,
7577 visible_row_range: Range<DisplayRow>,
7578 target_display_point: DisplayPoint,
7579 line_height: Pixels,
7580 scroll_pixel_position: gpui::Point<Pixels>,
7581 content_origin: gpui::Point<Pixels>,
7582 editor_width: Pixels,
7583 window: &mut Window,
7584 cx: &mut App,
7585 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7586 let target_line_end = DisplayPoint::new(
7587 target_display_point.row(),
7588 editor_snapshot.line_len(target_display_point.row()),
7589 );
7590
7591 let mut element = self
7592 .render_edit_prediction_line_popover(label, None, window, cx)?
7593 .into_any();
7594
7595 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7596
7597 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7598
7599 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7600 let mut origin = start_point
7601 + line_origin
7602 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7603 origin.x = origin.x.max(content_origin.x);
7604
7605 let max_x = content_origin.x + editor_width - size.width;
7606
7607 if origin.x > max_x {
7608 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7609
7610 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7611 origin.y += offset;
7612 IconName::ArrowUp
7613 } else {
7614 origin.y -= offset;
7615 IconName::ArrowDown
7616 };
7617
7618 element = self
7619 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7620 .into_any();
7621
7622 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7623
7624 origin.x = content_origin.x + editor_width - size.width - px(2.);
7625 }
7626
7627 element.prepaint_at(origin, window, cx);
7628 Some((element, origin))
7629 }
7630
7631 fn render_edit_prediction_diff_popover(
7632 self: &Editor,
7633 text_bounds: &Bounds<Pixels>,
7634 content_origin: gpui::Point<Pixels>,
7635 editor_snapshot: &EditorSnapshot,
7636 visible_row_range: Range<DisplayRow>,
7637 line_layouts: &[LineWithInvisibles],
7638 line_height: Pixels,
7639 scroll_pixel_position: gpui::Point<Pixels>,
7640 newest_selection_head: Option<DisplayPoint>,
7641 editor_width: Pixels,
7642 style: &EditorStyle,
7643 edits: &Vec<(Range<Anchor>, String)>,
7644 edit_preview: &Option<language::EditPreview>,
7645 snapshot: &language::BufferSnapshot,
7646 window: &mut Window,
7647 cx: &mut App,
7648 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7649 let edit_start = edits
7650 .first()
7651 .unwrap()
7652 .0
7653 .start
7654 .to_display_point(editor_snapshot);
7655 let edit_end = edits
7656 .last()
7657 .unwrap()
7658 .0
7659 .end
7660 .to_display_point(editor_snapshot);
7661
7662 let is_visible = visible_row_range.contains(&edit_start.row())
7663 || visible_row_range.contains(&edit_end.row());
7664 if !is_visible {
7665 return None;
7666 }
7667
7668 let highlighted_edits =
7669 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
7670
7671 let styled_text = highlighted_edits.to_styled_text(&style.text);
7672 let line_count = highlighted_edits.text.lines().count();
7673
7674 const BORDER_WIDTH: Pixels = px(1.);
7675
7676 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7677 let has_keybind = keybind.is_some();
7678
7679 let mut element = h_flex()
7680 .items_start()
7681 .child(
7682 h_flex()
7683 .bg(cx.theme().colors().editor_background)
7684 .border(BORDER_WIDTH)
7685 .shadow_sm()
7686 .border_color(cx.theme().colors().border)
7687 .rounded_l_lg()
7688 .when(line_count > 1, |el| el.rounded_br_lg())
7689 .pr_1()
7690 .child(styled_text),
7691 )
7692 .child(
7693 h_flex()
7694 .h(line_height + BORDER_WIDTH * 2.)
7695 .px_1p5()
7696 .gap_1()
7697 // Workaround: For some reason, there's a gap if we don't do this
7698 .ml(-BORDER_WIDTH)
7699 .shadow(smallvec![gpui::BoxShadow {
7700 color: gpui::black().opacity(0.05),
7701 offset: point(px(1.), px(1.)),
7702 blur_radius: px(2.),
7703 spread_radius: px(0.),
7704 }])
7705 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
7706 .border(BORDER_WIDTH)
7707 .border_color(cx.theme().colors().border)
7708 .rounded_r_lg()
7709 .id("edit_prediction_diff_popover_keybind")
7710 .when(!has_keybind, |el| {
7711 let status_colors = cx.theme().status();
7712
7713 el.bg(status_colors.error_background)
7714 .border_color(status_colors.error.opacity(0.6))
7715 .child(Icon::new(IconName::Info).color(Color::Error))
7716 .cursor_default()
7717 .hoverable_tooltip(move |_window, cx| {
7718 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7719 })
7720 })
7721 .children(keybind),
7722 )
7723 .into_any();
7724
7725 let longest_row =
7726 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
7727 let longest_line_width = if visible_row_range.contains(&longest_row) {
7728 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
7729 } else {
7730 layout_line(
7731 longest_row,
7732 editor_snapshot,
7733 style,
7734 editor_width,
7735 |_| false,
7736 window,
7737 cx,
7738 )
7739 .width
7740 };
7741
7742 let viewport_bounds =
7743 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
7744 right: -EditorElement::SCROLLBAR_WIDTH,
7745 ..Default::default()
7746 });
7747
7748 let x_after_longest =
7749 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
7750 - scroll_pixel_position.x;
7751
7752 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7753
7754 // Fully visible if it can be displayed within the window (allow overlapping other
7755 // panes). However, this is only allowed if the popover starts within text_bounds.
7756 let can_position_to_the_right = x_after_longest < text_bounds.right()
7757 && x_after_longest + element_bounds.width < viewport_bounds.right();
7758
7759 let mut origin = if can_position_to_the_right {
7760 point(
7761 x_after_longest,
7762 text_bounds.origin.y + edit_start.row().as_f32() * line_height
7763 - scroll_pixel_position.y,
7764 )
7765 } else {
7766 let cursor_row = newest_selection_head.map(|head| head.row());
7767 let above_edit = edit_start
7768 .row()
7769 .0
7770 .checked_sub(line_count as u32)
7771 .map(DisplayRow);
7772 let below_edit = Some(edit_end.row() + 1);
7773 let above_cursor =
7774 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
7775 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
7776
7777 // Place the edit popover adjacent to the edit if there is a location
7778 // available that is onscreen and does not obscure the cursor. Otherwise,
7779 // place it adjacent to the cursor.
7780 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
7781 .into_iter()
7782 .flatten()
7783 .find(|&start_row| {
7784 let end_row = start_row + line_count as u32;
7785 visible_row_range.contains(&start_row)
7786 && visible_row_range.contains(&end_row)
7787 && cursor_row.map_or(true, |cursor_row| {
7788 !((start_row..end_row).contains(&cursor_row))
7789 })
7790 })?;
7791
7792 content_origin
7793 + point(
7794 -scroll_pixel_position.x,
7795 row_target.as_f32() * line_height - scroll_pixel_position.y,
7796 )
7797 };
7798
7799 origin.x -= BORDER_WIDTH;
7800
7801 window.defer_draw(element, origin, 1);
7802
7803 // Do not return an element, since it will already be drawn due to defer_draw.
7804 None
7805 }
7806
7807 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
7808 px(30.)
7809 }
7810
7811 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
7812 if self.read_only(cx) {
7813 cx.theme().players().read_only()
7814 } else {
7815 self.style.as_ref().unwrap().local_player
7816 }
7817 }
7818
7819 fn render_edit_prediction_accept_keybind(
7820 &self,
7821 window: &mut Window,
7822 cx: &App,
7823 ) -> Option<AnyElement> {
7824 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
7825 let accept_keystroke = accept_binding.keystroke()?;
7826
7827 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
7828
7829 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
7830 Color::Accent
7831 } else {
7832 Color::Muted
7833 };
7834
7835 h_flex()
7836 .px_0p5()
7837 .when(is_platform_style_mac, |parent| parent.gap_0p5())
7838 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
7839 .text_size(TextSize::XSmall.rems(cx))
7840 .child(h_flex().children(ui::render_modifiers(
7841 &accept_keystroke.modifiers,
7842 PlatformStyle::platform(),
7843 Some(modifiers_color),
7844 Some(IconSize::XSmall.rems().into()),
7845 true,
7846 )))
7847 .when(is_platform_style_mac, |parent| {
7848 parent.child(accept_keystroke.key.clone())
7849 })
7850 .when(!is_platform_style_mac, |parent| {
7851 parent.child(
7852 Key::new(
7853 util::capitalize(&accept_keystroke.key),
7854 Some(Color::Default),
7855 )
7856 .size(Some(IconSize::XSmall.rems().into())),
7857 )
7858 })
7859 .into_any()
7860 .into()
7861 }
7862
7863 fn render_edit_prediction_line_popover(
7864 &self,
7865 label: impl Into<SharedString>,
7866 icon: Option<IconName>,
7867 window: &mut Window,
7868 cx: &App,
7869 ) -> Option<Stateful<Div>> {
7870 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
7871
7872 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
7873 let has_keybind = keybind.is_some();
7874
7875 let result = h_flex()
7876 .id("ep-line-popover")
7877 .py_0p5()
7878 .pl_1()
7879 .pr(padding_right)
7880 .gap_1()
7881 .rounded_md()
7882 .border_1()
7883 .bg(Self::edit_prediction_line_popover_bg_color(cx))
7884 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
7885 .shadow_sm()
7886 .when(!has_keybind, |el| {
7887 let status_colors = cx.theme().status();
7888
7889 el.bg(status_colors.error_background)
7890 .border_color(status_colors.error.opacity(0.6))
7891 .pl_2()
7892 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
7893 .cursor_default()
7894 .hoverable_tooltip(move |_window, cx| {
7895 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
7896 })
7897 })
7898 .children(keybind)
7899 .child(
7900 Label::new(label)
7901 .size(LabelSize::Small)
7902 .when(!has_keybind, |el| {
7903 el.color(cx.theme().status().error.into()).strikethrough()
7904 }),
7905 )
7906 .when(!has_keybind, |el| {
7907 el.child(
7908 h_flex().ml_1().child(
7909 Icon::new(IconName::Info)
7910 .size(IconSize::Small)
7911 .color(cx.theme().status().error.into()),
7912 ),
7913 )
7914 })
7915 .when_some(icon, |element, icon| {
7916 element.child(
7917 div()
7918 .mt(px(1.5))
7919 .child(Icon::new(icon).size(IconSize::Small)),
7920 )
7921 });
7922
7923 Some(result)
7924 }
7925
7926 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
7927 let accent_color = cx.theme().colors().text_accent;
7928 let editor_bg_color = cx.theme().colors().editor_background;
7929 editor_bg_color.blend(accent_color.opacity(0.1))
7930 }
7931
7932 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
7933 let accent_color = cx.theme().colors().text_accent;
7934 let editor_bg_color = cx.theme().colors().editor_background;
7935 editor_bg_color.blend(accent_color.opacity(0.6))
7936 }
7937
7938 fn render_edit_prediction_cursor_popover(
7939 &self,
7940 min_width: Pixels,
7941 max_width: Pixels,
7942 cursor_point: Point,
7943 style: &EditorStyle,
7944 accept_keystroke: Option<&gpui::Keystroke>,
7945 _window: &Window,
7946 cx: &mut Context<Editor>,
7947 ) -> Option<AnyElement> {
7948 let provider = self.edit_prediction_provider.as_ref()?;
7949
7950 if provider.provider.needs_terms_acceptance(cx) {
7951 return Some(
7952 h_flex()
7953 .min_w(min_width)
7954 .flex_1()
7955 .px_2()
7956 .py_1()
7957 .gap_3()
7958 .elevation_2(cx)
7959 .hover(|style| style.bg(cx.theme().colors().element_hover))
7960 .id("accept-terms")
7961 .cursor_pointer()
7962 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
7963 .on_click(cx.listener(|this, _event, window, cx| {
7964 cx.stop_propagation();
7965 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
7966 window.dispatch_action(
7967 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
7968 cx,
7969 );
7970 }))
7971 .child(
7972 h_flex()
7973 .flex_1()
7974 .gap_2()
7975 .child(Icon::new(IconName::ZedPredict))
7976 .child(Label::new("Accept Terms of Service"))
7977 .child(div().w_full())
7978 .child(
7979 Icon::new(IconName::ArrowUpRight)
7980 .color(Color::Muted)
7981 .size(IconSize::Small),
7982 )
7983 .into_any_element(),
7984 )
7985 .into_any(),
7986 );
7987 }
7988
7989 let is_refreshing = provider.provider.is_refreshing(cx);
7990
7991 fn pending_completion_container() -> Div {
7992 h_flex()
7993 .h_full()
7994 .flex_1()
7995 .gap_2()
7996 .child(Icon::new(IconName::ZedPredict))
7997 }
7998
7999 let completion = match &self.active_inline_completion {
8000 Some(prediction) => {
8001 if !self.has_visible_completions_menu() {
8002 const RADIUS: Pixels = px(6.);
8003 const BORDER_WIDTH: Pixels = px(1.);
8004
8005 return Some(
8006 h_flex()
8007 .elevation_2(cx)
8008 .border(BORDER_WIDTH)
8009 .border_color(cx.theme().colors().border)
8010 .when(accept_keystroke.is_none(), |el| {
8011 el.border_color(cx.theme().status().error)
8012 })
8013 .rounded(RADIUS)
8014 .rounded_tl(px(0.))
8015 .overflow_hidden()
8016 .child(div().px_1p5().child(match &prediction.completion {
8017 InlineCompletion::Move { target, snapshot } => {
8018 use text::ToPoint as _;
8019 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8020 {
8021 Icon::new(IconName::ZedPredictDown)
8022 } else {
8023 Icon::new(IconName::ZedPredictUp)
8024 }
8025 }
8026 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8027 }))
8028 .child(
8029 h_flex()
8030 .gap_1()
8031 .py_1()
8032 .px_2()
8033 .rounded_r(RADIUS - BORDER_WIDTH)
8034 .border_l_1()
8035 .border_color(cx.theme().colors().border)
8036 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8037 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8038 el.child(
8039 Label::new("Hold")
8040 .size(LabelSize::Small)
8041 .when(accept_keystroke.is_none(), |el| {
8042 el.strikethrough()
8043 })
8044 .line_height_style(LineHeightStyle::UiLabel),
8045 )
8046 })
8047 .id("edit_prediction_cursor_popover_keybind")
8048 .when(accept_keystroke.is_none(), |el| {
8049 let status_colors = cx.theme().status();
8050
8051 el.bg(status_colors.error_background)
8052 .border_color(status_colors.error.opacity(0.6))
8053 .child(Icon::new(IconName::Info).color(Color::Error))
8054 .cursor_default()
8055 .hoverable_tooltip(move |_window, cx| {
8056 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8057 .into()
8058 })
8059 })
8060 .when_some(
8061 accept_keystroke.as_ref(),
8062 |el, accept_keystroke| {
8063 el.child(h_flex().children(ui::render_modifiers(
8064 &accept_keystroke.modifiers,
8065 PlatformStyle::platform(),
8066 Some(Color::Default),
8067 Some(IconSize::XSmall.rems().into()),
8068 false,
8069 )))
8070 },
8071 ),
8072 )
8073 .into_any(),
8074 );
8075 }
8076
8077 self.render_edit_prediction_cursor_popover_preview(
8078 prediction,
8079 cursor_point,
8080 style,
8081 cx,
8082 )?
8083 }
8084
8085 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8086 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8087 stale_completion,
8088 cursor_point,
8089 style,
8090 cx,
8091 )?,
8092
8093 None => {
8094 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8095 }
8096 },
8097
8098 None => pending_completion_container().child(Label::new("No Prediction")),
8099 };
8100
8101 let completion = if is_refreshing {
8102 completion
8103 .with_animation(
8104 "loading-completion",
8105 Animation::new(Duration::from_secs(2))
8106 .repeat()
8107 .with_easing(pulsating_between(0.4, 0.8)),
8108 |label, delta| label.opacity(delta),
8109 )
8110 .into_any_element()
8111 } else {
8112 completion.into_any_element()
8113 };
8114
8115 let has_completion = self.active_inline_completion.is_some();
8116
8117 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8118 Some(
8119 h_flex()
8120 .min_w(min_width)
8121 .max_w(max_width)
8122 .flex_1()
8123 .elevation_2(cx)
8124 .border_color(cx.theme().colors().border)
8125 .child(
8126 div()
8127 .flex_1()
8128 .py_1()
8129 .px_2()
8130 .overflow_hidden()
8131 .child(completion),
8132 )
8133 .when_some(accept_keystroke, |el, accept_keystroke| {
8134 if !accept_keystroke.modifiers.modified() {
8135 return el;
8136 }
8137
8138 el.child(
8139 h_flex()
8140 .h_full()
8141 .border_l_1()
8142 .rounded_r_lg()
8143 .border_color(cx.theme().colors().border)
8144 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8145 .gap_1()
8146 .py_1()
8147 .px_2()
8148 .child(
8149 h_flex()
8150 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8151 .when(is_platform_style_mac, |parent| parent.gap_1())
8152 .child(h_flex().children(ui::render_modifiers(
8153 &accept_keystroke.modifiers,
8154 PlatformStyle::platform(),
8155 Some(if !has_completion {
8156 Color::Muted
8157 } else {
8158 Color::Default
8159 }),
8160 None,
8161 false,
8162 ))),
8163 )
8164 .child(Label::new("Preview").into_any_element())
8165 .opacity(if has_completion { 1.0 } else { 0.4 }),
8166 )
8167 })
8168 .into_any(),
8169 )
8170 }
8171
8172 fn render_edit_prediction_cursor_popover_preview(
8173 &self,
8174 completion: &InlineCompletionState,
8175 cursor_point: Point,
8176 style: &EditorStyle,
8177 cx: &mut Context<Editor>,
8178 ) -> Option<Div> {
8179 use text::ToPoint as _;
8180
8181 fn render_relative_row_jump(
8182 prefix: impl Into<String>,
8183 current_row: u32,
8184 target_row: u32,
8185 ) -> Div {
8186 let (row_diff, arrow) = if target_row < current_row {
8187 (current_row - target_row, IconName::ArrowUp)
8188 } else {
8189 (target_row - current_row, IconName::ArrowDown)
8190 };
8191
8192 h_flex()
8193 .child(
8194 Label::new(format!("{}{}", prefix.into(), row_diff))
8195 .color(Color::Muted)
8196 .size(LabelSize::Small),
8197 )
8198 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8199 }
8200
8201 match &completion.completion {
8202 InlineCompletion::Move {
8203 target, snapshot, ..
8204 } => Some(
8205 h_flex()
8206 .px_2()
8207 .gap_2()
8208 .flex_1()
8209 .child(
8210 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8211 Icon::new(IconName::ZedPredictDown)
8212 } else {
8213 Icon::new(IconName::ZedPredictUp)
8214 },
8215 )
8216 .child(Label::new("Jump to Edit")),
8217 ),
8218
8219 InlineCompletion::Edit {
8220 edits,
8221 edit_preview,
8222 snapshot,
8223 display_mode: _,
8224 } => {
8225 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8226
8227 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8228 &snapshot,
8229 &edits,
8230 edit_preview.as_ref()?,
8231 true,
8232 cx,
8233 )
8234 .first_line_preview();
8235
8236 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8237 .with_default_highlights(&style.text, highlighted_edits.highlights);
8238
8239 let preview = h_flex()
8240 .gap_1()
8241 .min_w_16()
8242 .child(styled_text)
8243 .when(has_more_lines, |parent| parent.child("…"));
8244
8245 let left = if first_edit_row != cursor_point.row {
8246 render_relative_row_jump("", cursor_point.row, first_edit_row)
8247 .into_any_element()
8248 } else {
8249 Icon::new(IconName::ZedPredict).into_any_element()
8250 };
8251
8252 Some(
8253 h_flex()
8254 .h_full()
8255 .flex_1()
8256 .gap_2()
8257 .pr_1()
8258 .overflow_x_hidden()
8259 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8260 .child(left)
8261 .child(preview),
8262 )
8263 }
8264 }
8265 }
8266
8267 fn render_context_menu(
8268 &self,
8269 style: &EditorStyle,
8270 max_height_in_lines: u32,
8271 window: &mut Window,
8272 cx: &mut Context<Editor>,
8273 ) -> Option<AnyElement> {
8274 let menu = self.context_menu.borrow();
8275 let menu = menu.as_ref()?;
8276 if !menu.visible() {
8277 return None;
8278 };
8279 Some(menu.render(style, max_height_in_lines, window, cx))
8280 }
8281
8282 fn render_context_menu_aside(
8283 &mut self,
8284 max_size: Size<Pixels>,
8285 window: &mut Window,
8286 cx: &mut Context<Editor>,
8287 ) -> Option<AnyElement> {
8288 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8289 if menu.visible() {
8290 menu.render_aside(self, max_size, window, cx)
8291 } else {
8292 None
8293 }
8294 })
8295 }
8296
8297 fn hide_context_menu(
8298 &mut self,
8299 window: &mut Window,
8300 cx: &mut Context<Self>,
8301 ) -> Option<CodeContextMenu> {
8302 cx.notify();
8303 self.completion_tasks.clear();
8304 let context_menu = self.context_menu.borrow_mut().take();
8305 self.stale_inline_completion_in_menu.take();
8306 self.update_visible_inline_completion(window, cx);
8307 context_menu
8308 }
8309
8310 fn show_snippet_choices(
8311 &mut self,
8312 choices: &Vec<String>,
8313 selection: Range<Anchor>,
8314 cx: &mut Context<Self>,
8315 ) {
8316 if selection.start.buffer_id.is_none() {
8317 return;
8318 }
8319 let buffer_id = selection.start.buffer_id.unwrap();
8320 let buffer = self.buffer().read(cx).buffer(buffer_id);
8321 let id = post_inc(&mut self.next_completion_id);
8322 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8323
8324 if let Some(buffer) = buffer {
8325 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8326 CompletionsMenu::new_snippet_choices(
8327 id,
8328 true,
8329 choices,
8330 selection,
8331 buffer,
8332 snippet_sort_order,
8333 ),
8334 ));
8335 }
8336 }
8337
8338 pub fn insert_snippet(
8339 &mut self,
8340 insertion_ranges: &[Range<usize>],
8341 snippet: Snippet,
8342 window: &mut Window,
8343 cx: &mut Context<Self>,
8344 ) -> Result<()> {
8345 struct Tabstop<T> {
8346 is_end_tabstop: bool,
8347 ranges: Vec<Range<T>>,
8348 choices: Option<Vec<String>>,
8349 }
8350
8351 let tabstops = self.buffer.update(cx, |buffer, cx| {
8352 let snippet_text: Arc<str> = snippet.text.clone().into();
8353 let edits = insertion_ranges
8354 .iter()
8355 .cloned()
8356 .map(|range| (range, snippet_text.clone()));
8357 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8358
8359 let snapshot = &*buffer.read(cx);
8360 let snippet = &snippet;
8361 snippet
8362 .tabstops
8363 .iter()
8364 .map(|tabstop| {
8365 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8366 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8367 });
8368 let mut tabstop_ranges = tabstop
8369 .ranges
8370 .iter()
8371 .flat_map(|tabstop_range| {
8372 let mut delta = 0_isize;
8373 insertion_ranges.iter().map(move |insertion_range| {
8374 let insertion_start = insertion_range.start as isize + delta;
8375 delta +=
8376 snippet.text.len() as isize - insertion_range.len() as isize;
8377
8378 let start = ((insertion_start + tabstop_range.start) as usize)
8379 .min(snapshot.len());
8380 let end = ((insertion_start + tabstop_range.end) as usize)
8381 .min(snapshot.len());
8382 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8383 })
8384 })
8385 .collect::<Vec<_>>();
8386 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8387
8388 Tabstop {
8389 is_end_tabstop,
8390 ranges: tabstop_ranges,
8391 choices: tabstop.choices.clone(),
8392 }
8393 })
8394 .collect::<Vec<_>>()
8395 });
8396 if let Some(tabstop) = tabstops.first() {
8397 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8398 s.select_ranges(tabstop.ranges.iter().cloned());
8399 });
8400
8401 if let Some(choices) = &tabstop.choices {
8402 if let Some(selection) = tabstop.ranges.first() {
8403 self.show_snippet_choices(choices, selection.clone(), cx)
8404 }
8405 }
8406
8407 // If we're already at the last tabstop and it's at the end of the snippet,
8408 // we're done, we don't need to keep the state around.
8409 if !tabstop.is_end_tabstop {
8410 let choices = tabstops
8411 .iter()
8412 .map(|tabstop| tabstop.choices.clone())
8413 .collect();
8414
8415 let ranges = tabstops
8416 .into_iter()
8417 .map(|tabstop| tabstop.ranges)
8418 .collect::<Vec<_>>();
8419
8420 self.snippet_stack.push(SnippetState {
8421 active_index: 0,
8422 ranges,
8423 choices,
8424 });
8425 }
8426
8427 // Check whether the just-entered snippet ends with an auto-closable bracket.
8428 if self.autoclose_regions.is_empty() {
8429 let snapshot = self.buffer.read(cx).snapshot(cx);
8430 for selection in &mut self.selections.all::<Point>(cx) {
8431 let selection_head = selection.head();
8432 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8433 continue;
8434 };
8435
8436 let mut bracket_pair = None;
8437 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8438 let prev_chars = snapshot
8439 .reversed_chars_at(selection_head)
8440 .collect::<String>();
8441 for (pair, enabled) in scope.brackets() {
8442 if enabled
8443 && pair.close
8444 && prev_chars.starts_with(pair.start.as_str())
8445 && next_chars.starts_with(pair.end.as_str())
8446 {
8447 bracket_pair = Some(pair.clone());
8448 break;
8449 }
8450 }
8451 if let Some(pair) = bracket_pair {
8452 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8453 let autoclose_enabled =
8454 self.use_autoclose && snapshot_settings.use_autoclose;
8455 if autoclose_enabled {
8456 let start = snapshot.anchor_after(selection_head);
8457 let end = snapshot.anchor_after(selection_head);
8458 self.autoclose_regions.push(AutocloseRegion {
8459 selection_id: selection.id,
8460 range: start..end,
8461 pair,
8462 });
8463 }
8464 }
8465 }
8466 }
8467 }
8468 Ok(())
8469 }
8470
8471 pub fn move_to_next_snippet_tabstop(
8472 &mut self,
8473 window: &mut Window,
8474 cx: &mut Context<Self>,
8475 ) -> bool {
8476 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8477 }
8478
8479 pub fn move_to_prev_snippet_tabstop(
8480 &mut self,
8481 window: &mut Window,
8482 cx: &mut Context<Self>,
8483 ) -> bool {
8484 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8485 }
8486
8487 pub fn move_to_snippet_tabstop(
8488 &mut self,
8489 bias: Bias,
8490 window: &mut Window,
8491 cx: &mut Context<Self>,
8492 ) -> bool {
8493 if let Some(mut snippet) = self.snippet_stack.pop() {
8494 match bias {
8495 Bias::Left => {
8496 if snippet.active_index > 0 {
8497 snippet.active_index -= 1;
8498 } else {
8499 self.snippet_stack.push(snippet);
8500 return false;
8501 }
8502 }
8503 Bias::Right => {
8504 if snippet.active_index + 1 < snippet.ranges.len() {
8505 snippet.active_index += 1;
8506 } else {
8507 self.snippet_stack.push(snippet);
8508 return false;
8509 }
8510 }
8511 }
8512 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8514 s.select_anchor_ranges(current_ranges.iter().cloned())
8515 });
8516
8517 if let Some(choices) = &snippet.choices[snippet.active_index] {
8518 if let Some(selection) = current_ranges.first() {
8519 self.show_snippet_choices(&choices, selection.clone(), cx);
8520 }
8521 }
8522
8523 // If snippet state is not at the last tabstop, push it back on the stack
8524 if snippet.active_index + 1 < snippet.ranges.len() {
8525 self.snippet_stack.push(snippet);
8526 }
8527 return true;
8528 }
8529 }
8530
8531 false
8532 }
8533
8534 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8535 self.transact(window, cx, |this, window, cx| {
8536 this.select_all(&SelectAll, window, cx);
8537 this.insert("", window, cx);
8538 });
8539 }
8540
8541 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8542 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8543 self.transact(window, cx, |this, window, cx| {
8544 this.select_autoclose_pair(window, cx);
8545 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8546 if !this.linked_edit_ranges.is_empty() {
8547 let selections = this.selections.all::<MultiBufferPoint>(cx);
8548 let snapshot = this.buffer.read(cx).snapshot(cx);
8549
8550 for selection in selections.iter() {
8551 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8552 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8553 if selection_start.buffer_id != selection_end.buffer_id {
8554 continue;
8555 }
8556 if let Some(ranges) =
8557 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8558 {
8559 for (buffer, entries) in ranges {
8560 linked_ranges.entry(buffer).or_default().extend(entries);
8561 }
8562 }
8563 }
8564 }
8565
8566 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8567 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8568 for selection in &mut selections {
8569 if selection.is_empty() {
8570 let old_head = selection.head();
8571 let mut new_head =
8572 movement::left(&display_map, old_head.to_display_point(&display_map))
8573 .to_point(&display_map);
8574 if let Some((buffer, line_buffer_range)) = display_map
8575 .buffer_snapshot
8576 .buffer_line_for_row(MultiBufferRow(old_head.row))
8577 {
8578 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8579 let indent_len = match indent_size.kind {
8580 IndentKind::Space => {
8581 buffer.settings_at(line_buffer_range.start, cx).tab_size
8582 }
8583 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8584 };
8585 if old_head.column <= indent_size.len && old_head.column > 0 {
8586 let indent_len = indent_len.get();
8587 new_head = cmp::min(
8588 new_head,
8589 MultiBufferPoint::new(
8590 old_head.row,
8591 ((old_head.column - 1) / indent_len) * indent_len,
8592 ),
8593 );
8594 }
8595 }
8596
8597 selection.set_head(new_head, SelectionGoal::None);
8598 }
8599 }
8600
8601 this.signature_help_state.set_backspace_pressed(true);
8602 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8603 s.select(selections)
8604 });
8605 this.insert("", window, cx);
8606 let empty_str: Arc<str> = Arc::from("");
8607 for (buffer, edits) in linked_ranges {
8608 let snapshot = buffer.read(cx).snapshot();
8609 use text::ToPoint as TP;
8610
8611 let edits = edits
8612 .into_iter()
8613 .map(|range| {
8614 let end_point = TP::to_point(&range.end, &snapshot);
8615 let mut start_point = TP::to_point(&range.start, &snapshot);
8616
8617 if end_point == start_point {
8618 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8619 .saturating_sub(1);
8620 start_point =
8621 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8622 };
8623
8624 (start_point..end_point, empty_str.clone())
8625 })
8626 .sorted_by_key(|(range, _)| range.start)
8627 .collect::<Vec<_>>();
8628 buffer.update(cx, |this, cx| {
8629 this.edit(edits, None, cx);
8630 })
8631 }
8632 this.refresh_inline_completion(true, false, window, cx);
8633 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8634 });
8635 }
8636
8637 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8638 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8639 self.transact(window, cx, |this, window, cx| {
8640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8641 s.move_with(|map, selection| {
8642 if selection.is_empty() {
8643 let cursor = movement::right(map, selection.head());
8644 selection.end = cursor;
8645 selection.reversed = true;
8646 selection.goal = SelectionGoal::None;
8647 }
8648 })
8649 });
8650 this.insert("", window, cx);
8651 this.refresh_inline_completion(true, false, window, cx);
8652 });
8653 }
8654
8655 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
8656 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8657 if self.move_to_prev_snippet_tabstop(window, cx) {
8658 return;
8659 }
8660 self.outdent(&Outdent, window, cx);
8661 }
8662
8663 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
8664 if self.move_to_next_snippet_tabstop(window, cx) {
8665 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8666 return;
8667 }
8668 if self.read_only(cx) {
8669 return;
8670 }
8671 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8672 let mut selections = self.selections.all_adjusted(cx);
8673 let buffer = self.buffer.read(cx);
8674 let snapshot = buffer.snapshot(cx);
8675 let rows_iter = selections.iter().map(|s| s.head().row);
8676 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
8677
8678 let has_some_cursor_in_whitespace = selections
8679 .iter()
8680 .filter(|selection| selection.is_empty())
8681 .any(|selection| {
8682 let cursor = selection.head();
8683 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8684 cursor.column < current_indent.len
8685 });
8686
8687 let mut edits = Vec::new();
8688 let mut prev_edited_row = 0;
8689 let mut row_delta = 0;
8690 for selection in &mut selections {
8691 if selection.start.row != prev_edited_row {
8692 row_delta = 0;
8693 }
8694 prev_edited_row = selection.end.row;
8695
8696 // If the selection is non-empty, then increase the indentation of the selected lines.
8697 if !selection.is_empty() {
8698 row_delta =
8699 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8700 continue;
8701 }
8702
8703 // If the selection is empty and the cursor is in the leading whitespace before the
8704 // suggested indentation, then auto-indent the line.
8705 let cursor = selection.head();
8706 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
8707 if let Some(suggested_indent) =
8708 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
8709 {
8710 // If there exist any empty selection in the leading whitespace, then skip
8711 // indent for selections at the boundary.
8712 if has_some_cursor_in_whitespace
8713 && cursor.column == current_indent.len
8714 && current_indent.len == suggested_indent.len
8715 {
8716 continue;
8717 }
8718
8719 if cursor.column < suggested_indent.len
8720 && cursor.column <= current_indent.len
8721 && current_indent.len <= suggested_indent.len
8722 {
8723 selection.start = Point::new(cursor.row, suggested_indent.len);
8724 selection.end = selection.start;
8725 if row_delta == 0 {
8726 edits.extend(Buffer::edit_for_indent_size_adjustment(
8727 cursor.row,
8728 current_indent,
8729 suggested_indent,
8730 ));
8731 row_delta = suggested_indent.len - current_indent.len;
8732 }
8733 continue;
8734 }
8735 }
8736
8737 // Otherwise, insert a hard or soft tab.
8738 let settings = buffer.language_settings_at(cursor, cx);
8739 let tab_size = if settings.hard_tabs {
8740 IndentSize::tab()
8741 } else {
8742 let tab_size = settings.tab_size.get();
8743 let indent_remainder = snapshot
8744 .text_for_range(Point::new(cursor.row, 0)..cursor)
8745 .flat_map(str::chars)
8746 .fold(row_delta % tab_size, |counter: u32, c| {
8747 if c == '\t' {
8748 0
8749 } else {
8750 (counter + 1) % tab_size
8751 }
8752 });
8753
8754 let chars_to_next_tab_stop = tab_size - indent_remainder;
8755 IndentSize::spaces(chars_to_next_tab_stop)
8756 };
8757 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
8758 selection.end = selection.start;
8759 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
8760 row_delta += tab_size.len;
8761 }
8762
8763 self.transact(window, cx, |this, window, cx| {
8764 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8765 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8766 s.select(selections)
8767 });
8768 this.refresh_inline_completion(true, false, window, cx);
8769 });
8770 }
8771
8772 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
8773 if self.read_only(cx) {
8774 return;
8775 }
8776 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8777 let mut selections = self.selections.all::<Point>(cx);
8778 let mut prev_edited_row = 0;
8779 let mut row_delta = 0;
8780 let mut edits = Vec::new();
8781 let buffer = self.buffer.read(cx);
8782 let snapshot = buffer.snapshot(cx);
8783 for selection in &mut selections {
8784 if selection.start.row != prev_edited_row {
8785 row_delta = 0;
8786 }
8787 prev_edited_row = selection.end.row;
8788
8789 row_delta =
8790 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
8791 }
8792
8793 self.transact(window, cx, |this, window, cx| {
8794 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
8795 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8796 s.select(selections)
8797 });
8798 });
8799 }
8800
8801 fn indent_selection(
8802 buffer: &MultiBuffer,
8803 snapshot: &MultiBufferSnapshot,
8804 selection: &mut Selection<Point>,
8805 edits: &mut Vec<(Range<Point>, String)>,
8806 delta_for_start_row: u32,
8807 cx: &App,
8808 ) -> u32 {
8809 let settings = buffer.language_settings_at(selection.start, cx);
8810 let tab_size = settings.tab_size.get();
8811 let indent_kind = if settings.hard_tabs {
8812 IndentKind::Tab
8813 } else {
8814 IndentKind::Space
8815 };
8816 let mut start_row = selection.start.row;
8817 let mut end_row = selection.end.row + 1;
8818
8819 // If a selection ends at the beginning of a line, don't indent
8820 // that last line.
8821 if selection.end.column == 0 && selection.end.row > selection.start.row {
8822 end_row -= 1;
8823 }
8824
8825 // Avoid re-indenting a row that has already been indented by a
8826 // previous selection, but still update this selection's column
8827 // to reflect that indentation.
8828 if delta_for_start_row > 0 {
8829 start_row += 1;
8830 selection.start.column += delta_for_start_row;
8831 if selection.end.row == selection.start.row {
8832 selection.end.column += delta_for_start_row;
8833 }
8834 }
8835
8836 let mut delta_for_end_row = 0;
8837 let has_multiple_rows = start_row + 1 != end_row;
8838 for row in start_row..end_row {
8839 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
8840 let indent_delta = match (current_indent.kind, indent_kind) {
8841 (IndentKind::Space, IndentKind::Space) => {
8842 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
8843 IndentSize::spaces(columns_to_next_tab_stop)
8844 }
8845 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
8846 (_, IndentKind::Tab) => IndentSize::tab(),
8847 };
8848
8849 let start = if has_multiple_rows || current_indent.len < selection.start.column {
8850 0
8851 } else {
8852 selection.start.column
8853 };
8854 let row_start = Point::new(row, start);
8855 edits.push((
8856 row_start..row_start,
8857 indent_delta.chars().collect::<String>(),
8858 ));
8859
8860 // Update this selection's endpoints to reflect the indentation.
8861 if row == selection.start.row {
8862 selection.start.column += indent_delta.len;
8863 }
8864 if row == selection.end.row {
8865 selection.end.column += indent_delta.len;
8866 delta_for_end_row = indent_delta.len;
8867 }
8868 }
8869
8870 if selection.start.row == selection.end.row {
8871 delta_for_start_row + delta_for_end_row
8872 } else {
8873 delta_for_end_row
8874 }
8875 }
8876
8877 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
8878 if self.read_only(cx) {
8879 return;
8880 }
8881 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8882 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8883 let selections = self.selections.all::<Point>(cx);
8884 let mut deletion_ranges = Vec::new();
8885 let mut last_outdent = None;
8886 {
8887 let buffer = self.buffer.read(cx);
8888 let snapshot = buffer.snapshot(cx);
8889 for selection in &selections {
8890 let settings = buffer.language_settings_at(selection.start, cx);
8891 let tab_size = settings.tab_size.get();
8892 let mut rows = selection.spanned_rows(false, &display_map);
8893
8894 // Avoid re-outdenting a row that has already been outdented by a
8895 // previous selection.
8896 if let Some(last_row) = last_outdent {
8897 if last_row == rows.start {
8898 rows.start = rows.start.next_row();
8899 }
8900 }
8901 let has_multiple_rows = rows.len() > 1;
8902 for row in rows.iter_rows() {
8903 let indent_size = snapshot.indent_size_for_line(row);
8904 if indent_size.len > 0 {
8905 let deletion_len = match indent_size.kind {
8906 IndentKind::Space => {
8907 let columns_to_prev_tab_stop = indent_size.len % tab_size;
8908 if columns_to_prev_tab_stop == 0 {
8909 tab_size
8910 } else {
8911 columns_to_prev_tab_stop
8912 }
8913 }
8914 IndentKind::Tab => 1,
8915 };
8916 let start = if has_multiple_rows
8917 || deletion_len > selection.start.column
8918 || indent_size.len < selection.start.column
8919 {
8920 0
8921 } else {
8922 selection.start.column - deletion_len
8923 };
8924 deletion_ranges.push(
8925 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
8926 );
8927 last_outdent = Some(row);
8928 }
8929 }
8930 }
8931 }
8932
8933 self.transact(window, cx, |this, window, cx| {
8934 this.buffer.update(cx, |buffer, cx| {
8935 let empty_str: Arc<str> = Arc::default();
8936 buffer.edit(
8937 deletion_ranges
8938 .into_iter()
8939 .map(|range| (range, empty_str.clone())),
8940 None,
8941 cx,
8942 );
8943 });
8944 let selections = this.selections.all::<usize>(cx);
8945 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8946 s.select(selections)
8947 });
8948 });
8949 }
8950
8951 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
8952 if self.read_only(cx) {
8953 return;
8954 }
8955 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8956 let selections = self
8957 .selections
8958 .all::<usize>(cx)
8959 .into_iter()
8960 .map(|s| s.range());
8961
8962 self.transact(window, cx, |this, window, cx| {
8963 this.buffer.update(cx, |buffer, cx| {
8964 buffer.autoindent_ranges(selections, cx);
8965 });
8966 let selections = this.selections.all::<usize>(cx);
8967 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8968 s.select(selections)
8969 });
8970 });
8971 }
8972
8973 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
8974 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8975 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
8976 let selections = self.selections.all::<Point>(cx);
8977
8978 let mut new_cursors = Vec::new();
8979 let mut edit_ranges = Vec::new();
8980 let mut selections = selections.iter().peekable();
8981 while let Some(selection) = selections.next() {
8982 let mut rows = selection.spanned_rows(false, &display_map);
8983 let goal_display_column = selection.head().to_display_point(&display_map).column();
8984
8985 // Accumulate contiguous regions of rows that we want to delete.
8986 while let Some(next_selection) = selections.peek() {
8987 let next_rows = next_selection.spanned_rows(false, &display_map);
8988 if next_rows.start <= rows.end {
8989 rows.end = next_rows.end;
8990 selections.next().unwrap();
8991 } else {
8992 break;
8993 }
8994 }
8995
8996 let buffer = &display_map.buffer_snapshot;
8997 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
8998 let edit_end;
8999 let cursor_buffer_row;
9000 if buffer.max_point().row >= rows.end.0 {
9001 // If there's a line after the range, delete the \n from the end of the row range
9002 // and position the cursor on the next line.
9003 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9004 cursor_buffer_row = rows.end;
9005 } else {
9006 // If there isn't a line after the range, delete the \n from the line before the
9007 // start of the row range and position the cursor there.
9008 edit_start = edit_start.saturating_sub(1);
9009 edit_end = buffer.len();
9010 cursor_buffer_row = rows.start.previous_row();
9011 }
9012
9013 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9014 *cursor.column_mut() =
9015 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9016
9017 new_cursors.push((
9018 selection.id,
9019 buffer.anchor_after(cursor.to_point(&display_map)),
9020 ));
9021 edit_ranges.push(edit_start..edit_end);
9022 }
9023
9024 self.transact(window, cx, |this, window, cx| {
9025 let buffer = this.buffer.update(cx, |buffer, cx| {
9026 let empty_str: Arc<str> = Arc::default();
9027 buffer.edit(
9028 edit_ranges
9029 .into_iter()
9030 .map(|range| (range, empty_str.clone())),
9031 None,
9032 cx,
9033 );
9034 buffer.snapshot(cx)
9035 });
9036 let new_selections = new_cursors
9037 .into_iter()
9038 .map(|(id, cursor)| {
9039 let cursor = cursor.to_point(&buffer);
9040 Selection {
9041 id,
9042 start: cursor,
9043 end: cursor,
9044 reversed: false,
9045 goal: SelectionGoal::None,
9046 }
9047 })
9048 .collect();
9049
9050 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9051 s.select(new_selections);
9052 });
9053 });
9054 }
9055
9056 pub fn join_lines_impl(
9057 &mut self,
9058 insert_whitespace: bool,
9059 window: &mut Window,
9060 cx: &mut Context<Self>,
9061 ) {
9062 if self.read_only(cx) {
9063 return;
9064 }
9065 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9066 for selection in self.selections.all::<Point>(cx) {
9067 let start = MultiBufferRow(selection.start.row);
9068 // Treat single line selections as if they include the next line. Otherwise this action
9069 // would do nothing for single line selections individual cursors.
9070 let end = if selection.start.row == selection.end.row {
9071 MultiBufferRow(selection.start.row + 1)
9072 } else {
9073 MultiBufferRow(selection.end.row)
9074 };
9075
9076 if let Some(last_row_range) = row_ranges.last_mut() {
9077 if start <= last_row_range.end {
9078 last_row_range.end = end;
9079 continue;
9080 }
9081 }
9082 row_ranges.push(start..end);
9083 }
9084
9085 let snapshot = self.buffer.read(cx).snapshot(cx);
9086 let mut cursor_positions = Vec::new();
9087 for row_range in &row_ranges {
9088 let anchor = snapshot.anchor_before(Point::new(
9089 row_range.end.previous_row().0,
9090 snapshot.line_len(row_range.end.previous_row()),
9091 ));
9092 cursor_positions.push(anchor..anchor);
9093 }
9094
9095 self.transact(window, cx, |this, window, cx| {
9096 for row_range in row_ranges.into_iter().rev() {
9097 for row in row_range.iter_rows().rev() {
9098 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9099 let next_line_row = row.next_row();
9100 let indent = snapshot.indent_size_for_line(next_line_row);
9101 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9102
9103 let replace =
9104 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9105 " "
9106 } else {
9107 ""
9108 };
9109
9110 this.buffer.update(cx, |buffer, cx| {
9111 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9112 });
9113 }
9114 }
9115
9116 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9117 s.select_anchor_ranges(cursor_positions)
9118 });
9119 });
9120 }
9121
9122 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9123 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9124 self.join_lines_impl(true, window, cx);
9125 }
9126
9127 pub fn sort_lines_case_sensitive(
9128 &mut self,
9129 _: &SortLinesCaseSensitive,
9130 window: &mut Window,
9131 cx: &mut Context<Self>,
9132 ) {
9133 self.manipulate_lines(window, cx, |lines| lines.sort())
9134 }
9135
9136 pub fn sort_lines_case_insensitive(
9137 &mut self,
9138 _: &SortLinesCaseInsensitive,
9139 window: &mut Window,
9140 cx: &mut Context<Self>,
9141 ) {
9142 self.manipulate_lines(window, cx, |lines| {
9143 lines.sort_by_key(|line| line.to_lowercase())
9144 })
9145 }
9146
9147 pub fn unique_lines_case_insensitive(
9148 &mut self,
9149 _: &UniqueLinesCaseInsensitive,
9150 window: &mut Window,
9151 cx: &mut Context<Self>,
9152 ) {
9153 self.manipulate_lines(window, cx, |lines| {
9154 let mut seen = HashSet::default();
9155 lines.retain(|line| seen.insert(line.to_lowercase()));
9156 })
9157 }
9158
9159 pub fn unique_lines_case_sensitive(
9160 &mut self,
9161 _: &UniqueLinesCaseSensitive,
9162 window: &mut Window,
9163 cx: &mut Context<Self>,
9164 ) {
9165 self.manipulate_lines(window, cx, |lines| {
9166 let mut seen = HashSet::default();
9167 lines.retain(|line| seen.insert(*line));
9168 })
9169 }
9170
9171 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9172 let Some(project) = self.project.clone() else {
9173 return;
9174 };
9175 self.reload(project, window, cx)
9176 .detach_and_notify_err(window, cx);
9177 }
9178
9179 pub fn restore_file(
9180 &mut self,
9181 _: &::git::RestoreFile,
9182 window: &mut Window,
9183 cx: &mut Context<Self>,
9184 ) {
9185 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9186 let mut buffer_ids = HashSet::default();
9187 let snapshot = self.buffer().read(cx).snapshot(cx);
9188 for selection in self.selections.all::<usize>(cx) {
9189 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9190 }
9191
9192 let buffer = self.buffer().read(cx);
9193 let ranges = buffer_ids
9194 .into_iter()
9195 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9196 .collect::<Vec<_>>();
9197
9198 self.restore_hunks_in_ranges(ranges, window, cx);
9199 }
9200
9201 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9202 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9203 let selections = self
9204 .selections
9205 .all(cx)
9206 .into_iter()
9207 .map(|s| s.range())
9208 .collect();
9209 self.restore_hunks_in_ranges(selections, window, cx);
9210 }
9211
9212 pub fn restore_hunks_in_ranges(
9213 &mut self,
9214 ranges: Vec<Range<Point>>,
9215 window: &mut Window,
9216 cx: &mut Context<Editor>,
9217 ) {
9218 let mut revert_changes = HashMap::default();
9219 let chunk_by = self
9220 .snapshot(window, cx)
9221 .hunks_for_ranges(ranges)
9222 .into_iter()
9223 .chunk_by(|hunk| hunk.buffer_id);
9224 for (buffer_id, hunks) in &chunk_by {
9225 let hunks = hunks.collect::<Vec<_>>();
9226 for hunk in &hunks {
9227 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9228 }
9229 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9230 }
9231 drop(chunk_by);
9232 if !revert_changes.is_empty() {
9233 self.transact(window, cx, |editor, window, cx| {
9234 editor.restore(revert_changes, window, cx);
9235 });
9236 }
9237 }
9238
9239 pub fn open_active_item_in_terminal(
9240 &mut self,
9241 _: &OpenInTerminal,
9242 window: &mut Window,
9243 cx: &mut Context<Self>,
9244 ) {
9245 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9246 let project_path = buffer.read(cx).project_path(cx)?;
9247 let project = self.project.as_ref()?.read(cx);
9248 let entry = project.entry_for_path(&project_path, cx)?;
9249 let parent = match &entry.canonical_path {
9250 Some(canonical_path) => canonical_path.to_path_buf(),
9251 None => project.absolute_path(&project_path, cx)?,
9252 }
9253 .parent()?
9254 .to_path_buf();
9255 Some(parent)
9256 }) {
9257 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9258 }
9259 }
9260
9261 fn set_breakpoint_context_menu(
9262 &mut self,
9263 display_row: DisplayRow,
9264 position: Option<Anchor>,
9265 clicked_point: gpui::Point<Pixels>,
9266 window: &mut Window,
9267 cx: &mut Context<Self>,
9268 ) {
9269 if !cx.has_flag::<DebuggerFeatureFlag>() {
9270 return;
9271 }
9272 let source = self
9273 .buffer
9274 .read(cx)
9275 .snapshot(cx)
9276 .anchor_before(Point::new(display_row.0, 0u32));
9277
9278 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9279
9280 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9281 self,
9282 source,
9283 clicked_point,
9284 context_menu,
9285 window,
9286 cx,
9287 );
9288 }
9289
9290 fn add_edit_breakpoint_block(
9291 &mut self,
9292 anchor: Anchor,
9293 breakpoint: &Breakpoint,
9294 edit_action: BreakpointPromptEditAction,
9295 window: &mut Window,
9296 cx: &mut Context<Self>,
9297 ) {
9298 let weak_editor = cx.weak_entity();
9299 let bp_prompt = cx.new(|cx| {
9300 BreakpointPromptEditor::new(
9301 weak_editor,
9302 anchor,
9303 breakpoint.clone(),
9304 edit_action,
9305 window,
9306 cx,
9307 )
9308 });
9309
9310 let height = bp_prompt.update(cx, |this, cx| {
9311 this.prompt
9312 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9313 });
9314 let cloned_prompt = bp_prompt.clone();
9315 let blocks = vec![BlockProperties {
9316 style: BlockStyle::Sticky,
9317 placement: BlockPlacement::Above(anchor),
9318 height: Some(height),
9319 render: Arc::new(move |cx| {
9320 *cloned_prompt.read(cx).gutter_dimensions.lock() = *cx.gutter_dimensions;
9321 cloned_prompt.clone().into_any_element()
9322 }),
9323 priority: 0,
9324 }];
9325
9326 let focus_handle = bp_prompt.focus_handle(cx);
9327 window.focus(&focus_handle);
9328
9329 let block_ids = self.insert_blocks(blocks, None, cx);
9330 bp_prompt.update(cx, |prompt, _| {
9331 prompt.add_block_ids(block_ids);
9332 });
9333 }
9334
9335 pub(crate) fn breakpoint_at_row(
9336 &self,
9337 row: u32,
9338 window: &mut Window,
9339 cx: &mut Context<Self>,
9340 ) -> Option<(Anchor, Breakpoint)> {
9341 let snapshot = self.snapshot(window, cx);
9342 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9343
9344 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9345 }
9346
9347 pub(crate) fn breakpoint_at_anchor(
9348 &self,
9349 breakpoint_position: Anchor,
9350 snapshot: &EditorSnapshot,
9351 cx: &mut Context<Self>,
9352 ) -> Option<(Anchor, Breakpoint)> {
9353 let project = self.project.clone()?;
9354
9355 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9356 snapshot
9357 .buffer_snapshot
9358 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9359 })?;
9360
9361 let enclosing_excerpt = breakpoint_position.excerpt_id;
9362 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9363 let buffer_snapshot = buffer.read(cx).snapshot();
9364
9365 let row = buffer_snapshot
9366 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9367 .row;
9368
9369 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9370 let anchor_end = snapshot
9371 .buffer_snapshot
9372 .anchor_after(Point::new(row, line_len));
9373
9374 let bp = self
9375 .breakpoint_store
9376 .as_ref()?
9377 .read_with(cx, |breakpoint_store, cx| {
9378 breakpoint_store
9379 .breakpoints(
9380 &buffer,
9381 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9382 &buffer_snapshot,
9383 cx,
9384 )
9385 .next()
9386 .and_then(|(anchor, bp)| {
9387 let breakpoint_row = buffer_snapshot
9388 .summary_for_anchor::<text::PointUtf16>(anchor)
9389 .row;
9390
9391 if breakpoint_row == row {
9392 snapshot
9393 .buffer_snapshot
9394 .anchor_in_excerpt(enclosing_excerpt, *anchor)
9395 .map(|anchor| (anchor, bp.clone()))
9396 } else {
9397 None
9398 }
9399 })
9400 });
9401 bp
9402 }
9403
9404 pub fn edit_log_breakpoint(
9405 &mut self,
9406 _: &EditLogBreakpoint,
9407 window: &mut Window,
9408 cx: &mut Context<Self>,
9409 ) {
9410 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9411 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9412 message: None,
9413 state: BreakpointState::Enabled,
9414 condition: None,
9415 hit_condition: None,
9416 });
9417
9418 self.add_edit_breakpoint_block(
9419 anchor,
9420 &breakpoint,
9421 BreakpointPromptEditAction::Log,
9422 window,
9423 cx,
9424 );
9425 }
9426 }
9427
9428 fn breakpoints_at_cursors(
9429 &self,
9430 window: &mut Window,
9431 cx: &mut Context<Self>,
9432 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9433 let snapshot = self.snapshot(window, cx);
9434 let cursors = self
9435 .selections
9436 .disjoint_anchors()
9437 .into_iter()
9438 .map(|selection| {
9439 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9440
9441 let breakpoint_position = self
9442 .breakpoint_at_row(cursor_position.row, window, cx)
9443 .map(|bp| bp.0)
9444 .unwrap_or_else(|| {
9445 snapshot
9446 .display_snapshot
9447 .buffer_snapshot
9448 .anchor_after(Point::new(cursor_position.row, 0))
9449 });
9450
9451 let breakpoint = self
9452 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9453 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9454
9455 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9456 })
9457 // 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.
9458 .collect::<HashMap<Anchor, _>>();
9459
9460 cursors.into_iter().collect()
9461 }
9462
9463 pub fn enable_breakpoint(
9464 &mut self,
9465 _: &crate::actions::EnableBreakpoint,
9466 window: &mut Window,
9467 cx: &mut Context<Self>,
9468 ) {
9469 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9470 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9471 continue;
9472 };
9473 self.edit_breakpoint_at_anchor(
9474 anchor,
9475 breakpoint,
9476 BreakpointEditAction::InvertState,
9477 cx,
9478 );
9479 }
9480 }
9481
9482 pub fn disable_breakpoint(
9483 &mut self,
9484 _: &crate::actions::DisableBreakpoint,
9485 window: &mut Window,
9486 cx: &mut Context<Self>,
9487 ) {
9488 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9489 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9490 continue;
9491 };
9492 self.edit_breakpoint_at_anchor(
9493 anchor,
9494 breakpoint,
9495 BreakpointEditAction::InvertState,
9496 cx,
9497 );
9498 }
9499 }
9500
9501 pub fn toggle_breakpoint(
9502 &mut self,
9503 _: &crate::actions::ToggleBreakpoint,
9504 window: &mut Window,
9505 cx: &mut Context<Self>,
9506 ) {
9507 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9508 if let Some(breakpoint) = breakpoint {
9509 self.edit_breakpoint_at_anchor(
9510 anchor,
9511 breakpoint,
9512 BreakpointEditAction::Toggle,
9513 cx,
9514 );
9515 } else {
9516 self.edit_breakpoint_at_anchor(
9517 anchor,
9518 Breakpoint::new_standard(),
9519 BreakpointEditAction::Toggle,
9520 cx,
9521 );
9522 }
9523 }
9524 }
9525
9526 pub fn edit_breakpoint_at_anchor(
9527 &mut self,
9528 breakpoint_position: Anchor,
9529 breakpoint: Breakpoint,
9530 edit_action: BreakpointEditAction,
9531 cx: &mut Context<Self>,
9532 ) {
9533 let Some(breakpoint_store) = &self.breakpoint_store else {
9534 return;
9535 };
9536
9537 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9538 if breakpoint_position == Anchor::min() {
9539 self.buffer()
9540 .read(cx)
9541 .excerpt_buffer_ids()
9542 .into_iter()
9543 .next()
9544 } else {
9545 None
9546 }
9547 }) else {
9548 return;
9549 };
9550
9551 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9552 return;
9553 };
9554
9555 breakpoint_store.update(cx, |breakpoint_store, cx| {
9556 breakpoint_store.toggle_breakpoint(
9557 buffer,
9558 (breakpoint_position.text_anchor, breakpoint),
9559 edit_action,
9560 cx,
9561 );
9562 });
9563
9564 cx.notify();
9565 }
9566
9567 #[cfg(any(test, feature = "test-support"))]
9568 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9569 self.breakpoint_store.clone()
9570 }
9571
9572 pub fn prepare_restore_change(
9573 &self,
9574 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9575 hunk: &MultiBufferDiffHunk,
9576 cx: &mut App,
9577 ) -> Option<()> {
9578 if hunk.is_created_file() {
9579 return None;
9580 }
9581 let buffer = self.buffer.read(cx);
9582 let diff = buffer.diff_for(hunk.buffer_id)?;
9583 let buffer = buffer.buffer(hunk.buffer_id)?;
9584 let buffer = buffer.read(cx);
9585 let original_text = diff
9586 .read(cx)
9587 .base_text()
9588 .as_rope()
9589 .slice(hunk.diff_base_byte_range.clone());
9590 let buffer_snapshot = buffer.snapshot();
9591 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9592 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9593 probe
9594 .0
9595 .start
9596 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9597 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9598 }) {
9599 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9600 Some(())
9601 } else {
9602 None
9603 }
9604 }
9605
9606 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9607 self.manipulate_lines(window, cx, |lines| lines.reverse())
9608 }
9609
9610 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9611 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9612 }
9613
9614 fn manipulate_lines<Fn>(
9615 &mut self,
9616 window: &mut Window,
9617 cx: &mut Context<Self>,
9618 mut callback: Fn,
9619 ) where
9620 Fn: FnMut(&mut Vec<&str>),
9621 {
9622 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9623
9624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9625 let buffer = self.buffer.read(cx).snapshot(cx);
9626
9627 let mut edits = Vec::new();
9628
9629 let selections = self.selections.all::<Point>(cx);
9630 let mut selections = selections.iter().peekable();
9631 let mut contiguous_row_selections = Vec::new();
9632 let mut new_selections = Vec::new();
9633 let mut added_lines = 0;
9634 let mut removed_lines = 0;
9635
9636 while let Some(selection) = selections.next() {
9637 let (start_row, end_row) = consume_contiguous_rows(
9638 &mut contiguous_row_selections,
9639 selection,
9640 &display_map,
9641 &mut selections,
9642 );
9643
9644 let start_point = Point::new(start_row.0, 0);
9645 let end_point = Point::new(
9646 end_row.previous_row().0,
9647 buffer.line_len(end_row.previous_row()),
9648 );
9649 let text = buffer
9650 .text_for_range(start_point..end_point)
9651 .collect::<String>();
9652
9653 let mut lines = text.split('\n').collect_vec();
9654
9655 let lines_before = lines.len();
9656 callback(&mut lines);
9657 let lines_after = lines.len();
9658
9659 edits.push((start_point..end_point, lines.join("\n")));
9660
9661 // Selections must change based on added and removed line count
9662 let start_row =
9663 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
9664 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
9665 new_selections.push(Selection {
9666 id: selection.id,
9667 start: start_row,
9668 end: end_row,
9669 goal: SelectionGoal::None,
9670 reversed: selection.reversed,
9671 });
9672
9673 if lines_after > lines_before {
9674 added_lines += lines_after - lines_before;
9675 } else if lines_before > lines_after {
9676 removed_lines += lines_before - lines_after;
9677 }
9678 }
9679
9680 self.transact(window, cx, |this, window, cx| {
9681 let buffer = this.buffer.update(cx, |buffer, cx| {
9682 buffer.edit(edits, None, cx);
9683 buffer.snapshot(cx)
9684 });
9685
9686 // Recalculate offsets on newly edited buffer
9687 let new_selections = new_selections
9688 .iter()
9689 .map(|s| {
9690 let start_point = Point::new(s.start.0, 0);
9691 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
9692 Selection {
9693 id: s.id,
9694 start: buffer.point_to_offset(start_point),
9695 end: buffer.point_to_offset(end_point),
9696 goal: s.goal,
9697 reversed: s.reversed,
9698 }
9699 })
9700 .collect();
9701
9702 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9703 s.select(new_selections);
9704 });
9705
9706 this.request_autoscroll(Autoscroll::fit(), cx);
9707 });
9708 }
9709
9710 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
9711 self.manipulate_text(window, cx, |text| {
9712 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
9713 if has_upper_case_characters {
9714 text.to_lowercase()
9715 } else {
9716 text.to_uppercase()
9717 }
9718 })
9719 }
9720
9721 pub fn convert_to_upper_case(
9722 &mut self,
9723 _: &ConvertToUpperCase,
9724 window: &mut Window,
9725 cx: &mut Context<Self>,
9726 ) {
9727 self.manipulate_text(window, cx, |text| text.to_uppercase())
9728 }
9729
9730 pub fn convert_to_lower_case(
9731 &mut self,
9732 _: &ConvertToLowerCase,
9733 window: &mut Window,
9734 cx: &mut Context<Self>,
9735 ) {
9736 self.manipulate_text(window, cx, |text| text.to_lowercase())
9737 }
9738
9739 pub fn convert_to_title_case(
9740 &mut self,
9741 _: &ConvertToTitleCase,
9742 window: &mut Window,
9743 cx: &mut Context<Self>,
9744 ) {
9745 self.manipulate_text(window, cx, |text| {
9746 text.split('\n')
9747 .map(|line| line.to_case(Case::Title))
9748 .join("\n")
9749 })
9750 }
9751
9752 pub fn convert_to_snake_case(
9753 &mut self,
9754 _: &ConvertToSnakeCase,
9755 window: &mut Window,
9756 cx: &mut Context<Self>,
9757 ) {
9758 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
9759 }
9760
9761 pub fn convert_to_kebab_case(
9762 &mut self,
9763 _: &ConvertToKebabCase,
9764 window: &mut Window,
9765 cx: &mut Context<Self>,
9766 ) {
9767 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
9768 }
9769
9770 pub fn convert_to_upper_camel_case(
9771 &mut self,
9772 _: &ConvertToUpperCamelCase,
9773 window: &mut Window,
9774 cx: &mut Context<Self>,
9775 ) {
9776 self.manipulate_text(window, cx, |text| {
9777 text.split('\n')
9778 .map(|line| line.to_case(Case::UpperCamel))
9779 .join("\n")
9780 })
9781 }
9782
9783 pub fn convert_to_lower_camel_case(
9784 &mut self,
9785 _: &ConvertToLowerCamelCase,
9786 window: &mut Window,
9787 cx: &mut Context<Self>,
9788 ) {
9789 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
9790 }
9791
9792 pub fn convert_to_opposite_case(
9793 &mut self,
9794 _: &ConvertToOppositeCase,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) {
9798 self.manipulate_text(window, cx, |text| {
9799 text.chars()
9800 .fold(String::with_capacity(text.len()), |mut t, c| {
9801 if c.is_uppercase() {
9802 t.extend(c.to_lowercase());
9803 } else {
9804 t.extend(c.to_uppercase());
9805 }
9806 t
9807 })
9808 })
9809 }
9810
9811 pub fn convert_to_rot13(
9812 &mut self,
9813 _: &ConvertToRot13,
9814 window: &mut Window,
9815 cx: &mut Context<Self>,
9816 ) {
9817 self.manipulate_text(window, cx, |text| {
9818 text.chars()
9819 .map(|c| match c {
9820 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
9821 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
9822 _ => c,
9823 })
9824 .collect()
9825 })
9826 }
9827
9828 pub fn convert_to_rot47(
9829 &mut self,
9830 _: &ConvertToRot47,
9831 window: &mut Window,
9832 cx: &mut Context<Self>,
9833 ) {
9834 self.manipulate_text(window, cx, |text| {
9835 text.chars()
9836 .map(|c| {
9837 let code_point = c as u32;
9838 if code_point >= 33 && code_point <= 126 {
9839 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
9840 }
9841 c
9842 })
9843 .collect()
9844 })
9845 }
9846
9847 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
9848 where
9849 Fn: FnMut(&str) -> String,
9850 {
9851 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9852 let buffer = self.buffer.read(cx).snapshot(cx);
9853
9854 let mut new_selections = Vec::new();
9855 let mut edits = Vec::new();
9856 let mut selection_adjustment = 0i32;
9857
9858 for selection in self.selections.all::<usize>(cx) {
9859 let selection_is_empty = selection.is_empty();
9860
9861 let (start, end) = if selection_is_empty {
9862 let word_range = movement::surrounding_word(
9863 &display_map,
9864 selection.start.to_display_point(&display_map),
9865 );
9866 let start = word_range.start.to_offset(&display_map, Bias::Left);
9867 let end = word_range.end.to_offset(&display_map, Bias::Left);
9868 (start, end)
9869 } else {
9870 (selection.start, selection.end)
9871 };
9872
9873 let text = buffer.text_for_range(start..end).collect::<String>();
9874 let old_length = text.len() as i32;
9875 let text = callback(&text);
9876
9877 new_selections.push(Selection {
9878 start: (start as i32 - selection_adjustment) as usize,
9879 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
9880 goal: SelectionGoal::None,
9881 ..selection
9882 });
9883
9884 selection_adjustment += old_length - text.len() as i32;
9885
9886 edits.push((start..end, text));
9887 }
9888
9889 self.transact(window, cx, |this, window, cx| {
9890 this.buffer.update(cx, |buffer, cx| {
9891 buffer.edit(edits, None, cx);
9892 });
9893
9894 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9895 s.select(new_selections);
9896 });
9897
9898 this.request_autoscroll(Autoscroll::fit(), cx);
9899 });
9900 }
9901
9902 pub fn duplicate(
9903 &mut self,
9904 upwards: bool,
9905 whole_lines: bool,
9906 window: &mut Window,
9907 cx: &mut Context<Self>,
9908 ) {
9909 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9910
9911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9912 let buffer = &display_map.buffer_snapshot;
9913 let selections = self.selections.all::<Point>(cx);
9914
9915 let mut edits = Vec::new();
9916 let mut selections_iter = selections.iter().peekable();
9917 while let Some(selection) = selections_iter.next() {
9918 let mut rows = selection.spanned_rows(false, &display_map);
9919 // duplicate line-wise
9920 if whole_lines || selection.start == selection.end {
9921 // Avoid duplicating the same lines twice.
9922 while let Some(next_selection) = selections_iter.peek() {
9923 let next_rows = next_selection.spanned_rows(false, &display_map);
9924 if next_rows.start < rows.end {
9925 rows.end = next_rows.end;
9926 selections_iter.next().unwrap();
9927 } else {
9928 break;
9929 }
9930 }
9931
9932 // Copy the text from the selected row region and splice it either at the start
9933 // or end of the region.
9934 let start = Point::new(rows.start.0, 0);
9935 let end = Point::new(
9936 rows.end.previous_row().0,
9937 buffer.line_len(rows.end.previous_row()),
9938 );
9939 let text = buffer
9940 .text_for_range(start..end)
9941 .chain(Some("\n"))
9942 .collect::<String>();
9943 let insert_location = if upwards {
9944 Point::new(rows.end.0, 0)
9945 } else {
9946 start
9947 };
9948 edits.push((insert_location..insert_location, text));
9949 } else {
9950 // duplicate character-wise
9951 let start = selection.start;
9952 let end = selection.end;
9953 let text = buffer.text_for_range(start..end).collect::<String>();
9954 edits.push((selection.end..selection.end, text));
9955 }
9956 }
9957
9958 self.transact(window, cx, |this, _, cx| {
9959 this.buffer.update(cx, |buffer, cx| {
9960 buffer.edit(edits, None, cx);
9961 });
9962
9963 this.request_autoscroll(Autoscroll::fit(), cx);
9964 });
9965 }
9966
9967 pub fn duplicate_line_up(
9968 &mut self,
9969 _: &DuplicateLineUp,
9970 window: &mut Window,
9971 cx: &mut Context<Self>,
9972 ) {
9973 self.duplicate(true, true, window, cx);
9974 }
9975
9976 pub fn duplicate_line_down(
9977 &mut self,
9978 _: &DuplicateLineDown,
9979 window: &mut Window,
9980 cx: &mut Context<Self>,
9981 ) {
9982 self.duplicate(false, true, window, cx);
9983 }
9984
9985 pub fn duplicate_selection(
9986 &mut self,
9987 _: &DuplicateSelection,
9988 window: &mut Window,
9989 cx: &mut Context<Self>,
9990 ) {
9991 self.duplicate(false, false, window, cx);
9992 }
9993
9994 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
9995 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9996
9997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9998 let buffer = self.buffer.read(cx).snapshot(cx);
9999
10000 let mut edits = Vec::new();
10001 let mut unfold_ranges = Vec::new();
10002 let mut refold_creases = Vec::new();
10003
10004 let selections = self.selections.all::<Point>(cx);
10005 let mut selections = selections.iter().peekable();
10006 let mut contiguous_row_selections = Vec::new();
10007 let mut new_selections = Vec::new();
10008
10009 while let Some(selection) = selections.next() {
10010 // Find all the selections that span a contiguous row range
10011 let (start_row, end_row) = consume_contiguous_rows(
10012 &mut contiguous_row_selections,
10013 selection,
10014 &display_map,
10015 &mut selections,
10016 );
10017
10018 // Move the text spanned by the row range to be before the line preceding the row range
10019 if start_row.0 > 0 {
10020 let range_to_move = Point::new(
10021 start_row.previous_row().0,
10022 buffer.line_len(start_row.previous_row()),
10023 )
10024 ..Point::new(
10025 end_row.previous_row().0,
10026 buffer.line_len(end_row.previous_row()),
10027 );
10028 let insertion_point = display_map
10029 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10030 .0;
10031
10032 // Don't move lines across excerpts
10033 if buffer
10034 .excerpt_containing(insertion_point..range_to_move.end)
10035 .is_some()
10036 {
10037 let text = buffer
10038 .text_for_range(range_to_move.clone())
10039 .flat_map(|s| s.chars())
10040 .skip(1)
10041 .chain(['\n'])
10042 .collect::<String>();
10043
10044 edits.push((
10045 buffer.anchor_after(range_to_move.start)
10046 ..buffer.anchor_before(range_to_move.end),
10047 String::new(),
10048 ));
10049 let insertion_anchor = buffer.anchor_after(insertion_point);
10050 edits.push((insertion_anchor..insertion_anchor, text));
10051
10052 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10053
10054 // Move selections up
10055 new_selections.extend(contiguous_row_selections.drain(..).map(
10056 |mut selection| {
10057 selection.start.row -= row_delta;
10058 selection.end.row -= row_delta;
10059 selection
10060 },
10061 ));
10062
10063 // Move folds up
10064 unfold_ranges.push(range_to_move.clone());
10065 for fold in display_map.folds_in_range(
10066 buffer.anchor_before(range_to_move.start)
10067 ..buffer.anchor_after(range_to_move.end),
10068 ) {
10069 let mut start = fold.range.start.to_point(&buffer);
10070 let mut end = fold.range.end.to_point(&buffer);
10071 start.row -= row_delta;
10072 end.row -= row_delta;
10073 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10074 }
10075 }
10076 }
10077
10078 // If we didn't move line(s), preserve the existing selections
10079 new_selections.append(&mut contiguous_row_selections);
10080 }
10081
10082 self.transact(window, cx, |this, window, cx| {
10083 this.unfold_ranges(&unfold_ranges, true, true, cx);
10084 this.buffer.update(cx, |buffer, cx| {
10085 for (range, text) in edits {
10086 buffer.edit([(range, text)], None, cx);
10087 }
10088 });
10089 this.fold_creases(refold_creases, true, window, cx);
10090 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10091 s.select(new_selections);
10092 })
10093 });
10094 }
10095
10096 pub fn move_line_down(
10097 &mut self,
10098 _: &MoveLineDown,
10099 window: &mut Window,
10100 cx: &mut Context<Self>,
10101 ) {
10102 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10103
10104 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10105 let buffer = self.buffer.read(cx).snapshot(cx);
10106
10107 let mut edits = Vec::new();
10108 let mut unfold_ranges = Vec::new();
10109 let mut refold_creases = Vec::new();
10110
10111 let selections = self.selections.all::<Point>(cx);
10112 let mut selections = selections.iter().peekable();
10113 let mut contiguous_row_selections = Vec::new();
10114 let mut new_selections = Vec::new();
10115
10116 while let Some(selection) = selections.next() {
10117 // Find all the selections that span a contiguous row range
10118 let (start_row, end_row) = consume_contiguous_rows(
10119 &mut contiguous_row_selections,
10120 selection,
10121 &display_map,
10122 &mut selections,
10123 );
10124
10125 // Move the text spanned by the row range to be after the last line of the row range
10126 if end_row.0 <= buffer.max_point().row {
10127 let range_to_move =
10128 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10129 let insertion_point = display_map
10130 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10131 .0;
10132
10133 // Don't move lines across excerpt boundaries
10134 if buffer
10135 .excerpt_containing(range_to_move.start..insertion_point)
10136 .is_some()
10137 {
10138 let mut text = String::from("\n");
10139 text.extend(buffer.text_for_range(range_to_move.clone()));
10140 text.pop(); // Drop trailing newline
10141 edits.push((
10142 buffer.anchor_after(range_to_move.start)
10143 ..buffer.anchor_before(range_to_move.end),
10144 String::new(),
10145 ));
10146 let insertion_anchor = buffer.anchor_after(insertion_point);
10147 edits.push((insertion_anchor..insertion_anchor, text));
10148
10149 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10150
10151 // Move selections down
10152 new_selections.extend(contiguous_row_selections.drain(..).map(
10153 |mut selection| {
10154 selection.start.row += row_delta;
10155 selection.end.row += row_delta;
10156 selection
10157 },
10158 ));
10159
10160 // Move folds down
10161 unfold_ranges.push(range_to_move.clone());
10162 for fold in display_map.folds_in_range(
10163 buffer.anchor_before(range_to_move.start)
10164 ..buffer.anchor_after(range_to_move.end),
10165 ) {
10166 let mut start = fold.range.start.to_point(&buffer);
10167 let mut end = fold.range.end.to_point(&buffer);
10168 start.row += row_delta;
10169 end.row += row_delta;
10170 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10171 }
10172 }
10173 }
10174
10175 // If we didn't move line(s), preserve the existing selections
10176 new_selections.append(&mut contiguous_row_selections);
10177 }
10178
10179 self.transact(window, cx, |this, window, cx| {
10180 this.unfold_ranges(&unfold_ranges, true, true, cx);
10181 this.buffer.update(cx, |buffer, cx| {
10182 for (range, text) in edits {
10183 buffer.edit([(range, text)], None, cx);
10184 }
10185 });
10186 this.fold_creases(refold_creases, true, window, cx);
10187 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10188 s.select(new_selections)
10189 });
10190 });
10191 }
10192
10193 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10194 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10195 let text_layout_details = &self.text_layout_details(window);
10196 self.transact(window, cx, |this, window, cx| {
10197 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10198 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10199 s.move_with(|display_map, selection| {
10200 if !selection.is_empty() {
10201 return;
10202 }
10203
10204 let mut head = selection.head();
10205 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10206 if head.column() == display_map.line_len(head.row()) {
10207 transpose_offset = display_map
10208 .buffer_snapshot
10209 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10210 }
10211
10212 if transpose_offset == 0 {
10213 return;
10214 }
10215
10216 *head.column_mut() += 1;
10217 head = display_map.clip_point(head, Bias::Right);
10218 let goal = SelectionGoal::HorizontalPosition(
10219 display_map
10220 .x_for_display_point(head, text_layout_details)
10221 .into(),
10222 );
10223 selection.collapse_to(head, goal);
10224
10225 let transpose_start = display_map
10226 .buffer_snapshot
10227 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10228 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10229 let transpose_end = display_map
10230 .buffer_snapshot
10231 .clip_offset(transpose_offset + 1, Bias::Right);
10232 if let Some(ch) =
10233 display_map.buffer_snapshot.chars_at(transpose_start).next()
10234 {
10235 edits.push((transpose_start..transpose_offset, String::new()));
10236 edits.push((transpose_end..transpose_end, ch.to_string()));
10237 }
10238 }
10239 });
10240 edits
10241 });
10242 this.buffer
10243 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10244 let selections = this.selections.all::<usize>(cx);
10245 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10246 s.select(selections);
10247 });
10248 });
10249 }
10250
10251 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10252 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10253 self.rewrap_impl(RewrapOptions::default(), cx)
10254 }
10255
10256 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10257 let buffer = self.buffer.read(cx).snapshot(cx);
10258 let selections = self.selections.all::<Point>(cx);
10259 let mut selections = selections.iter().peekable();
10260
10261 let mut edits = Vec::new();
10262 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10263
10264 while let Some(selection) = selections.next() {
10265 let mut start_row = selection.start.row;
10266 let mut end_row = selection.end.row;
10267
10268 // Skip selections that overlap with a range that has already been rewrapped.
10269 let selection_range = start_row..end_row;
10270 if rewrapped_row_ranges
10271 .iter()
10272 .any(|range| range.overlaps(&selection_range))
10273 {
10274 continue;
10275 }
10276
10277 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10278
10279 // Since not all lines in the selection may be at the same indent
10280 // level, choose the indent size that is the most common between all
10281 // of the lines.
10282 //
10283 // If there is a tie, we use the deepest indent.
10284 let (indent_size, indent_end) = {
10285 let mut indent_size_occurrences = HashMap::default();
10286 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10287
10288 for row in start_row..=end_row {
10289 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10290 rows_by_indent_size.entry(indent).or_default().push(row);
10291 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10292 }
10293
10294 let indent_size = indent_size_occurrences
10295 .into_iter()
10296 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10297 .map(|(indent, _)| indent)
10298 .unwrap_or_default();
10299 let row = rows_by_indent_size[&indent_size][0];
10300 let indent_end = Point::new(row, indent_size.len);
10301
10302 (indent_size, indent_end)
10303 };
10304
10305 let mut line_prefix = indent_size.chars().collect::<String>();
10306
10307 let mut inside_comment = false;
10308 if let Some(comment_prefix) =
10309 buffer
10310 .language_scope_at(selection.head())
10311 .and_then(|language| {
10312 language
10313 .line_comment_prefixes()
10314 .iter()
10315 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10316 .cloned()
10317 })
10318 {
10319 line_prefix.push_str(&comment_prefix);
10320 inside_comment = true;
10321 }
10322
10323 let language_settings = buffer.language_settings_at(selection.head(), cx);
10324 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10325 RewrapBehavior::InComments => inside_comment,
10326 RewrapBehavior::InSelections => !selection.is_empty(),
10327 RewrapBehavior::Anywhere => true,
10328 };
10329
10330 let should_rewrap = options.override_language_settings
10331 || allow_rewrap_based_on_language
10332 || self.hard_wrap.is_some();
10333 if !should_rewrap {
10334 continue;
10335 }
10336
10337 if selection.is_empty() {
10338 'expand_upwards: while start_row > 0 {
10339 let prev_row = start_row - 1;
10340 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10341 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10342 {
10343 start_row = prev_row;
10344 } else {
10345 break 'expand_upwards;
10346 }
10347 }
10348
10349 'expand_downwards: while end_row < buffer.max_point().row {
10350 let next_row = end_row + 1;
10351 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10352 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10353 {
10354 end_row = next_row;
10355 } else {
10356 break 'expand_downwards;
10357 }
10358 }
10359 }
10360
10361 let start = Point::new(start_row, 0);
10362 let start_offset = start.to_offset(&buffer);
10363 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10364 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10365 let Some(lines_without_prefixes) = selection_text
10366 .lines()
10367 .map(|line| {
10368 line.strip_prefix(&line_prefix)
10369 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10370 .ok_or_else(|| {
10371 anyhow!("line did not start with prefix {line_prefix:?}: {line:?}")
10372 })
10373 })
10374 .collect::<Result<Vec<_>, _>>()
10375 .log_err()
10376 else {
10377 continue;
10378 };
10379
10380 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10381 buffer
10382 .language_settings_at(Point::new(start_row, 0), cx)
10383 .preferred_line_length as usize
10384 });
10385 let wrapped_text = wrap_with_prefix(
10386 line_prefix,
10387 lines_without_prefixes.join("\n"),
10388 wrap_column,
10389 tab_size,
10390 options.preserve_existing_whitespace,
10391 );
10392
10393 // TODO: should always use char-based diff while still supporting cursor behavior that
10394 // matches vim.
10395 let mut diff_options = DiffOptions::default();
10396 if options.override_language_settings {
10397 diff_options.max_word_diff_len = 0;
10398 diff_options.max_word_diff_line_count = 0;
10399 } else {
10400 diff_options.max_word_diff_len = usize::MAX;
10401 diff_options.max_word_diff_line_count = usize::MAX;
10402 }
10403
10404 for (old_range, new_text) in
10405 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10406 {
10407 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10408 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10409 edits.push((edit_start..edit_end, new_text));
10410 }
10411
10412 rewrapped_row_ranges.push(start_row..=end_row);
10413 }
10414
10415 self.buffer
10416 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10417 }
10418
10419 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10420 let mut text = String::new();
10421 let buffer = self.buffer.read(cx).snapshot(cx);
10422 let mut selections = self.selections.all::<Point>(cx);
10423 let mut clipboard_selections = Vec::with_capacity(selections.len());
10424 {
10425 let max_point = buffer.max_point();
10426 let mut is_first = true;
10427 for selection in &mut selections {
10428 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10429 if is_entire_line {
10430 selection.start = Point::new(selection.start.row, 0);
10431 if !selection.is_empty() && selection.end.column == 0 {
10432 selection.end = cmp::min(max_point, selection.end);
10433 } else {
10434 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10435 }
10436 selection.goal = SelectionGoal::None;
10437 }
10438 if is_first {
10439 is_first = false;
10440 } else {
10441 text += "\n";
10442 }
10443 let mut len = 0;
10444 for chunk in buffer.text_for_range(selection.start..selection.end) {
10445 text.push_str(chunk);
10446 len += chunk.len();
10447 }
10448 clipboard_selections.push(ClipboardSelection {
10449 len,
10450 is_entire_line,
10451 first_line_indent: buffer
10452 .indent_size_for_line(MultiBufferRow(selection.start.row))
10453 .len,
10454 });
10455 }
10456 }
10457
10458 self.transact(window, cx, |this, window, cx| {
10459 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10460 s.select(selections);
10461 });
10462 this.insert("", window, cx);
10463 });
10464 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10465 }
10466
10467 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10468 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10469 let item = self.cut_common(window, cx);
10470 cx.write_to_clipboard(item);
10471 }
10472
10473 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10474 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10475 self.change_selections(None, window, cx, |s| {
10476 s.move_with(|snapshot, sel| {
10477 if sel.is_empty() {
10478 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10479 }
10480 });
10481 });
10482 let item = self.cut_common(window, cx);
10483 cx.set_global(KillRing(item))
10484 }
10485
10486 pub fn kill_ring_yank(
10487 &mut self,
10488 _: &KillRingYank,
10489 window: &mut Window,
10490 cx: &mut Context<Self>,
10491 ) {
10492 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10493 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10494 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10495 (kill_ring.text().to_string(), kill_ring.metadata_json())
10496 } else {
10497 return;
10498 }
10499 } else {
10500 return;
10501 };
10502 self.do_paste(&text, metadata, false, window, cx);
10503 }
10504
10505 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10506 self.do_copy(true, cx);
10507 }
10508
10509 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10510 self.do_copy(false, cx);
10511 }
10512
10513 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10514 let selections = self.selections.all::<Point>(cx);
10515 let buffer = self.buffer.read(cx).read(cx);
10516 let mut text = String::new();
10517
10518 let mut clipboard_selections = Vec::with_capacity(selections.len());
10519 {
10520 let max_point = buffer.max_point();
10521 let mut is_first = true;
10522 for selection in &selections {
10523 let mut start = selection.start;
10524 let mut end = selection.end;
10525 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10526 if is_entire_line {
10527 start = Point::new(start.row, 0);
10528 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10529 }
10530
10531 let mut trimmed_selections = Vec::new();
10532 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10533 let row = MultiBufferRow(start.row);
10534 let first_indent = buffer.indent_size_for_line(row);
10535 if first_indent.len == 0 || start.column > first_indent.len {
10536 trimmed_selections.push(start..end);
10537 } else {
10538 trimmed_selections.push(
10539 Point::new(row.0, first_indent.len)
10540 ..Point::new(row.0, buffer.line_len(row)),
10541 );
10542 for row in start.row + 1..=end.row {
10543 let mut line_len = buffer.line_len(MultiBufferRow(row));
10544 if row == end.row {
10545 line_len = end.column;
10546 }
10547 if line_len == 0 {
10548 trimmed_selections
10549 .push(Point::new(row, 0)..Point::new(row, line_len));
10550 continue;
10551 }
10552 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10553 if row_indent_size.len >= first_indent.len {
10554 trimmed_selections.push(
10555 Point::new(row, first_indent.len)..Point::new(row, line_len),
10556 );
10557 } else {
10558 trimmed_selections.clear();
10559 trimmed_selections.push(start..end);
10560 break;
10561 }
10562 }
10563 }
10564 } else {
10565 trimmed_selections.push(start..end);
10566 }
10567
10568 for trimmed_range in trimmed_selections {
10569 if is_first {
10570 is_first = false;
10571 } else {
10572 text += "\n";
10573 }
10574 let mut len = 0;
10575 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10576 text.push_str(chunk);
10577 len += chunk.len();
10578 }
10579 clipboard_selections.push(ClipboardSelection {
10580 len,
10581 is_entire_line,
10582 first_line_indent: buffer
10583 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10584 .len,
10585 });
10586 }
10587 }
10588 }
10589
10590 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10591 text,
10592 clipboard_selections,
10593 ));
10594 }
10595
10596 pub fn do_paste(
10597 &mut self,
10598 text: &String,
10599 clipboard_selections: Option<Vec<ClipboardSelection>>,
10600 handle_entire_lines: bool,
10601 window: &mut Window,
10602 cx: &mut Context<Self>,
10603 ) {
10604 if self.read_only(cx) {
10605 return;
10606 }
10607
10608 let clipboard_text = Cow::Borrowed(text);
10609
10610 self.transact(window, cx, |this, window, cx| {
10611 if let Some(mut clipboard_selections) = clipboard_selections {
10612 let old_selections = this.selections.all::<usize>(cx);
10613 let all_selections_were_entire_line =
10614 clipboard_selections.iter().all(|s| s.is_entire_line);
10615 let first_selection_indent_column =
10616 clipboard_selections.first().map(|s| s.first_line_indent);
10617 if clipboard_selections.len() != old_selections.len() {
10618 clipboard_selections.drain(..);
10619 }
10620 let cursor_offset = this.selections.last::<usize>(cx).head();
10621 let mut auto_indent_on_paste = true;
10622
10623 this.buffer.update(cx, |buffer, cx| {
10624 let snapshot = buffer.read(cx);
10625 auto_indent_on_paste = snapshot
10626 .language_settings_at(cursor_offset, cx)
10627 .auto_indent_on_paste;
10628
10629 let mut start_offset = 0;
10630 let mut edits = Vec::new();
10631 let mut original_indent_columns = Vec::new();
10632 for (ix, selection) in old_selections.iter().enumerate() {
10633 let to_insert;
10634 let entire_line;
10635 let original_indent_column;
10636 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
10637 let end_offset = start_offset + clipboard_selection.len;
10638 to_insert = &clipboard_text[start_offset..end_offset];
10639 entire_line = clipboard_selection.is_entire_line;
10640 start_offset = end_offset + 1;
10641 original_indent_column = Some(clipboard_selection.first_line_indent);
10642 } else {
10643 to_insert = clipboard_text.as_str();
10644 entire_line = all_selections_were_entire_line;
10645 original_indent_column = first_selection_indent_column
10646 }
10647
10648 // If the corresponding selection was empty when this slice of the
10649 // clipboard text was written, then the entire line containing the
10650 // selection was copied. If this selection is also currently empty,
10651 // then paste the line before the current line of the buffer.
10652 let range = if selection.is_empty() && handle_entire_lines && entire_line {
10653 let column = selection.start.to_point(&snapshot).column as usize;
10654 let line_start = selection.start - column;
10655 line_start..line_start
10656 } else {
10657 selection.range()
10658 };
10659
10660 edits.push((range, to_insert));
10661 original_indent_columns.push(original_indent_column);
10662 }
10663 drop(snapshot);
10664
10665 buffer.edit(
10666 edits,
10667 if auto_indent_on_paste {
10668 Some(AutoindentMode::Block {
10669 original_indent_columns,
10670 })
10671 } else {
10672 None
10673 },
10674 cx,
10675 );
10676 });
10677
10678 let selections = this.selections.all::<usize>(cx);
10679 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10680 s.select(selections)
10681 });
10682 } else {
10683 this.insert(&clipboard_text, window, cx);
10684 }
10685 });
10686 }
10687
10688 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
10689 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10690 if let Some(item) = cx.read_from_clipboard() {
10691 let entries = item.entries();
10692
10693 match entries.first() {
10694 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
10695 // of all the pasted entries.
10696 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
10697 .do_paste(
10698 clipboard_string.text(),
10699 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
10700 true,
10701 window,
10702 cx,
10703 ),
10704 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
10705 }
10706 }
10707 }
10708
10709 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
10710 if self.read_only(cx) {
10711 return;
10712 }
10713
10714 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10715
10716 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
10717 if let Some((selections, _)) =
10718 self.selection_history.transaction(transaction_id).cloned()
10719 {
10720 self.change_selections(None, window, cx, |s| {
10721 s.select_anchors(selections.to_vec());
10722 });
10723 } else {
10724 log::error!(
10725 "No entry in selection_history found for undo. \
10726 This may correspond to a bug where undo does not update the selection. \
10727 If this is occurring, please add details to \
10728 https://github.com/zed-industries/zed/issues/22692"
10729 );
10730 }
10731 self.request_autoscroll(Autoscroll::fit(), cx);
10732 self.unmark_text(window, cx);
10733 self.refresh_inline_completion(true, false, window, cx);
10734 cx.emit(EditorEvent::Edited { transaction_id });
10735 cx.emit(EditorEvent::TransactionUndone { transaction_id });
10736 }
10737 }
10738
10739 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
10740 if self.read_only(cx) {
10741 return;
10742 }
10743
10744 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10745
10746 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
10747 if let Some((_, Some(selections))) =
10748 self.selection_history.transaction(transaction_id).cloned()
10749 {
10750 self.change_selections(None, window, cx, |s| {
10751 s.select_anchors(selections.to_vec());
10752 });
10753 } else {
10754 log::error!(
10755 "No entry in selection_history found for redo. \
10756 This may correspond to a bug where undo does not update the selection. \
10757 If this is occurring, please add details to \
10758 https://github.com/zed-industries/zed/issues/22692"
10759 );
10760 }
10761 self.request_autoscroll(Autoscroll::fit(), cx);
10762 self.unmark_text(window, cx);
10763 self.refresh_inline_completion(true, false, window, cx);
10764 cx.emit(EditorEvent::Edited { transaction_id });
10765 }
10766 }
10767
10768 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
10769 self.buffer
10770 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
10771 }
10772
10773 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
10774 self.buffer
10775 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
10776 }
10777
10778 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
10779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10780 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10781 s.move_with(|map, selection| {
10782 let cursor = if selection.is_empty() {
10783 movement::left(map, selection.start)
10784 } else {
10785 selection.start
10786 };
10787 selection.collapse_to(cursor, SelectionGoal::None);
10788 });
10789 })
10790 }
10791
10792 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
10793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10794 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10795 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
10796 })
10797 }
10798
10799 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
10800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10802 s.move_with(|map, selection| {
10803 let cursor = if selection.is_empty() {
10804 movement::right(map, selection.end)
10805 } else {
10806 selection.end
10807 };
10808 selection.collapse_to(cursor, SelectionGoal::None)
10809 });
10810 })
10811 }
10812
10813 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
10814 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10815 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10816 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
10817 })
10818 }
10819
10820 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
10821 if self.take_rename(true, window, cx).is_some() {
10822 return;
10823 }
10824
10825 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10826 cx.propagate();
10827 return;
10828 }
10829
10830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10831
10832 let text_layout_details = &self.text_layout_details(window);
10833 let selection_count = self.selections.count();
10834 let first_selection = self.selections.first_anchor();
10835
10836 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10837 s.move_with(|map, selection| {
10838 if !selection.is_empty() {
10839 selection.goal = SelectionGoal::None;
10840 }
10841 let (cursor, goal) = movement::up(
10842 map,
10843 selection.start,
10844 selection.goal,
10845 false,
10846 text_layout_details,
10847 );
10848 selection.collapse_to(cursor, goal);
10849 });
10850 });
10851
10852 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
10853 {
10854 cx.propagate();
10855 }
10856 }
10857
10858 pub fn move_up_by_lines(
10859 &mut self,
10860 action: &MoveUpByLines,
10861 window: &mut Window,
10862 cx: &mut Context<Self>,
10863 ) {
10864 if self.take_rename(true, window, cx).is_some() {
10865 return;
10866 }
10867
10868 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10869 cx.propagate();
10870 return;
10871 }
10872
10873 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10874
10875 let text_layout_details = &self.text_layout_details(window);
10876
10877 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10878 s.move_with(|map, selection| {
10879 if !selection.is_empty() {
10880 selection.goal = SelectionGoal::None;
10881 }
10882 let (cursor, goal) = movement::up_by_rows(
10883 map,
10884 selection.start,
10885 action.lines,
10886 selection.goal,
10887 false,
10888 text_layout_details,
10889 );
10890 selection.collapse_to(cursor, goal);
10891 });
10892 })
10893 }
10894
10895 pub fn move_down_by_lines(
10896 &mut self,
10897 action: &MoveDownByLines,
10898 window: &mut Window,
10899 cx: &mut Context<Self>,
10900 ) {
10901 if self.take_rename(true, window, cx).is_some() {
10902 return;
10903 }
10904
10905 if matches!(self.mode, EditorMode::SingleLine { .. }) {
10906 cx.propagate();
10907 return;
10908 }
10909
10910 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10911
10912 let text_layout_details = &self.text_layout_details(window);
10913
10914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10915 s.move_with(|map, selection| {
10916 if !selection.is_empty() {
10917 selection.goal = SelectionGoal::None;
10918 }
10919 let (cursor, goal) = movement::down_by_rows(
10920 map,
10921 selection.start,
10922 action.lines,
10923 selection.goal,
10924 false,
10925 text_layout_details,
10926 );
10927 selection.collapse_to(cursor, goal);
10928 });
10929 })
10930 }
10931
10932 pub fn select_down_by_lines(
10933 &mut self,
10934 action: &SelectDownByLines,
10935 window: &mut Window,
10936 cx: &mut Context<Self>,
10937 ) {
10938 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10939 let text_layout_details = &self.text_layout_details(window);
10940 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10941 s.move_heads_with(|map, head, goal| {
10942 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
10943 })
10944 })
10945 }
10946
10947 pub fn select_up_by_lines(
10948 &mut self,
10949 action: &SelectUpByLines,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10954 let text_layout_details = &self.text_layout_details(window);
10955 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10956 s.move_heads_with(|map, head, goal| {
10957 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
10958 })
10959 })
10960 }
10961
10962 pub fn select_page_up(
10963 &mut self,
10964 _: &SelectPageUp,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 let Some(row_count) = self.visible_row_count() else {
10969 return;
10970 };
10971
10972 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
10973
10974 let text_layout_details = &self.text_layout_details(window);
10975
10976 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10977 s.move_heads_with(|map, head, goal| {
10978 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
10979 })
10980 })
10981 }
10982
10983 pub fn move_page_up(
10984 &mut self,
10985 action: &MovePageUp,
10986 window: &mut Window,
10987 cx: &mut Context<Self>,
10988 ) {
10989 if self.take_rename(true, window, cx).is_some() {
10990 return;
10991 }
10992
10993 if self
10994 .context_menu
10995 .borrow_mut()
10996 .as_mut()
10997 .map(|menu| menu.select_first(self.completion_provider.as_deref(), cx))
10998 .unwrap_or(false)
10999 {
11000 return;
11001 }
11002
11003 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11004 cx.propagate();
11005 return;
11006 }
11007
11008 let Some(row_count) = self.visible_row_count() else {
11009 return;
11010 };
11011
11012 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11013
11014 let autoscroll = if action.center_cursor {
11015 Autoscroll::center()
11016 } else {
11017 Autoscroll::fit()
11018 };
11019
11020 let text_layout_details = &self.text_layout_details(window);
11021
11022 self.change_selections(Some(autoscroll), window, cx, |s| {
11023 s.move_with(|map, selection| {
11024 if !selection.is_empty() {
11025 selection.goal = SelectionGoal::None;
11026 }
11027 let (cursor, goal) = movement::up_by_rows(
11028 map,
11029 selection.end,
11030 row_count,
11031 selection.goal,
11032 false,
11033 text_layout_details,
11034 );
11035 selection.collapse_to(cursor, goal);
11036 });
11037 });
11038 }
11039
11040 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11041 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11042 let text_layout_details = &self.text_layout_details(window);
11043 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11044 s.move_heads_with(|map, head, goal| {
11045 movement::up(map, head, goal, false, text_layout_details)
11046 })
11047 })
11048 }
11049
11050 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11051 self.take_rename(true, window, cx);
11052
11053 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11054 cx.propagate();
11055 return;
11056 }
11057
11058 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11059
11060 let text_layout_details = &self.text_layout_details(window);
11061 let selection_count = self.selections.count();
11062 let first_selection = self.selections.first_anchor();
11063
11064 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11065 s.move_with(|map, selection| {
11066 if !selection.is_empty() {
11067 selection.goal = SelectionGoal::None;
11068 }
11069 let (cursor, goal) = movement::down(
11070 map,
11071 selection.end,
11072 selection.goal,
11073 false,
11074 text_layout_details,
11075 );
11076 selection.collapse_to(cursor, goal);
11077 });
11078 });
11079
11080 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11081 {
11082 cx.propagate();
11083 }
11084 }
11085
11086 pub fn select_page_down(
11087 &mut self,
11088 _: &SelectPageDown,
11089 window: &mut Window,
11090 cx: &mut Context<Self>,
11091 ) {
11092 let Some(row_count) = self.visible_row_count() else {
11093 return;
11094 };
11095
11096 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11097
11098 let text_layout_details = &self.text_layout_details(window);
11099
11100 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11101 s.move_heads_with(|map, head, goal| {
11102 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11103 })
11104 })
11105 }
11106
11107 pub fn move_page_down(
11108 &mut self,
11109 action: &MovePageDown,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) {
11113 if self.take_rename(true, window, cx).is_some() {
11114 return;
11115 }
11116
11117 if self
11118 .context_menu
11119 .borrow_mut()
11120 .as_mut()
11121 .map(|menu| menu.select_last(self.completion_provider.as_deref(), cx))
11122 .unwrap_or(false)
11123 {
11124 return;
11125 }
11126
11127 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11128 cx.propagate();
11129 return;
11130 }
11131
11132 let Some(row_count) = self.visible_row_count() else {
11133 return;
11134 };
11135
11136 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11137
11138 let autoscroll = if action.center_cursor {
11139 Autoscroll::center()
11140 } else {
11141 Autoscroll::fit()
11142 };
11143
11144 let text_layout_details = &self.text_layout_details(window);
11145 self.change_selections(Some(autoscroll), window, cx, |s| {
11146 s.move_with(|map, selection| {
11147 if !selection.is_empty() {
11148 selection.goal = SelectionGoal::None;
11149 }
11150 let (cursor, goal) = movement::down_by_rows(
11151 map,
11152 selection.end,
11153 row_count,
11154 selection.goal,
11155 false,
11156 text_layout_details,
11157 );
11158 selection.collapse_to(cursor, goal);
11159 });
11160 });
11161 }
11162
11163 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11164 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11165 let text_layout_details = &self.text_layout_details(window);
11166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11167 s.move_heads_with(|map, head, goal| {
11168 movement::down(map, head, goal, false, text_layout_details)
11169 })
11170 });
11171 }
11172
11173 pub fn context_menu_first(
11174 &mut self,
11175 _: &ContextMenuFirst,
11176 _window: &mut Window,
11177 cx: &mut Context<Self>,
11178 ) {
11179 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11180 context_menu.select_first(self.completion_provider.as_deref(), cx);
11181 }
11182 }
11183
11184 pub fn context_menu_prev(
11185 &mut self,
11186 _: &ContextMenuPrevious,
11187 _window: &mut Window,
11188 cx: &mut Context<Self>,
11189 ) {
11190 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11191 context_menu.select_prev(self.completion_provider.as_deref(), cx);
11192 }
11193 }
11194
11195 pub fn context_menu_next(
11196 &mut self,
11197 _: &ContextMenuNext,
11198 _window: &mut Window,
11199 cx: &mut Context<Self>,
11200 ) {
11201 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11202 context_menu.select_next(self.completion_provider.as_deref(), cx);
11203 }
11204 }
11205
11206 pub fn context_menu_last(
11207 &mut self,
11208 _: &ContextMenuLast,
11209 _window: &mut Window,
11210 cx: &mut Context<Self>,
11211 ) {
11212 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11213 context_menu.select_last(self.completion_provider.as_deref(), cx);
11214 }
11215 }
11216
11217 pub fn move_to_previous_word_start(
11218 &mut self,
11219 _: &MoveToPreviousWordStart,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 ) {
11223 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11224 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11225 s.move_cursors_with(|map, head, _| {
11226 (
11227 movement::previous_word_start(map, head),
11228 SelectionGoal::None,
11229 )
11230 });
11231 })
11232 }
11233
11234 pub fn move_to_previous_subword_start(
11235 &mut self,
11236 _: &MoveToPreviousSubwordStart,
11237 window: &mut Window,
11238 cx: &mut Context<Self>,
11239 ) {
11240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11242 s.move_cursors_with(|map, head, _| {
11243 (
11244 movement::previous_subword_start(map, head),
11245 SelectionGoal::None,
11246 )
11247 });
11248 })
11249 }
11250
11251 pub fn select_to_previous_word_start(
11252 &mut self,
11253 _: &SelectToPreviousWordStart,
11254 window: &mut Window,
11255 cx: &mut Context<Self>,
11256 ) {
11257 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11258 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11259 s.move_heads_with(|map, head, _| {
11260 (
11261 movement::previous_word_start(map, head),
11262 SelectionGoal::None,
11263 )
11264 });
11265 })
11266 }
11267
11268 pub fn select_to_previous_subword_start(
11269 &mut self,
11270 _: &SelectToPreviousSubwordStart,
11271 window: &mut Window,
11272 cx: &mut Context<Self>,
11273 ) {
11274 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11275 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11276 s.move_heads_with(|map, head, _| {
11277 (
11278 movement::previous_subword_start(map, head),
11279 SelectionGoal::None,
11280 )
11281 });
11282 })
11283 }
11284
11285 pub fn delete_to_previous_word_start(
11286 &mut self,
11287 action: &DeleteToPreviousWordStart,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11292 self.transact(window, cx, |this, window, cx| {
11293 this.select_autoclose_pair(window, cx);
11294 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11295 s.move_with(|map, selection| {
11296 if selection.is_empty() {
11297 let cursor = if action.ignore_newlines {
11298 movement::previous_word_start(map, selection.head())
11299 } else {
11300 movement::previous_word_start_or_newline(map, selection.head())
11301 };
11302 selection.set_head(cursor, SelectionGoal::None);
11303 }
11304 });
11305 });
11306 this.insert("", window, cx);
11307 });
11308 }
11309
11310 pub fn delete_to_previous_subword_start(
11311 &mut self,
11312 _: &DeleteToPreviousSubwordStart,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11317 self.transact(window, cx, |this, window, cx| {
11318 this.select_autoclose_pair(window, cx);
11319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11320 s.move_with(|map, selection| {
11321 if selection.is_empty() {
11322 let cursor = movement::previous_subword_start(map, selection.head());
11323 selection.set_head(cursor, SelectionGoal::None);
11324 }
11325 });
11326 });
11327 this.insert("", window, cx);
11328 });
11329 }
11330
11331 pub fn move_to_next_word_end(
11332 &mut self,
11333 _: &MoveToNextWordEnd,
11334 window: &mut Window,
11335 cx: &mut Context<Self>,
11336 ) {
11337 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11338 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11339 s.move_cursors_with(|map, head, _| {
11340 (movement::next_word_end(map, head), SelectionGoal::None)
11341 });
11342 })
11343 }
11344
11345 pub fn move_to_next_subword_end(
11346 &mut self,
11347 _: &MoveToNextSubwordEnd,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11352 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11353 s.move_cursors_with(|map, head, _| {
11354 (movement::next_subword_end(map, head), SelectionGoal::None)
11355 });
11356 })
11357 }
11358
11359 pub fn select_to_next_word_end(
11360 &mut self,
11361 _: &SelectToNextWordEnd,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 ) {
11365 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11367 s.move_heads_with(|map, head, _| {
11368 (movement::next_word_end(map, head), SelectionGoal::None)
11369 });
11370 })
11371 }
11372
11373 pub fn select_to_next_subword_end(
11374 &mut self,
11375 _: &SelectToNextSubwordEnd,
11376 window: &mut Window,
11377 cx: &mut Context<Self>,
11378 ) {
11379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11380 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11381 s.move_heads_with(|map, head, _| {
11382 (movement::next_subword_end(map, head), SelectionGoal::None)
11383 });
11384 })
11385 }
11386
11387 pub fn delete_to_next_word_end(
11388 &mut self,
11389 action: &DeleteToNextWordEnd,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11394 self.transact(window, cx, |this, window, cx| {
11395 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11396 s.move_with(|map, selection| {
11397 if selection.is_empty() {
11398 let cursor = if action.ignore_newlines {
11399 movement::next_word_end(map, selection.head())
11400 } else {
11401 movement::next_word_end_or_newline(map, selection.head())
11402 };
11403 selection.set_head(cursor, SelectionGoal::None);
11404 }
11405 });
11406 });
11407 this.insert("", window, cx);
11408 });
11409 }
11410
11411 pub fn delete_to_next_subword_end(
11412 &mut self,
11413 _: &DeleteToNextSubwordEnd,
11414 window: &mut Window,
11415 cx: &mut Context<Self>,
11416 ) {
11417 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11418 self.transact(window, cx, |this, window, cx| {
11419 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11420 s.move_with(|map, selection| {
11421 if selection.is_empty() {
11422 let cursor = movement::next_subword_end(map, selection.head());
11423 selection.set_head(cursor, SelectionGoal::None);
11424 }
11425 });
11426 });
11427 this.insert("", window, cx);
11428 });
11429 }
11430
11431 pub fn move_to_beginning_of_line(
11432 &mut self,
11433 action: &MoveToBeginningOfLine,
11434 window: &mut Window,
11435 cx: &mut Context<Self>,
11436 ) {
11437 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11438 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11439 s.move_cursors_with(|map, head, _| {
11440 (
11441 movement::indented_line_beginning(
11442 map,
11443 head,
11444 action.stop_at_soft_wraps,
11445 action.stop_at_indent,
11446 ),
11447 SelectionGoal::None,
11448 )
11449 });
11450 })
11451 }
11452
11453 pub fn select_to_beginning_of_line(
11454 &mut self,
11455 action: &SelectToBeginningOfLine,
11456 window: &mut Window,
11457 cx: &mut Context<Self>,
11458 ) {
11459 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11460 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11461 s.move_heads_with(|map, head, _| {
11462 (
11463 movement::indented_line_beginning(
11464 map,
11465 head,
11466 action.stop_at_soft_wraps,
11467 action.stop_at_indent,
11468 ),
11469 SelectionGoal::None,
11470 )
11471 });
11472 });
11473 }
11474
11475 pub fn delete_to_beginning_of_line(
11476 &mut self,
11477 action: &DeleteToBeginningOfLine,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11482 self.transact(window, cx, |this, window, cx| {
11483 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11484 s.move_with(|_, selection| {
11485 selection.reversed = true;
11486 });
11487 });
11488
11489 this.select_to_beginning_of_line(
11490 &SelectToBeginningOfLine {
11491 stop_at_soft_wraps: false,
11492 stop_at_indent: action.stop_at_indent,
11493 },
11494 window,
11495 cx,
11496 );
11497 this.backspace(&Backspace, window, cx);
11498 });
11499 }
11500
11501 pub fn move_to_end_of_line(
11502 &mut self,
11503 action: &MoveToEndOfLine,
11504 window: &mut Window,
11505 cx: &mut Context<Self>,
11506 ) {
11507 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11508 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11509 s.move_cursors_with(|map, head, _| {
11510 (
11511 movement::line_end(map, head, action.stop_at_soft_wraps),
11512 SelectionGoal::None,
11513 )
11514 });
11515 })
11516 }
11517
11518 pub fn select_to_end_of_line(
11519 &mut self,
11520 action: &SelectToEndOfLine,
11521 window: &mut Window,
11522 cx: &mut Context<Self>,
11523 ) {
11524 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11525 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11526 s.move_heads_with(|map, head, _| {
11527 (
11528 movement::line_end(map, head, action.stop_at_soft_wraps),
11529 SelectionGoal::None,
11530 )
11531 });
11532 })
11533 }
11534
11535 pub fn delete_to_end_of_line(
11536 &mut self,
11537 _: &DeleteToEndOfLine,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11542 self.transact(window, cx, |this, window, cx| {
11543 this.select_to_end_of_line(
11544 &SelectToEndOfLine {
11545 stop_at_soft_wraps: false,
11546 },
11547 window,
11548 cx,
11549 );
11550 this.delete(&Delete, window, cx);
11551 });
11552 }
11553
11554 pub fn cut_to_end_of_line(
11555 &mut self,
11556 _: &CutToEndOfLine,
11557 window: &mut Window,
11558 cx: &mut Context<Self>,
11559 ) {
11560 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11561 self.transact(window, cx, |this, window, cx| {
11562 this.select_to_end_of_line(
11563 &SelectToEndOfLine {
11564 stop_at_soft_wraps: false,
11565 },
11566 window,
11567 cx,
11568 );
11569 this.cut(&Cut, window, cx);
11570 });
11571 }
11572
11573 pub fn move_to_start_of_paragraph(
11574 &mut self,
11575 _: &MoveToStartOfParagraph,
11576 window: &mut Window,
11577 cx: &mut Context<Self>,
11578 ) {
11579 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11580 cx.propagate();
11581 return;
11582 }
11583 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11584 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11585 s.move_with(|map, selection| {
11586 selection.collapse_to(
11587 movement::start_of_paragraph(map, selection.head(), 1),
11588 SelectionGoal::None,
11589 )
11590 });
11591 })
11592 }
11593
11594 pub fn move_to_end_of_paragraph(
11595 &mut self,
11596 _: &MoveToEndOfParagraph,
11597 window: &mut Window,
11598 cx: &mut Context<Self>,
11599 ) {
11600 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11601 cx.propagate();
11602 return;
11603 }
11604 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11605 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11606 s.move_with(|map, selection| {
11607 selection.collapse_to(
11608 movement::end_of_paragraph(map, selection.head(), 1),
11609 SelectionGoal::None,
11610 )
11611 });
11612 })
11613 }
11614
11615 pub fn select_to_start_of_paragraph(
11616 &mut self,
11617 _: &SelectToStartOfParagraph,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11622 cx.propagate();
11623 return;
11624 }
11625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11627 s.move_heads_with(|map, head, _| {
11628 (
11629 movement::start_of_paragraph(map, head, 1),
11630 SelectionGoal::None,
11631 )
11632 });
11633 })
11634 }
11635
11636 pub fn select_to_end_of_paragraph(
11637 &mut self,
11638 _: &SelectToEndOfParagraph,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11643 cx.propagate();
11644 return;
11645 }
11646 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11647 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11648 s.move_heads_with(|map, head, _| {
11649 (
11650 movement::end_of_paragraph(map, head, 1),
11651 SelectionGoal::None,
11652 )
11653 });
11654 })
11655 }
11656
11657 pub fn move_to_start_of_excerpt(
11658 &mut self,
11659 _: &MoveToStartOfExcerpt,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11664 cx.propagate();
11665 return;
11666 }
11667 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11668 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11669 s.move_with(|map, selection| {
11670 selection.collapse_to(
11671 movement::start_of_excerpt(
11672 map,
11673 selection.head(),
11674 workspace::searchable::Direction::Prev,
11675 ),
11676 SelectionGoal::None,
11677 )
11678 });
11679 })
11680 }
11681
11682 pub fn move_to_start_of_next_excerpt(
11683 &mut self,
11684 _: &MoveToStartOfNextExcerpt,
11685 window: &mut Window,
11686 cx: &mut Context<Self>,
11687 ) {
11688 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11689 cx.propagate();
11690 return;
11691 }
11692
11693 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11694 s.move_with(|map, selection| {
11695 selection.collapse_to(
11696 movement::start_of_excerpt(
11697 map,
11698 selection.head(),
11699 workspace::searchable::Direction::Next,
11700 ),
11701 SelectionGoal::None,
11702 )
11703 });
11704 })
11705 }
11706
11707 pub fn move_to_end_of_excerpt(
11708 &mut self,
11709 _: &MoveToEndOfExcerpt,
11710 window: &mut Window,
11711 cx: &mut Context<Self>,
11712 ) {
11713 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11714 cx.propagate();
11715 return;
11716 }
11717 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11719 s.move_with(|map, selection| {
11720 selection.collapse_to(
11721 movement::end_of_excerpt(
11722 map,
11723 selection.head(),
11724 workspace::searchable::Direction::Next,
11725 ),
11726 SelectionGoal::None,
11727 )
11728 });
11729 })
11730 }
11731
11732 pub fn move_to_end_of_previous_excerpt(
11733 &mut self,
11734 _: &MoveToEndOfPreviousExcerpt,
11735 window: &mut Window,
11736 cx: &mut Context<Self>,
11737 ) {
11738 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11739 cx.propagate();
11740 return;
11741 }
11742 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11743 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11744 s.move_with(|map, selection| {
11745 selection.collapse_to(
11746 movement::end_of_excerpt(
11747 map,
11748 selection.head(),
11749 workspace::searchable::Direction::Prev,
11750 ),
11751 SelectionGoal::None,
11752 )
11753 });
11754 })
11755 }
11756
11757 pub fn select_to_start_of_excerpt(
11758 &mut self,
11759 _: &SelectToStartOfExcerpt,
11760 window: &mut Window,
11761 cx: &mut Context<Self>,
11762 ) {
11763 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11764 cx.propagate();
11765 return;
11766 }
11767 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11769 s.move_heads_with(|map, head, _| {
11770 (
11771 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11772 SelectionGoal::None,
11773 )
11774 });
11775 })
11776 }
11777
11778 pub fn select_to_start_of_next_excerpt(
11779 &mut self,
11780 _: &SelectToStartOfNextExcerpt,
11781 window: &mut Window,
11782 cx: &mut Context<Self>,
11783 ) {
11784 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11785 cx.propagate();
11786 return;
11787 }
11788 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11789 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11790 s.move_heads_with(|map, head, _| {
11791 (
11792 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
11793 SelectionGoal::None,
11794 )
11795 });
11796 })
11797 }
11798
11799 pub fn select_to_end_of_excerpt(
11800 &mut self,
11801 _: &SelectToEndOfExcerpt,
11802 window: &mut Window,
11803 cx: &mut Context<Self>,
11804 ) {
11805 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11806 cx.propagate();
11807 return;
11808 }
11809 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11810 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11811 s.move_heads_with(|map, head, _| {
11812 (
11813 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
11814 SelectionGoal::None,
11815 )
11816 });
11817 })
11818 }
11819
11820 pub fn select_to_end_of_previous_excerpt(
11821 &mut self,
11822 _: &SelectToEndOfPreviousExcerpt,
11823 window: &mut Window,
11824 cx: &mut Context<Self>,
11825 ) {
11826 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11827 cx.propagate();
11828 return;
11829 }
11830 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11831 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11832 s.move_heads_with(|map, head, _| {
11833 (
11834 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
11835 SelectionGoal::None,
11836 )
11837 });
11838 })
11839 }
11840
11841 pub fn move_to_beginning(
11842 &mut self,
11843 _: &MoveToBeginning,
11844 window: &mut Window,
11845 cx: &mut Context<Self>,
11846 ) {
11847 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11848 cx.propagate();
11849 return;
11850 }
11851 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11852 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11853 s.select_ranges(vec![0..0]);
11854 });
11855 }
11856
11857 pub fn select_to_beginning(
11858 &mut self,
11859 _: &SelectToBeginning,
11860 window: &mut Window,
11861 cx: &mut Context<Self>,
11862 ) {
11863 let mut selection = self.selections.last::<Point>(cx);
11864 selection.set_head(Point::zero(), SelectionGoal::None);
11865 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11866 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11867 s.select(vec![selection]);
11868 });
11869 }
11870
11871 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
11872 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11873 cx.propagate();
11874 return;
11875 }
11876 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11877 let cursor = self.buffer.read(cx).read(cx).len();
11878 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11879 s.select_ranges(vec![cursor..cursor])
11880 });
11881 }
11882
11883 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
11884 self.nav_history = nav_history;
11885 }
11886
11887 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
11888 self.nav_history.as_ref()
11889 }
11890
11891 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
11892 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
11893 }
11894
11895 fn push_to_nav_history(
11896 &mut self,
11897 cursor_anchor: Anchor,
11898 new_position: Option<Point>,
11899 is_deactivate: bool,
11900 cx: &mut Context<Self>,
11901 ) {
11902 if let Some(nav_history) = self.nav_history.as_mut() {
11903 let buffer = self.buffer.read(cx).read(cx);
11904 let cursor_position = cursor_anchor.to_point(&buffer);
11905 let scroll_state = self.scroll_manager.anchor();
11906 let scroll_top_row = scroll_state.top_row(&buffer);
11907 drop(buffer);
11908
11909 if let Some(new_position) = new_position {
11910 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
11911 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
11912 return;
11913 }
11914 }
11915
11916 nav_history.push(
11917 Some(NavigationData {
11918 cursor_anchor,
11919 cursor_position,
11920 scroll_anchor: scroll_state,
11921 scroll_top_row,
11922 }),
11923 cx,
11924 );
11925 cx.emit(EditorEvent::PushedToNavHistory {
11926 anchor: cursor_anchor,
11927 is_deactivate,
11928 })
11929 }
11930 }
11931
11932 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
11933 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11934 let buffer = self.buffer.read(cx).snapshot(cx);
11935 let mut selection = self.selections.first::<usize>(cx);
11936 selection.set_head(buffer.len(), SelectionGoal::None);
11937 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11938 s.select(vec![selection]);
11939 });
11940 }
11941
11942 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
11943 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11944 let end = self.buffer.read(cx).read(cx).len();
11945 self.change_selections(None, window, cx, |s| {
11946 s.select_ranges(vec![0..end]);
11947 });
11948 }
11949
11950 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11952 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11953 let mut selections = self.selections.all::<Point>(cx);
11954 let max_point = display_map.buffer_snapshot.max_point();
11955 for selection in &mut selections {
11956 let rows = selection.spanned_rows(true, &display_map);
11957 selection.start = Point::new(rows.start.0, 0);
11958 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
11959 selection.reversed = false;
11960 }
11961 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11962 s.select(selections);
11963 });
11964 }
11965
11966 pub fn split_selection_into_lines(
11967 &mut self,
11968 _: &SplitSelectionIntoLines,
11969 window: &mut Window,
11970 cx: &mut Context<Self>,
11971 ) {
11972 let selections = self
11973 .selections
11974 .all::<Point>(cx)
11975 .into_iter()
11976 .map(|selection| selection.start..selection.end)
11977 .collect::<Vec<_>>();
11978 self.unfold_ranges(&selections, true, true, cx);
11979
11980 let mut new_selection_ranges = Vec::new();
11981 {
11982 let buffer = self.buffer.read(cx).read(cx);
11983 for selection in selections {
11984 for row in selection.start.row..selection.end.row {
11985 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11986 new_selection_ranges.push(cursor..cursor);
11987 }
11988
11989 let is_multiline_selection = selection.start.row != selection.end.row;
11990 // Don't insert last one if it's a multi-line selection ending at the start of a line,
11991 // so this action feels more ergonomic when paired with other selection operations
11992 let should_skip_last = is_multiline_selection && selection.end.column == 0;
11993 if !should_skip_last {
11994 new_selection_ranges.push(selection.end..selection.end);
11995 }
11996 }
11997 }
11998 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11999 s.select_ranges(new_selection_ranges);
12000 });
12001 }
12002
12003 pub fn add_selection_above(
12004 &mut self,
12005 _: &AddSelectionAbove,
12006 window: &mut Window,
12007 cx: &mut Context<Self>,
12008 ) {
12009 self.add_selection(true, window, cx);
12010 }
12011
12012 pub fn add_selection_below(
12013 &mut self,
12014 _: &AddSelectionBelow,
12015 window: &mut Window,
12016 cx: &mut Context<Self>,
12017 ) {
12018 self.add_selection(false, window, cx);
12019 }
12020
12021 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12022 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12023
12024 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12025 let mut selections = self.selections.all::<Point>(cx);
12026 let text_layout_details = self.text_layout_details(window);
12027 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12028 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12029 let range = oldest_selection.display_range(&display_map).sorted();
12030
12031 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12032 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12033 let positions = start_x.min(end_x)..start_x.max(end_x);
12034
12035 selections.clear();
12036 let mut stack = Vec::new();
12037 for row in range.start.row().0..=range.end.row().0 {
12038 if let Some(selection) = self.selections.build_columnar_selection(
12039 &display_map,
12040 DisplayRow(row),
12041 &positions,
12042 oldest_selection.reversed,
12043 &text_layout_details,
12044 ) {
12045 stack.push(selection.id);
12046 selections.push(selection);
12047 }
12048 }
12049
12050 if above {
12051 stack.reverse();
12052 }
12053
12054 AddSelectionsState { above, stack }
12055 });
12056
12057 let last_added_selection = *state.stack.last().unwrap();
12058 let mut new_selections = Vec::new();
12059 if above == state.above {
12060 let end_row = if above {
12061 DisplayRow(0)
12062 } else {
12063 display_map.max_point().row()
12064 };
12065
12066 'outer: for selection in selections {
12067 if selection.id == last_added_selection {
12068 let range = selection.display_range(&display_map).sorted();
12069 debug_assert_eq!(range.start.row(), range.end.row());
12070 let mut row = range.start.row();
12071 let positions =
12072 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12073 px(start)..px(end)
12074 } else {
12075 let start_x =
12076 display_map.x_for_display_point(range.start, &text_layout_details);
12077 let end_x =
12078 display_map.x_for_display_point(range.end, &text_layout_details);
12079 start_x.min(end_x)..start_x.max(end_x)
12080 };
12081
12082 while row != end_row {
12083 if above {
12084 row.0 -= 1;
12085 } else {
12086 row.0 += 1;
12087 }
12088
12089 if let Some(new_selection) = self.selections.build_columnar_selection(
12090 &display_map,
12091 row,
12092 &positions,
12093 selection.reversed,
12094 &text_layout_details,
12095 ) {
12096 state.stack.push(new_selection.id);
12097 if above {
12098 new_selections.push(new_selection);
12099 new_selections.push(selection);
12100 } else {
12101 new_selections.push(selection);
12102 new_selections.push(new_selection);
12103 }
12104
12105 continue 'outer;
12106 }
12107 }
12108 }
12109
12110 new_selections.push(selection);
12111 }
12112 } else {
12113 new_selections = selections;
12114 new_selections.retain(|s| s.id != last_added_selection);
12115 state.stack.pop();
12116 }
12117
12118 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12119 s.select(new_selections);
12120 });
12121 if state.stack.len() > 1 {
12122 self.add_selections_state = Some(state);
12123 }
12124 }
12125
12126 pub fn select_next_match_internal(
12127 &mut self,
12128 display_map: &DisplaySnapshot,
12129 replace_newest: bool,
12130 autoscroll: Option<Autoscroll>,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) -> Result<()> {
12134 fn select_next_match_ranges(
12135 this: &mut Editor,
12136 range: Range<usize>,
12137 reversed: bool,
12138 replace_newest: bool,
12139 auto_scroll: Option<Autoscroll>,
12140 window: &mut Window,
12141 cx: &mut Context<Editor>,
12142 ) {
12143 this.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12144 this.change_selections(auto_scroll, window, cx, |s| {
12145 if replace_newest {
12146 s.delete(s.newest_anchor().id);
12147 }
12148 if reversed {
12149 s.insert_range(range.end..range.start);
12150 } else {
12151 s.insert_range(range);
12152 }
12153 });
12154 }
12155
12156 let buffer = &display_map.buffer_snapshot;
12157 let mut selections = self.selections.all::<usize>(cx);
12158 if let Some(mut select_next_state) = self.select_next_state.take() {
12159 let query = &select_next_state.query;
12160 if !select_next_state.done {
12161 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12162 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12163 let mut next_selected_range = None;
12164
12165 let bytes_after_last_selection =
12166 buffer.bytes_in_range(last_selection.end..buffer.len());
12167 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12168 let query_matches = query
12169 .stream_find_iter(bytes_after_last_selection)
12170 .map(|result| (last_selection.end, result))
12171 .chain(
12172 query
12173 .stream_find_iter(bytes_before_first_selection)
12174 .map(|result| (0, result)),
12175 );
12176
12177 for (start_offset, query_match) in query_matches {
12178 let query_match = query_match.unwrap(); // can only fail due to I/O
12179 let offset_range =
12180 start_offset + query_match.start()..start_offset + query_match.end();
12181 let display_range = offset_range.start.to_display_point(display_map)
12182 ..offset_range.end.to_display_point(display_map);
12183
12184 if !select_next_state.wordwise
12185 || (!movement::is_inside_word(display_map, display_range.start)
12186 && !movement::is_inside_word(display_map, display_range.end))
12187 {
12188 // TODO: This is n^2, because we might check all the selections
12189 if !selections
12190 .iter()
12191 .any(|selection| selection.range().overlaps(&offset_range))
12192 {
12193 next_selected_range = Some(offset_range);
12194 break;
12195 }
12196 }
12197 }
12198
12199 if let Some(next_selected_range) = next_selected_range {
12200 select_next_match_ranges(
12201 self,
12202 next_selected_range,
12203 last_selection.reversed,
12204 replace_newest,
12205 autoscroll,
12206 window,
12207 cx,
12208 );
12209 } else {
12210 select_next_state.done = true;
12211 }
12212 }
12213
12214 self.select_next_state = Some(select_next_state);
12215 } else {
12216 let mut only_carets = true;
12217 let mut same_text_selected = true;
12218 let mut selected_text = None;
12219
12220 let mut selections_iter = selections.iter().peekable();
12221 while let Some(selection) = selections_iter.next() {
12222 if selection.start != selection.end {
12223 only_carets = false;
12224 }
12225
12226 if same_text_selected {
12227 if selected_text.is_none() {
12228 selected_text =
12229 Some(buffer.text_for_range(selection.range()).collect::<String>());
12230 }
12231
12232 if let Some(next_selection) = selections_iter.peek() {
12233 if next_selection.range().len() == selection.range().len() {
12234 let next_selected_text = buffer
12235 .text_for_range(next_selection.range())
12236 .collect::<String>();
12237 if Some(next_selected_text) != selected_text {
12238 same_text_selected = false;
12239 selected_text = None;
12240 }
12241 } else {
12242 same_text_selected = false;
12243 selected_text = None;
12244 }
12245 }
12246 }
12247 }
12248
12249 if only_carets {
12250 for selection in &mut selections {
12251 let word_range = movement::surrounding_word(
12252 display_map,
12253 selection.start.to_display_point(display_map),
12254 );
12255 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12256 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12257 selection.goal = SelectionGoal::None;
12258 selection.reversed = false;
12259 select_next_match_ranges(
12260 self,
12261 selection.start..selection.end,
12262 selection.reversed,
12263 replace_newest,
12264 autoscroll,
12265 window,
12266 cx,
12267 );
12268 }
12269
12270 if selections.len() == 1 {
12271 let selection = selections
12272 .last()
12273 .expect("ensured that there's only one selection");
12274 let query = buffer
12275 .text_for_range(selection.start..selection.end)
12276 .collect::<String>();
12277 let is_empty = query.is_empty();
12278 let select_state = SelectNextState {
12279 query: AhoCorasick::new(&[query])?,
12280 wordwise: true,
12281 done: is_empty,
12282 };
12283 self.select_next_state = Some(select_state);
12284 } else {
12285 self.select_next_state = None;
12286 }
12287 } else if let Some(selected_text) = selected_text {
12288 self.select_next_state = Some(SelectNextState {
12289 query: AhoCorasick::new(&[selected_text])?,
12290 wordwise: false,
12291 done: false,
12292 });
12293 self.select_next_match_internal(
12294 display_map,
12295 replace_newest,
12296 autoscroll,
12297 window,
12298 cx,
12299 )?;
12300 }
12301 }
12302 Ok(())
12303 }
12304
12305 pub fn select_all_matches(
12306 &mut self,
12307 _action: &SelectAllMatches,
12308 window: &mut Window,
12309 cx: &mut Context<Self>,
12310 ) -> Result<()> {
12311 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12312
12313 self.push_to_selection_history();
12314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12315
12316 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12317 let Some(select_next_state) = self.select_next_state.as_mut() else {
12318 return Ok(());
12319 };
12320 if select_next_state.done {
12321 return Ok(());
12322 }
12323
12324 let mut new_selections = Vec::new();
12325
12326 let reversed = self.selections.oldest::<usize>(cx).reversed;
12327 let buffer = &display_map.buffer_snapshot;
12328 let query_matches = select_next_state
12329 .query
12330 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12331
12332 for query_match in query_matches.into_iter() {
12333 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12334 let offset_range = if reversed {
12335 query_match.end()..query_match.start()
12336 } else {
12337 query_match.start()..query_match.end()
12338 };
12339 let display_range = offset_range.start.to_display_point(&display_map)
12340 ..offset_range.end.to_display_point(&display_map);
12341
12342 if !select_next_state.wordwise
12343 || (!movement::is_inside_word(&display_map, display_range.start)
12344 && !movement::is_inside_word(&display_map, display_range.end))
12345 {
12346 new_selections.push(offset_range.start..offset_range.end);
12347 }
12348 }
12349
12350 select_next_state.done = true;
12351 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12352 self.change_selections(None, window, cx, |selections| {
12353 selections.select_ranges(new_selections)
12354 });
12355
12356 Ok(())
12357 }
12358
12359 pub fn select_next(
12360 &mut self,
12361 action: &SelectNext,
12362 window: &mut Window,
12363 cx: &mut Context<Self>,
12364 ) -> Result<()> {
12365 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12366 self.push_to_selection_history();
12367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12368 self.select_next_match_internal(
12369 &display_map,
12370 action.replace_newest,
12371 Some(Autoscroll::newest()),
12372 window,
12373 cx,
12374 )?;
12375 Ok(())
12376 }
12377
12378 pub fn select_previous(
12379 &mut self,
12380 action: &SelectPrevious,
12381 window: &mut Window,
12382 cx: &mut Context<Self>,
12383 ) -> Result<()> {
12384 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12385 self.push_to_selection_history();
12386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12387 let buffer = &display_map.buffer_snapshot;
12388 let mut selections = self.selections.all::<usize>(cx);
12389 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12390 let query = &select_prev_state.query;
12391 if !select_prev_state.done {
12392 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12393 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12394 let mut next_selected_range = None;
12395 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12396 let bytes_before_last_selection =
12397 buffer.reversed_bytes_in_range(0..last_selection.start);
12398 let bytes_after_first_selection =
12399 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12400 let query_matches = query
12401 .stream_find_iter(bytes_before_last_selection)
12402 .map(|result| (last_selection.start, result))
12403 .chain(
12404 query
12405 .stream_find_iter(bytes_after_first_selection)
12406 .map(|result| (buffer.len(), result)),
12407 );
12408 for (end_offset, query_match) in query_matches {
12409 let query_match = query_match.unwrap(); // can only fail due to I/O
12410 let offset_range =
12411 end_offset - query_match.end()..end_offset - query_match.start();
12412 let display_range = offset_range.start.to_display_point(&display_map)
12413 ..offset_range.end.to_display_point(&display_map);
12414
12415 if !select_prev_state.wordwise
12416 || (!movement::is_inside_word(&display_map, display_range.start)
12417 && !movement::is_inside_word(&display_map, display_range.end))
12418 {
12419 next_selected_range = Some(offset_range);
12420 break;
12421 }
12422 }
12423
12424 if let Some(next_selected_range) = next_selected_range {
12425 self.unfold_ranges(&[next_selected_range.clone()], false, true, cx);
12426 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12427 if action.replace_newest {
12428 s.delete(s.newest_anchor().id);
12429 }
12430 if last_selection.reversed {
12431 s.insert_range(next_selected_range.end..next_selected_range.start);
12432 } else {
12433 s.insert_range(next_selected_range);
12434 }
12435 });
12436 } else {
12437 select_prev_state.done = true;
12438 }
12439 }
12440
12441 self.select_prev_state = Some(select_prev_state);
12442 } else {
12443 let mut only_carets = true;
12444 let mut same_text_selected = true;
12445 let mut selected_text = None;
12446
12447 let mut selections_iter = selections.iter().peekable();
12448 while let Some(selection) = selections_iter.next() {
12449 if selection.start != selection.end {
12450 only_carets = false;
12451 }
12452
12453 if same_text_selected {
12454 if selected_text.is_none() {
12455 selected_text =
12456 Some(buffer.text_for_range(selection.range()).collect::<String>());
12457 }
12458
12459 if let Some(next_selection) = selections_iter.peek() {
12460 if next_selection.range().len() == selection.range().len() {
12461 let next_selected_text = buffer
12462 .text_for_range(next_selection.range())
12463 .collect::<String>();
12464 if Some(next_selected_text) != selected_text {
12465 same_text_selected = false;
12466 selected_text = None;
12467 }
12468 } else {
12469 same_text_selected = false;
12470 selected_text = None;
12471 }
12472 }
12473 }
12474 }
12475
12476 if only_carets {
12477 for selection in &mut selections {
12478 let word_range = movement::surrounding_word(
12479 &display_map,
12480 selection.start.to_display_point(&display_map),
12481 );
12482 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12483 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12484 selection.goal = SelectionGoal::None;
12485 selection.reversed = false;
12486 }
12487 if selections.len() == 1 {
12488 let selection = selections
12489 .last()
12490 .expect("ensured that there's only one selection");
12491 let query = buffer
12492 .text_for_range(selection.start..selection.end)
12493 .collect::<String>();
12494 let is_empty = query.is_empty();
12495 let select_state = SelectNextState {
12496 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12497 wordwise: true,
12498 done: is_empty,
12499 };
12500 self.select_prev_state = Some(select_state);
12501 } else {
12502 self.select_prev_state = None;
12503 }
12504
12505 self.unfold_ranges(
12506 &selections.iter().map(|s| s.range()).collect::<Vec<_>>(),
12507 false,
12508 true,
12509 cx,
12510 );
12511 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
12512 s.select(selections);
12513 });
12514 } else if let Some(selected_text) = selected_text {
12515 self.select_prev_state = Some(SelectNextState {
12516 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12517 wordwise: false,
12518 done: false,
12519 });
12520 self.select_previous(action, window, cx)?;
12521 }
12522 }
12523 Ok(())
12524 }
12525
12526 pub fn find_next_match(
12527 &mut self,
12528 _: &FindNextMatch,
12529 window: &mut Window,
12530 cx: &mut Context<Self>,
12531 ) -> Result<()> {
12532 let selections = self.selections.disjoint_anchors();
12533 match selections.first() {
12534 Some(first) if selections.len() >= 2 => {
12535 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12536 s.select_ranges([first.range()]);
12537 });
12538 }
12539 _ => self.select_next(
12540 &SelectNext {
12541 replace_newest: true,
12542 },
12543 window,
12544 cx,
12545 )?,
12546 }
12547 Ok(())
12548 }
12549
12550 pub fn find_previous_match(
12551 &mut self,
12552 _: &FindPreviousMatch,
12553 window: &mut Window,
12554 cx: &mut Context<Self>,
12555 ) -> Result<()> {
12556 let selections = self.selections.disjoint_anchors();
12557 match selections.last() {
12558 Some(last) if selections.len() >= 2 => {
12559 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12560 s.select_ranges([last.range()]);
12561 });
12562 }
12563 _ => self.select_previous(
12564 &SelectPrevious {
12565 replace_newest: true,
12566 },
12567 window,
12568 cx,
12569 )?,
12570 }
12571 Ok(())
12572 }
12573
12574 pub fn toggle_comments(
12575 &mut self,
12576 action: &ToggleComments,
12577 window: &mut Window,
12578 cx: &mut Context<Self>,
12579 ) {
12580 if self.read_only(cx) {
12581 return;
12582 }
12583 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12584 let text_layout_details = &self.text_layout_details(window);
12585 self.transact(window, cx, |this, window, cx| {
12586 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12587 let mut edits = Vec::new();
12588 let mut selection_edit_ranges = Vec::new();
12589 let mut last_toggled_row = None;
12590 let snapshot = this.buffer.read(cx).read(cx);
12591 let empty_str: Arc<str> = Arc::default();
12592 let mut suffixes_inserted = Vec::new();
12593 let ignore_indent = action.ignore_indent;
12594
12595 fn comment_prefix_range(
12596 snapshot: &MultiBufferSnapshot,
12597 row: MultiBufferRow,
12598 comment_prefix: &str,
12599 comment_prefix_whitespace: &str,
12600 ignore_indent: bool,
12601 ) -> Range<Point> {
12602 let indent_size = if ignore_indent {
12603 0
12604 } else {
12605 snapshot.indent_size_for_line(row).len
12606 };
12607
12608 let start = Point::new(row.0, indent_size);
12609
12610 let mut line_bytes = snapshot
12611 .bytes_in_range(start..snapshot.max_point())
12612 .flatten()
12613 .copied();
12614
12615 // If this line currently begins with the line comment prefix, then record
12616 // the range containing the prefix.
12617 if line_bytes
12618 .by_ref()
12619 .take(comment_prefix.len())
12620 .eq(comment_prefix.bytes())
12621 {
12622 // Include any whitespace that matches the comment prefix.
12623 let matching_whitespace_len = line_bytes
12624 .zip(comment_prefix_whitespace.bytes())
12625 .take_while(|(a, b)| a == b)
12626 .count() as u32;
12627 let end = Point::new(
12628 start.row,
12629 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12630 );
12631 start..end
12632 } else {
12633 start..start
12634 }
12635 }
12636
12637 fn comment_suffix_range(
12638 snapshot: &MultiBufferSnapshot,
12639 row: MultiBufferRow,
12640 comment_suffix: &str,
12641 comment_suffix_has_leading_space: bool,
12642 ) -> Range<Point> {
12643 let end = Point::new(row.0, snapshot.line_len(row));
12644 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
12645
12646 let mut line_end_bytes = snapshot
12647 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
12648 .flatten()
12649 .copied();
12650
12651 let leading_space_len = if suffix_start_column > 0
12652 && line_end_bytes.next() == Some(b' ')
12653 && comment_suffix_has_leading_space
12654 {
12655 1
12656 } else {
12657 0
12658 };
12659
12660 // If this line currently begins with the line comment prefix, then record
12661 // the range containing the prefix.
12662 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
12663 let start = Point::new(end.row, suffix_start_column - leading_space_len);
12664 start..end
12665 } else {
12666 end..end
12667 }
12668 }
12669
12670 // TODO: Handle selections that cross excerpts
12671 for selection in &mut selections {
12672 let start_column = snapshot
12673 .indent_size_for_line(MultiBufferRow(selection.start.row))
12674 .len;
12675 let language = if let Some(language) =
12676 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
12677 {
12678 language
12679 } else {
12680 continue;
12681 };
12682
12683 selection_edit_ranges.clear();
12684
12685 // If multiple selections contain a given row, avoid processing that
12686 // row more than once.
12687 let mut start_row = MultiBufferRow(selection.start.row);
12688 if last_toggled_row == Some(start_row) {
12689 start_row = start_row.next_row();
12690 }
12691 let end_row =
12692 if selection.end.row > selection.start.row && selection.end.column == 0 {
12693 MultiBufferRow(selection.end.row - 1)
12694 } else {
12695 MultiBufferRow(selection.end.row)
12696 };
12697 last_toggled_row = Some(end_row);
12698
12699 if start_row > end_row {
12700 continue;
12701 }
12702
12703 // If the language has line comments, toggle those.
12704 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
12705
12706 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
12707 if ignore_indent {
12708 full_comment_prefixes = full_comment_prefixes
12709 .into_iter()
12710 .map(|s| Arc::from(s.trim_end()))
12711 .collect();
12712 }
12713
12714 if !full_comment_prefixes.is_empty() {
12715 let first_prefix = full_comment_prefixes
12716 .first()
12717 .expect("prefixes is non-empty");
12718 let prefix_trimmed_lengths = full_comment_prefixes
12719 .iter()
12720 .map(|p| p.trim_end_matches(' ').len())
12721 .collect::<SmallVec<[usize; 4]>>();
12722
12723 let mut all_selection_lines_are_comments = true;
12724
12725 for row in start_row.0..=end_row.0 {
12726 let row = MultiBufferRow(row);
12727 if start_row < end_row && snapshot.is_line_blank(row) {
12728 continue;
12729 }
12730
12731 let prefix_range = full_comment_prefixes
12732 .iter()
12733 .zip(prefix_trimmed_lengths.iter().copied())
12734 .map(|(prefix, trimmed_prefix_len)| {
12735 comment_prefix_range(
12736 snapshot.deref(),
12737 row,
12738 &prefix[..trimmed_prefix_len],
12739 &prefix[trimmed_prefix_len..],
12740 ignore_indent,
12741 )
12742 })
12743 .max_by_key(|range| range.end.column - range.start.column)
12744 .expect("prefixes is non-empty");
12745
12746 if prefix_range.is_empty() {
12747 all_selection_lines_are_comments = false;
12748 }
12749
12750 selection_edit_ranges.push(prefix_range);
12751 }
12752
12753 if all_selection_lines_are_comments {
12754 edits.extend(
12755 selection_edit_ranges
12756 .iter()
12757 .cloned()
12758 .map(|range| (range, empty_str.clone())),
12759 );
12760 } else {
12761 let min_column = selection_edit_ranges
12762 .iter()
12763 .map(|range| range.start.column)
12764 .min()
12765 .unwrap_or(0);
12766 edits.extend(selection_edit_ranges.iter().map(|range| {
12767 let position = Point::new(range.start.row, min_column);
12768 (position..position, first_prefix.clone())
12769 }));
12770 }
12771 } else if let Some((full_comment_prefix, comment_suffix)) =
12772 language.block_comment_delimiters()
12773 {
12774 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
12775 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
12776 let prefix_range = comment_prefix_range(
12777 snapshot.deref(),
12778 start_row,
12779 comment_prefix,
12780 comment_prefix_whitespace,
12781 ignore_indent,
12782 );
12783 let suffix_range = comment_suffix_range(
12784 snapshot.deref(),
12785 end_row,
12786 comment_suffix.trim_start_matches(' '),
12787 comment_suffix.starts_with(' '),
12788 );
12789
12790 if prefix_range.is_empty() || suffix_range.is_empty() {
12791 edits.push((
12792 prefix_range.start..prefix_range.start,
12793 full_comment_prefix.clone(),
12794 ));
12795 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
12796 suffixes_inserted.push((end_row, comment_suffix.len()));
12797 } else {
12798 edits.push((prefix_range, empty_str.clone()));
12799 edits.push((suffix_range, empty_str.clone()));
12800 }
12801 } else {
12802 continue;
12803 }
12804 }
12805
12806 drop(snapshot);
12807 this.buffer.update(cx, |buffer, cx| {
12808 buffer.edit(edits, None, cx);
12809 });
12810
12811 // Adjust selections so that they end before any comment suffixes that
12812 // were inserted.
12813 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
12814 let mut selections = this.selections.all::<Point>(cx);
12815 let snapshot = this.buffer.read(cx).read(cx);
12816 for selection in &mut selections {
12817 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
12818 match row.cmp(&MultiBufferRow(selection.end.row)) {
12819 Ordering::Less => {
12820 suffixes_inserted.next();
12821 continue;
12822 }
12823 Ordering::Greater => break,
12824 Ordering::Equal => {
12825 if selection.end.column == snapshot.line_len(row) {
12826 if selection.is_empty() {
12827 selection.start.column -= suffix_len as u32;
12828 }
12829 selection.end.column -= suffix_len as u32;
12830 }
12831 break;
12832 }
12833 }
12834 }
12835 }
12836
12837 drop(snapshot);
12838 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12839 s.select(selections)
12840 });
12841
12842 let selections = this.selections.all::<Point>(cx);
12843 let selections_on_single_row = selections.windows(2).all(|selections| {
12844 selections[0].start.row == selections[1].start.row
12845 && selections[0].end.row == selections[1].end.row
12846 && selections[0].start.row == selections[0].end.row
12847 });
12848 let selections_selecting = selections
12849 .iter()
12850 .any(|selection| selection.start != selection.end);
12851 let advance_downwards = action.advance_downwards
12852 && selections_on_single_row
12853 && !selections_selecting
12854 && !matches!(this.mode, EditorMode::SingleLine { .. });
12855
12856 if advance_downwards {
12857 let snapshot = this.buffer.read(cx).snapshot(cx);
12858
12859 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12860 s.move_cursors_with(|display_snapshot, display_point, _| {
12861 let mut point = display_point.to_point(display_snapshot);
12862 point.row += 1;
12863 point = snapshot.clip_point(point, Bias::Left);
12864 let display_point = point.to_display_point(display_snapshot);
12865 let goal = SelectionGoal::HorizontalPosition(
12866 display_snapshot
12867 .x_for_display_point(display_point, text_layout_details)
12868 .into(),
12869 );
12870 (display_point, goal)
12871 })
12872 });
12873 }
12874 });
12875 }
12876
12877 pub fn select_enclosing_symbol(
12878 &mut self,
12879 _: &SelectEnclosingSymbol,
12880 window: &mut Window,
12881 cx: &mut Context<Self>,
12882 ) {
12883 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12884
12885 let buffer = self.buffer.read(cx).snapshot(cx);
12886 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
12887
12888 fn update_selection(
12889 selection: &Selection<usize>,
12890 buffer_snap: &MultiBufferSnapshot,
12891 ) -> Option<Selection<usize>> {
12892 let cursor = selection.head();
12893 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
12894 for symbol in symbols.iter().rev() {
12895 let start = symbol.range.start.to_offset(buffer_snap);
12896 let end = symbol.range.end.to_offset(buffer_snap);
12897 let new_range = start..end;
12898 if start < selection.start || end > selection.end {
12899 return Some(Selection {
12900 id: selection.id,
12901 start: new_range.start,
12902 end: new_range.end,
12903 goal: SelectionGoal::None,
12904 reversed: selection.reversed,
12905 });
12906 }
12907 }
12908 None
12909 }
12910
12911 let mut selected_larger_symbol = false;
12912 let new_selections = old_selections
12913 .iter()
12914 .map(|selection| match update_selection(selection, &buffer) {
12915 Some(new_selection) => {
12916 if new_selection.range() != selection.range() {
12917 selected_larger_symbol = true;
12918 }
12919 new_selection
12920 }
12921 None => selection.clone(),
12922 })
12923 .collect::<Vec<_>>();
12924
12925 if selected_larger_symbol {
12926 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12927 s.select(new_selections);
12928 });
12929 }
12930 }
12931
12932 pub fn select_larger_syntax_node(
12933 &mut self,
12934 _: &SelectLargerSyntaxNode,
12935 window: &mut Window,
12936 cx: &mut Context<Self>,
12937 ) {
12938 let Some(visible_row_count) = self.visible_row_count() else {
12939 return;
12940 };
12941 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
12942 if old_selections.is_empty() {
12943 return;
12944 }
12945
12946 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12947
12948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12949 let buffer = self.buffer.read(cx).snapshot(cx);
12950
12951 let mut selected_larger_node = false;
12952 let mut new_selections = old_selections
12953 .iter()
12954 .map(|selection| {
12955 let old_range = selection.start..selection.end;
12956
12957 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
12958 // manually select word at selection
12959 if ["string_content", "inline"].contains(&node.kind()) {
12960 let word_range = {
12961 let display_point = buffer
12962 .offset_to_point(old_range.start)
12963 .to_display_point(&display_map);
12964 let Range { start, end } =
12965 movement::surrounding_word(&display_map, display_point);
12966 start.to_point(&display_map).to_offset(&buffer)
12967 ..end.to_point(&display_map).to_offset(&buffer)
12968 };
12969 // ignore if word is already selected
12970 if !word_range.is_empty() && old_range != word_range {
12971 let last_word_range = {
12972 let display_point = buffer
12973 .offset_to_point(old_range.end)
12974 .to_display_point(&display_map);
12975 let Range { start, end } =
12976 movement::surrounding_word(&display_map, display_point);
12977 start.to_point(&display_map).to_offset(&buffer)
12978 ..end.to_point(&display_map).to_offset(&buffer)
12979 };
12980 // only select word if start and end point belongs to same word
12981 if word_range == last_word_range {
12982 selected_larger_node = true;
12983 return Selection {
12984 id: selection.id,
12985 start: word_range.start,
12986 end: word_range.end,
12987 goal: SelectionGoal::None,
12988 reversed: selection.reversed,
12989 };
12990 }
12991 }
12992 }
12993 }
12994
12995 let mut new_range = old_range.clone();
12996 let mut new_node = None;
12997 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
12998 {
12999 new_node = Some(node);
13000 new_range = match containing_range {
13001 MultiOrSingleBufferOffsetRange::Single(_) => break,
13002 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13003 };
13004 if !display_map.intersects_fold(new_range.start)
13005 && !display_map.intersects_fold(new_range.end)
13006 {
13007 break;
13008 }
13009 }
13010
13011 if let Some(node) = new_node {
13012 // Log the ancestor, to support using this action as a way to explore TreeSitter
13013 // nodes. Parent and grandparent are also logged because this operation will not
13014 // visit nodes that have the same range as their parent.
13015 log::info!("Node: {node:?}");
13016 let parent = node.parent();
13017 log::info!("Parent: {parent:?}");
13018 let grandparent = parent.and_then(|x| x.parent());
13019 log::info!("Grandparent: {grandparent:?}");
13020 }
13021
13022 selected_larger_node |= new_range != old_range;
13023 Selection {
13024 id: selection.id,
13025 start: new_range.start,
13026 end: new_range.end,
13027 goal: SelectionGoal::None,
13028 reversed: selection.reversed,
13029 }
13030 })
13031 .collect::<Vec<_>>();
13032
13033 if !selected_larger_node {
13034 return; // don't put this call in the history
13035 }
13036
13037 // scroll based on transformation done to the last selection created by the user
13038 let (last_old, last_new) = old_selections
13039 .last()
13040 .zip(new_selections.last().cloned())
13041 .expect("old_selections isn't empty");
13042
13043 // revert selection
13044 let is_selection_reversed = {
13045 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13046 new_selections.last_mut().expect("checked above").reversed =
13047 should_newest_selection_be_reversed;
13048 should_newest_selection_be_reversed
13049 };
13050
13051 if selected_larger_node {
13052 self.select_syntax_node_history.disable_clearing = true;
13053 self.change_selections(None, window, cx, |s| {
13054 s.select(new_selections.clone());
13055 });
13056 self.select_syntax_node_history.disable_clearing = false;
13057 }
13058
13059 let start_row = last_new.start.to_display_point(&display_map).row().0;
13060 let end_row = last_new.end.to_display_point(&display_map).row().0;
13061 let selection_height = end_row - start_row + 1;
13062 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13063
13064 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13065 let scroll_behavior = if fits_on_the_screen {
13066 self.request_autoscroll(Autoscroll::fit(), cx);
13067 SelectSyntaxNodeScrollBehavior::FitSelection
13068 } else if is_selection_reversed {
13069 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13070 SelectSyntaxNodeScrollBehavior::CursorTop
13071 } else {
13072 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13073 SelectSyntaxNodeScrollBehavior::CursorBottom
13074 };
13075
13076 self.select_syntax_node_history.push((
13077 old_selections,
13078 scroll_behavior,
13079 is_selection_reversed,
13080 ));
13081 }
13082
13083 pub fn select_smaller_syntax_node(
13084 &mut self,
13085 _: &SelectSmallerSyntaxNode,
13086 window: &mut Window,
13087 cx: &mut Context<Self>,
13088 ) {
13089 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13090
13091 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13092 self.select_syntax_node_history.pop()
13093 {
13094 if let Some(selection) = selections.last_mut() {
13095 selection.reversed = is_selection_reversed;
13096 }
13097
13098 self.select_syntax_node_history.disable_clearing = true;
13099 self.change_selections(None, window, cx, |s| {
13100 s.select(selections.to_vec());
13101 });
13102 self.select_syntax_node_history.disable_clearing = false;
13103
13104 match scroll_behavior {
13105 SelectSyntaxNodeScrollBehavior::CursorTop => {
13106 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13107 }
13108 SelectSyntaxNodeScrollBehavior::FitSelection => {
13109 self.request_autoscroll(Autoscroll::fit(), cx);
13110 }
13111 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13112 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13113 }
13114 }
13115 }
13116 }
13117
13118 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13119 if !EditorSettings::get_global(cx).gutter.runnables {
13120 self.clear_tasks();
13121 return Task::ready(());
13122 }
13123 let project = self.project.as_ref().map(Entity::downgrade);
13124 let task_sources = self.lsp_task_sources(cx);
13125 cx.spawn_in(window, async move |editor, cx| {
13126 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13127 let Some(project) = project.and_then(|p| p.upgrade()) else {
13128 return;
13129 };
13130 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13131 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13132 }) else {
13133 return;
13134 };
13135
13136 let hide_runnables = project
13137 .update(cx, |project, cx| {
13138 // Do not display any test indicators in non-dev server remote projects.
13139 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13140 })
13141 .unwrap_or(true);
13142 if hide_runnables {
13143 return;
13144 }
13145 let new_rows =
13146 cx.background_spawn({
13147 let snapshot = display_snapshot.clone();
13148 async move {
13149 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13150 }
13151 })
13152 .await;
13153 let Ok(lsp_tasks) =
13154 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13155 else {
13156 return;
13157 };
13158 let lsp_tasks = lsp_tasks.await;
13159
13160 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13161 lsp_tasks
13162 .into_iter()
13163 .flat_map(|(kind, tasks)| {
13164 tasks.into_iter().filter_map(move |(location, task)| {
13165 Some((kind.clone(), location?, task))
13166 })
13167 })
13168 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13169 let buffer = location.target.buffer;
13170 let buffer_snapshot = buffer.read(cx).snapshot();
13171 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13172 |(excerpt_id, snapshot, _)| {
13173 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13174 display_snapshot
13175 .buffer_snapshot
13176 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13177 } else {
13178 None
13179 }
13180 },
13181 );
13182 if let Some(offset) = offset {
13183 let task_buffer_range =
13184 location.target.range.to_point(&buffer_snapshot);
13185 let context_buffer_range =
13186 task_buffer_range.to_offset(&buffer_snapshot);
13187 let context_range = BufferOffset(context_buffer_range.start)
13188 ..BufferOffset(context_buffer_range.end);
13189
13190 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13191 .or_insert_with(|| RunnableTasks {
13192 templates: Vec::new(),
13193 offset,
13194 column: task_buffer_range.start.column,
13195 extra_variables: HashMap::default(),
13196 context_range,
13197 })
13198 .templates
13199 .push((kind, task.original_task().clone()));
13200 }
13201
13202 acc
13203 })
13204 }) else {
13205 return;
13206 };
13207
13208 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13209 editor
13210 .update(cx, |editor, _| {
13211 editor.clear_tasks();
13212 for (key, mut value) in rows {
13213 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13214 value.templates.extend(lsp_tasks.templates);
13215 }
13216
13217 editor.insert_tasks(key, value);
13218 }
13219 for (key, value) in lsp_tasks_by_rows {
13220 editor.insert_tasks(key, value);
13221 }
13222 })
13223 .ok();
13224 })
13225 }
13226 fn fetch_runnable_ranges(
13227 snapshot: &DisplaySnapshot,
13228 range: Range<Anchor>,
13229 ) -> Vec<language::RunnableRange> {
13230 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13231 }
13232
13233 fn runnable_rows(
13234 project: Entity<Project>,
13235 snapshot: DisplaySnapshot,
13236 runnable_ranges: Vec<RunnableRange>,
13237 mut cx: AsyncWindowContext,
13238 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13239 runnable_ranges
13240 .into_iter()
13241 .filter_map(|mut runnable| {
13242 let tasks = cx
13243 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13244 .ok()?;
13245 if tasks.is_empty() {
13246 return None;
13247 }
13248
13249 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13250
13251 let row = snapshot
13252 .buffer_snapshot
13253 .buffer_line_for_row(MultiBufferRow(point.row))?
13254 .1
13255 .start
13256 .row;
13257
13258 let context_range =
13259 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13260 Some((
13261 (runnable.buffer_id, row),
13262 RunnableTasks {
13263 templates: tasks,
13264 offset: snapshot
13265 .buffer_snapshot
13266 .anchor_before(runnable.run_range.start),
13267 context_range,
13268 column: point.column,
13269 extra_variables: runnable.extra_captures,
13270 },
13271 ))
13272 })
13273 .collect()
13274 }
13275
13276 fn templates_with_tags(
13277 project: &Entity<Project>,
13278 runnable: &mut Runnable,
13279 cx: &mut App,
13280 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13281 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13282 let (worktree_id, file) = project
13283 .buffer_for_id(runnable.buffer, cx)
13284 .and_then(|buffer| buffer.read(cx).file())
13285 .map(|file| (file.worktree_id(cx), file.clone()))
13286 .unzip();
13287
13288 (
13289 project.task_store().read(cx).task_inventory().cloned(),
13290 worktree_id,
13291 file,
13292 )
13293 });
13294
13295 let mut templates_with_tags = mem::take(&mut runnable.tags)
13296 .into_iter()
13297 .flat_map(|RunnableTag(tag)| {
13298 inventory
13299 .as_ref()
13300 .into_iter()
13301 .flat_map(|inventory| {
13302 inventory.read(cx).list_tasks(
13303 file.clone(),
13304 Some(runnable.language.clone()),
13305 worktree_id,
13306 cx,
13307 )
13308 })
13309 .filter(move |(_, template)| {
13310 template.tags.iter().any(|source_tag| source_tag == &tag)
13311 })
13312 })
13313 .sorted_by_key(|(kind, _)| kind.to_owned())
13314 .collect::<Vec<_>>();
13315 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13316 // Strongest source wins; if we have worktree tag binding, prefer that to
13317 // global and language bindings;
13318 // if we have a global binding, prefer that to language binding.
13319 let first_mismatch = templates_with_tags
13320 .iter()
13321 .position(|(tag_source, _)| tag_source != leading_tag_source);
13322 if let Some(index) = first_mismatch {
13323 templates_with_tags.truncate(index);
13324 }
13325 }
13326
13327 templates_with_tags
13328 }
13329
13330 pub fn move_to_enclosing_bracket(
13331 &mut self,
13332 _: &MoveToEnclosingBracket,
13333 window: &mut Window,
13334 cx: &mut Context<Self>,
13335 ) {
13336 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13337 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13338 s.move_offsets_with(|snapshot, selection| {
13339 let Some(enclosing_bracket_ranges) =
13340 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13341 else {
13342 return;
13343 };
13344
13345 let mut best_length = usize::MAX;
13346 let mut best_inside = false;
13347 let mut best_in_bracket_range = false;
13348 let mut best_destination = None;
13349 for (open, close) in enclosing_bracket_ranges {
13350 let close = close.to_inclusive();
13351 let length = close.end() - open.start;
13352 let inside = selection.start >= open.end && selection.end <= *close.start();
13353 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13354 || close.contains(&selection.head());
13355
13356 // If best is next to a bracket and current isn't, skip
13357 if !in_bracket_range && best_in_bracket_range {
13358 continue;
13359 }
13360
13361 // Prefer smaller lengths unless best is inside and current isn't
13362 if length > best_length && (best_inside || !inside) {
13363 continue;
13364 }
13365
13366 best_length = length;
13367 best_inside = inside;
13368 best_in_bracket_range = in_bracket_range;
13369 best_destination = Some(
13370 if close.contains(&selection.start) && close.contains(&selection.end) {
13371 if inside { open.end } else { open.start }
13372 } else if inside {
13373 *close.start()
13374 } else {
13375 *close.end()
13376 },
13377 );
13378 }
13379
13380 if let Some(destination) = best_destination {
13381 selection.collapse_to(destination, SelectionGoal::None);
13382 }
13383 })
13384 });
13385 }
13386
13387 pub fn undo_selection(
13388 &mut self,
13389 _: &UndoSelection,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13394 self.end_selection(window, cx);
13395 self.selection_history.mode = SelectionHistoryMode::Undoing;
13396 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13397 self.change_selections(None, window, cx, |s| {
13398 s.select_anchors(entry.selections.to_vec())
13399 });
13400 self.select_next_state = entry.select_next_state;
13401 self.select_prev_state = entry.select_prev_state;
13402 self.add_selections_state = entry.add_selections_state;
13403 self.request_autoscroll(Autoscroll::newest(), cx);
13404 }
13405 self.selection_history.mode = SelectionHistoryMode::Normal;
13406 }
13407
13408 pub fn redo_selection(
13409 &mut self,
13410 _: &RedoSelection,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13415 self.end_selection(window, cx);
13416 self.selection_history.mode = SelectionHistoryMode::Redoing;
13417 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13418 self.change_selections(None, window, cx, |s| {
13419 s.select_anchors(entry.selections.to_vec())
13420 });
13421 self.select_next_state = entry.select_next_state;
13422 self.select_prev_state = entry.select_prev_state;
13423 self.add_selections_state = entry.add_selections_state;
13424 self.request_autoscroll(Autoscroll::newest(), cx);
13425 }
13426 self.selection_history.mode = SelectionHistoryMode::Normal;
13427 }
13428
13429 pub fn expand_excerpts(
13430 &mut self,
13431 action: &ExpandExcerpts,
13432 _: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13436 }
13437
13438 pub fn expand_excerpts_down(
13439 &mut self,
13440 action: &ExpandExcerptsDown,
13441 _: &mut Window,
13442 cx: &mut Context<Self>,
13443 ) {
13444 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13445 }
13446
13447 pub fn expand_excerpts_up(
13448 &mut self,
13449 action: &ExpandExcerptsUp,
13450 _: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) {
13453 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13454 }
13455
13456 pub fn expand_excerpts_for_direction(
13457 &mut self,
13458 lines: u32,
13459 direction: ExpandExcerptDirection,
13460
13461 cx: &mut Context<Self>,
13462 ) {
13463 let selections = self.selections.disjoint_anchors();
13464
13465 let lines = if lines == 0 {
13466 EditorSettings::get_global(cx).expand_excerpt_lines
13467 } else {
13468 lines
13469 };
13470
13471 self.buffer.update(cx, |buffer, cx| {
13472 let snapshot = buffer.snapshot(cx);
13473 let mut excerpt_ids = selections
13474 .iter()
13475 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13476 .collect::<Vec<_>>();
13477 excerpt_ids.sort();
13478 excerpt_ids.dedup();
13479 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13480 })
13481 }
13482
13483 pub fn expand_excerpt(
13484 &mut self,
13485 excerpt: ExcerptId,
13486 direction: ExpandExcerptDirection,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 let current_scroll_position = self.scroll_position(cx);
13491 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13492 let mut should_scroll_up = false;
13493
13494 if direction == ExpandExcerptDirection::Down {
13495 let multi_buffer = self.buffer.read(cx);
13496 let snapshot = multi_buffer.snapshot(cx);
13497 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13498 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13499 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13500 let buffer_snapshot = buffer.read(cx).snapshot();
13501 let excerpt_end_row =
13502 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13503 let last_row = buffer_snapshot.max_point().row;
13504 let lines_below = last_row.saturating_sub(excerpt_end_row);
13505 should_scroll_up = lines_below >= lines_to_expand;
13506 }
13507 }
13508 }
13509 }
13510
13511 self.buffer.update(cx, |buffer, cx| {
13512 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13513 });
13514
13515 if should_scroll_up {
13516 let new_scroll_position =
13517 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13518 self.set_scroll_position(new_scroll_position, window, cx);
13519 }
13520 }
13521
13522 pub fn go_to_singleton_buffer_point(
13523 &mut self,
13524 point: Point,
13525 window: &mut Window,
13526 cx: &mut Context<Self>,
13527 ) {
13528 self.go_to_singleton_buffer_range(point..point, window, cx);
13529 }
13530
13531 pub fn go_to_singleton_buffer_range(
13532 &mut self,
13533 range: Range<Point>,
13534 window: &mut Window,
13535 cx: &mut Context<Self>,
13536 ) {
13537 let multibuffer = self.buffer().read(cx);
13538 let Some(buffer) = multibuffer.as_singleton() else {
13539 return;
13540 };
13541 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13542 return;
13543 };
13544 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13545 return;
13546 };
13547 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13548 s.select_anchor_ranges([start..end])
13549 });
13550 }
13551
13552 pub fn go_to_diagnostic(
13553 &mut self,
13554 _: &GoToDiagnostic,
13555 window: &mut Window,
13556 cx: &mut Context<Self>,
13557 ) {
13558 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13559 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13560 }
13561
13562 pub fn go_to_prev_diagnostic(
13563 &mut self,
13564 _: &GoToPreviousDiagnostic,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13569 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13570 }
13571
13572 pub fn go_to_diagnostic_impl(
13573 &mut self,
13574 direction: Direction,
13575 window: &mut Window,
13576 cx: &mut Context<Self>,
13577 ) {
13578 let buffer = self.buffer.read(cx).snapshot(cx);
13579 let selection = self.selections.newest::<usize>(cx);
13580
13581 let mut active_group_id = None;
13582 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13583 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13584 active_group_id = Some(active_group.group_id);
13585 }
13586 }
13587
13588 fn filtered(
13589 snapshot: EditorSnapshot,
13590 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13591 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13592 diagnostics
13593 .filter(|entry| entry.range.start != entry.range.end)
13594 .filter(|entry| !entry.diagnostic.is_unnecessary)
13595 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13596 }
13597
13598 let snapshot = self.snapshot(window, cx);
13599 let before = filtered(
13600 snapshot.clone(),
13601 buffer
13602 .diagnostics_in_range(0..selection.start)
13603 .filter(|entry| entry.range.start <= selection.start),
13604 );
13605 let after = filtered(
13606 snapshot,
13607 buffer
13608 .diagnostics_in_range(selection.start..buffer.len())
13609 .filter(|entry| entry.range.start >= selection.start),
13610 );
13611
13612 let mut found: Option<DiagnosticEntry<usize>> = None;
13613 if direction == Direction::Prev {
13614 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13615 {
13616 for diagnostic in prev_diagnostics.into_iter().rev() {
13617 if diagnostic.range.start != selection.start
13618 || active_group_id
13619 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13620 {
13621 found = Some(diagnostic);
13622 break 'outer;
13623 }
13624 }
13625 }
13626 } else {
13627 for diagnostic in after.chain(before) {
13628 if diagnostic.range.start != selection.start
13629 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13630 {
13631 found = Some(diagnostic);
13632 break;
13633 }
13634 }
13635 }
13636 let Some(next_diagnostic) = found else {
13637 return;
13638 };
13639
13640 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13641 return;
13642 };
13643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13644 s.select_ranges(vec![
13645 next_diagnostic.range.start..next_diagnostic.range.start,
13646 ])
13647 });
13648 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13649 self.refresh_inline_completion(false, true, window, cx);
13650 }
13651
13652 fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
13653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13654 let snapshot = self.snapshot(window, cx);
13655 let selection = self.selections.newest::<Point>(cx);
13656 self.go_to_hunk_before_or_after_position(
13657 &snapshot,
13658 selection.head(),
13659 Direction::Next,
13660 window,
13661 cx,
13662 );
13663 }
13664
13665 pub fn go_to_hunk_before_or_after_position(
13666 &mut self,
13667 snapshot: &EditorSnapshot,
13668 position: Point,
13669 direction: Direction,
13670 window: &mut Window,
13671 cx: &mut Context<Editor>,
13672 ) {
13673 let row = if direction == Direction::Next {
13674 self.hunk_after_position(snapshot, position)
13675 .map(|hunk| hunk.row_range.start)
13676 } else {
13677 self.hunk_before_position(snapshot, position)
13678 };
13679
13680 if let Some(row) = row {
13681 let destination = Point::new(row.0, 0);
13682 let autoscroll = Autoscroll::center();
13683
13684 self.unfold_ranges(&[destination..destination], false, false, cx);
13685 self.change_selections(Some(autoscroll), window, cx, |s| {
13686 s.select_ranges([destination..destination]);
13687 });
13688 }
13689 }
13690
13691 fn hunk_after_position(
13692 &mut self,
13693 snapshot: &EditorSnapshot,
13694 position: Point,
13695 ) -> Option<MultiBufferDiffHunk> {
13696 snapshot
13697 .buffer_snapshot
13698 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
13699 .find(|hunk| hunk.row_range.start.0 > position.row)
13700 .or_else(|| {
13701 snapshot
13702 .buffer_snapshot
13703 .diff_hunks_in_range(Point::zero()..position)
13704 .find(|hunk| hunk.row_range.end.0 < position.row)
13705 })
13706 }
13707
13708 fn go_to_prev_hunk(
13709 &mut self,
13710 _: &GoToPreviousHunk,
13711 window: &mut Window,
13712 cx: &mut Context<Self>,
13713 ) {
13714 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13715 let snapshot = self.snapshot(window, cx);
13716 let selection = self.selections.newest::<Point>(cx);
13717 self.go_to_hunk_before_or_after_position(
13718 &snapshot,
13719 selection.head(),
13720 Direction::Prev,
13721 window,
13722 cx,
13723 );
13724 }
13725
13726 fn hunk_before_position(
13727 &mut self,
13728 snapshot: &EditorSnapshot,
13729 position: Point,
13730 ) -> Option<MultiBufferRow> {
13731 snapshot
13732 .buffer_snapshot
13733 .diff_hunk_before(position)
13734 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
13735 }
13736
13737 fn go_to_next_change(
13738 &mut self,
13739 _: &GoToNextChange,
13740 window: &mut Window,
13741 cx: &mut Context<Self>,
13742 ) {
13743 if let Some(selections) = self
13744 .change_list
13745 .next_change(1, Direction::Next)
13746 .map(|s| s.to_vec())
13747 {
13748 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13749 let map = s.display_map();
13750 s.select_display_ranges(selections.iter().map(|a| {
13751 let point = a.to_display_point(&map);
13752 point..point
13753 }))
13754 })
13755 }
13756 }
13757
13758 fn go_to_previous_change(
13759 &mut self,
13760 _: &GoToPreviousChange,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 if let Some(selections) = self
13765 .change_list
13766 .next_change(1, Direction::Prev)
13767 .map(|s| s.to_vec())
13768 {
13769 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13770 let map = s.display_map();
13771 s.select_display_ranges(selections.iter().map(|a| {
13772 let point = a.to_display_point(&map);
13773 point..point
13774 }))
13775 })
13776 }
13777 }
13778
13779 fn go_to_line<T: 'static>(
13780 &mut self,
13781 position: Anchor,
13782 highlight_color: Option<Hsla>,
13783 window: &mut Window,
13784 cx: &mut Context<Self>,
13785 ) {
13786 let snapshot = self.snapshot(window, cx).display_snapshot;
13787 let position = position.to_point(&snapshot.buffer_snapshot);
13788 let start = snapshot
13789 .buffer_snapshot
13790 .clip_point(Point::new(position.row, 0), Bias::Left);
13791 let end = start + Point::new(1, 0);
13792 let start = snapshot.buffer_snapshot.anchor_before(start);
13793 let end = snapshot.buffer_snapshot.anchor_before(end);
13794
13795 self.highlight_rows::<T>(
13796 start..end,
13797 highlight_color
13798 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
13799 Default::default(),
13800 cx,
13801 );
13802 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
13803 }
13804
13805 pub fn go_to_definition(
13806 &mut self,
13807 _: &GoToDefinition,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) -> Task<Result<Navigated>> {
13811 let definition =
13812 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
13813 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
13814 cx.spawn_in(window, async move |editor, cx| {
13815 if definition.await? == Navigated::Yes {
13816 return Ok(Navigated::Yes);
13817 }
13818 match fallback_strategy {
13819 GoToDefinitionFallback::None => Ok(Navigated::No),
13820 GoToDefinitionFallback::FindAllReferences => {
13821 match editor.update_in(cx, |editor, window, cx| {
13822 editor.find_all_references(&FindAllReferences, window, cx)
13823 })? {
13824 Some(references) => references.await,
13825 None => Ok(Navigated::No),
13826 }
13827 }
13828 }
13829 })
13830 }
13831
13832 pub fn go_to_declaration(
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, false, window, cx)
13839 }
13840
13841 pub fn go_to_declaration_split(
13842 &mut self,
13843 _: &GoToDeclaration,
13844 window: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) -> Task<Result<Navigated>> {
13847 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
13848 }
13849
13850 pub fn go_to_implementation(
13851 &mut self,
13852 _: &GoToImplementation,
13853 window: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) -> Task<Result<Navigated>> {
13856 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
13857 }
13858
13859 pub fn go_to_implementation_split(
13860 &mut self,
13861 _: &GoToImplementationSplit,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) -> Task<Result<Navigated>> {
13865 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
13866 }
13867
13868 pub fn go_to_type_definition(
13869 &mut self,
13870 _: &GoToTypeDefinition,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) -> Task<Result<Navigated>> {
13874 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
13875 }
13876
13877 pub fn go_to_definition_split(
13878 &mut self,
13879 _: &GoToDefinitionSplit,
13880 window: &mut Window,
13881 cx: &mut Context<Self>,
13882 ) -> Task<Result<Navigated>> {
13883 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
13884 }
13885
13886 pub fn go_to_type_definition_split(
13887 &mut self,
13888 _: &GoToTypeDefinitionSplit,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) -> Task<Result<Navigated>> {
13892 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
13893 }
13894
13895 fn go_to_definition_of_kind(
13896 &mut self,
13897 kind: GotoDefinitionKind,
13898 split: bool,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) -> Task<Result<Navigated>> {
13902 let Some(provider) = self.semantics_provider.clone() else {
13903 return Task::ready(Ok(Navigated::No));
13904 };
13905 let head = self.selections.newest::<usize>(cx).head();
13906 let buffer = self.buffer.read(cx);
13907 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
13908 text_anchor
13909 } else {
13910 return Task::ready(Ok(Navigated::No));
13911 };
13912
13913 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
13914 return Task::ready(Ok(Navigated::No));
13915 };
13916
13917 cx.spawn_in(window, async move |editor, cx| {
13918 let definitions = definitions.await?;
13919 let navigated = editor
13920 .update_in(cx, |editor, window, cx| {
13921 editor.navigate_to_hover_links(
13922 Some(kind),
13923 definitions
13924 .into_iter()
13925 .filter(|location| {
13926 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
13927 })
13928 .map(HoverLink::Text)
13929 .collect::<Vec<_>>(),
13930 split,
13931 window,
13932 cx,
13933 )
13934 })?
13935 .await?;
13936 anyhow::Ok(navigated)
13937 })
13938 }
13939
13940 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
13941 let selection = self.selections.newest_anchor();
13942 let head = selection.head();
13943 let tail = selection.tail();
13944
13945 let Some((buffer, start_position)) =
13946 self.buffer.read(cx).text_anchor_for_position(head, cx)
13947 else {
13948 return;
13949 };
13950
13951 let end_position = if head != tail {
13952 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
13953 return;
13954 };
13955 Some(pos)
13956 } else {
13957 None
13958 };
13959
13960 let url_finder = cx.spawn_in(window, async move |editor, cx| {
13961 let url = if let Some(end_pos) = end_position {
13962 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
13963 } else {
13964 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
13965 };
13966
13967 if let Some(url) = url {
13968 editor.update(cx, |_, cx| {
13969 cx.open_url(&url);
13970 })
13971 } else {
13972 Ok(())
13973 }
13974 });
13975
13976 url_finder.detach();
13977 }
13978
13979 pub fn open_selected_filename(
13980 &mut self,
13981 _: &OpenSelectedFilename,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 let Some(workspace) = self.workspace() else {
13986 return;
13987 };
13988
13989 let position = self.selections.newest_anchor().head();
13990
13991 let Some((buffer, buffer_position)) =
13992 self.buffer.read(cx).text_anchor_for_position(position, cx)
13993 else {
13994 return;
13995 };
13996
13997 let project = self.project.clone();
13998
13999 cx.spawn_in(window, async move |_, cx| {
14000 let result = find_file(&buffer, project, buffer_position, cx).await;
14001
14002 if let Some((_, path)) = result {
14003 workspace
14004 .update_in(cx, |workspace, window, cx| {
14005 workspace.open_resolved_path(path, window, cx)
14006 })?
14007 .await?;
14008 }
14009 anyhow::Ok(())
14010 })
14011 .detach();
14012 }
14013
14014 pub(crate) fn navigate_to_hover_links(
14015 &mut self,
14016 kind: Option<GotoDefinitionKind>,
14017 mut definitions: Vec<HoverLink>,
14018 split: bool,
14019 window: &mut Window,
14020 cx: &mut Context<Editor>,
14021 ) -> Task<Result<Navigated>> {
14022 // If there is one definition, just open it directly
14023 if definitions.len() == 1 {
14024 let definition = definitions.pop().unwrap();
14025
14026 enum TargetTaskResult {
14027 Location(Option<Location>),
14028 AlreadyNavigated,
14029 }
14030
14031 let target_task = match definition {
14032 HoverLink::Text(link) => {
14033 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14034 }
14035 HoverLink::InlayHint(lsp_location, server_id) => {
14036 let computation =
14037 self.compute_target_location(lsp_location, server_id, window, cx);
14038 cx.background_spawn(async move {
14039 let location = computation.await?;
14040 Ok(TargetTaskResult::Location(location))
14041 })
14042 }
14043 HoverLink::Url(url) => {
14044 cx.open_url(&url);
14045 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14046 }
14047 HoverLink::File(path) => {
14048 if let Some(workspace) = self.workspace() {
14049 cx.spawn_in(window, async move |_, cx| {
14050 workspace
14051 .update_in(cx, |workspace, window, cx| {
14052 workspace.open_resolved_path(path, window, cx)
14053 })?
14054 .await
14055 .map(|_| TargetTaskResult::AlreadyNavigated)
14056 })
14057 } else {
14058 Task::ready(Ok(TargetTaskResult::Location(None)))
14059 }
14060 }
14061 };
14062 cx.spawn_in(window, async move |editor, cx| {
14063 let target = match target_task.await.context("target resolution task")? {
14064 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14065 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14066 TargetTaskResult::Location(Some(target)) => target,
14067 };
14068
14069 editor.update_in(cx, |editor, window, cx| {
14070 let Some(workspace) = editor.workspace() else {
14071 return Navigated::No;
14072 };
14073 let pane = workspace.read(cx).active_pane().clone();
14074
14075 let range = target.range.to_point(target.buffer.read(cx));
14076 let range = editor.range_for_match(&range);
14077 let range = collapse_multiline_range(range);
14078
14079 if !split
14080 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14081 {
14082 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14083 } else {
14084 window.defer(cx, move |window, cx| {
14085 let target_editor: Entity<Self> =
14086 workspace.update(cx, |workspace, cx| {
14087 let pane = if split {
14088 workspace.adjacent_pane(window, cx)
14089 } else {
14090 workspace.active_pane().clone()
14091 };
14092
14093 workspace.open_project_item(
14094 pane,
14095 target.buffer.clone(),
14096 true,
14097 true,
14098 window,
14099 cx,
14100 )
14101 });
14102 target_editor.update(cx, |target_editor, cx| {
14103 // When selecting a definition in a different buffer, disable the nav history
14104 // to avoid creating a history entry at the previous cursor location.
14105 pane.update(cx, |pane, _| pane.disable_history());
14106 target_editor.go_to_singleton_buffer_range(range, window, cx);
14107 pane.update(cx, |pane, _| pane.enable_history());
14108 });
14109 });
14110 }
14111 Navigated::Yes
14112 })
14113 })
14114 } else if !definitions.is_empty() {
14115 cx.spawn_in(window, async move |editor, cx| {
14116 let (title, location_tasks, workspace) = editor
14117 .update_in(cx, |editor, window, cx| {
14118 let tab_kind = match kind {
14119 Some(GotoDefinitionKind::Implementation) => "Implementations",
14120 _ => "Definitions",
14121 };
14122 let title = definitions
14123 .iter()
14124 .find_map(|definition| match definition {
14125 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14126 let buffer = origin.buffer.read(cx);
14127 format!(
14128 "{} for {}",
14129 tab_kind,
14130 buffer
14131 .text_for_range(origin.range.clone())
14132 .collect::<String>()
14133 )
14134 }),
14135 HoverLink::InlayHint(_, _) => None,
14136 HoverLink::Url(_) => None,
14137 HoverLink::File(_) => None,
14138 })
14139 .unwrap_or(tab_kind.to_string());
14140 let location_tasks = definitions
14141 .into_iter()
14142 .map(|definition| match definition {
14143 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14144 HoverLink::InlayHint(lsp_location, server_id) => editor
14145 .compute_target_location(lsp_location, server_id, window, cx),
14146 HoverLink::Url(_) => Task::ready(Ok(None)),
14147 HoverLink::File(_) => Task::ready(Ok(None)),
14148 })
14149 .collect::<Vec<_>>();
14150 (title, location_tasks, editor.workspace().clone())
14151 })
14152 .context("location tasks preparation")?;
14153
14154 let locations = future::join_all(location_tasks)
14155 .await
14156 .into_iter()
14157 .filter_map(|location| location.transpose())
14158 .collect::<Result<_>>()
14159 .context("location tasks")?;
14160
14161 let Some(workspace) = workspace else {
14162 return Ok(Navigated::No);
14163 };
14164 let opened = workspace
14165 .update_in(cx, |workspace, window, cx| {
14166 Self::open_locations_in_multibuffer(
14167 workspace,
14168 locations,
14169 title,
14170 split,
14171 MultibufferSelectionMode::First,
14172 window,
14173 cx,
14174 )
14175 })
14176 .ok();
14177
14178 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14179 })
14180 } else {
14181 Task::ready(Ok(Navigated::No))
14182 }
14183 }
14184
14185 fn compute_target_location(
14186 &self,
14187 lsp_location: lsp::Location,
14188 server_id: LanguageServerId,
14189 window: &mut Window,
14190 cx: &mut Context<Self>,
14191 ) -> Task<anyhow::Result<Option<Location>>> {
14192 let Some(project) = self.project.clone() else {
14193 return Task::ready(Ok(None));
14194 };
14195
14196 cx.spawn_in(window, async move |editor, cx| {
14197 let location_task = editor.update(cx, |_, cx| {
14198 project.update(cx, |project, cx| {
14199 let language_server_name = project
14200 .language_server_statuses(cx)
14201 .find(|(id, _)| server_id == *id)
14202 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14203 language_server_name.map(|language_server_name| {
14204 project.open_local_buffer_via_lsp(
14205 lsp_location.uri.clone(),
14206 server_id,
14207 language_server_name,
14208 cx,
14209 )
14210 })
14211 })
14212 })?;
14213 let location = match location_task {
14214 Some(task) => Some({
14215 let target_buffer_handle = task.await.context("open local buffer")?;
14216 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14217 let target_start = target_buffer
14218 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14219 let target_end = target_buffer
14220 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14221 target_buffer.anchor_after(target_start)
14222 ..target_buffer.anchor_before(target_end)
14223 })?;
14224 Location {
14225 buffer: target_buffer_handle,
14226 range,
14227 }
14228 }),
14229 None => None,
14230 };
14231 Ok(location)
14232 })
14233 }
14234
14235 pub fn find_all_references(
14236 &mut self,
14237 _: &FindAllReferences,
14238 window: &mut Window,
14239 cx: &mut Context<Self>,
14240 ) -> Option<Task<Result<Navigated>>> {
14241 let selection = self.selections.newest::<usize>(cx);
14242 let multi_buffer = self.buffer.read(cx);
14243 let head = selection.head();
14244
14245 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14246 let head_anchor = multi_buffer_snapshot.anchor_at(
14247 head,
14248 if head < selection.tail() {
14249 Bias::Right
14250 } else {
14251 Bias::Left
14252 },
14253 );
14254
14255 match self
14256 .find_all_references_task_sources
14257 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14258 {
14259 Ok(_) => {
14260 log::info!(
14261 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14262 );
14263 return None;
14264 }
14265 Err(i) => {
14266 self.find_all_references_task_sources.insert(i, head_anchor);
14267 }
14268 }
14269
14270 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14271 let workspace = self.workspace()?;
14272 let project = workspace.read(cx).project().clone();
14273 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14274 Some(cx.spawn_in(window, async move |editor, cx| {
14275 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14276 if let Ok(i) = editor
14277 .find_all_references_task_sources
14278 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14279 {
14280 editor.find_all_references_task_sources.remove(i);
14281 }
14282 });
14283
14284 let locations = references.await?;
14285 if locations.is_empty() {
14286 return anyhow::Ok(Navigated::No);
14287 }
14288
14289 workspace.update_in(cx, |workspace, window, cx| {
14290 let title = locations
14291 .first()
14292 .as_ref()
14293 .map(|location| {
14294 let buffer = location.buffer.read(cx);
14295 format!(
14296 "References to `{}`",
14297 buffer
14298 .text_for_range(location.range.clone())
14299 .collect::<String>()
14300 )
14301 })
14302 .unwrap();
14303 Self::open_locations_in_multibuffer(
14304 workspace,
14305 locations,
14306 title,
14307 false,
14308 MultibufferSelectionMode::First,
14309 window,
14310 cx,
14311 );
14312 Navigated::Yes
14313 })
14314 }))
14315 }
14316
14317 /// Opens a multibuffer with the given project locations in it
14318 pub fn open_locations_in_multibuffer(
14319 workspace: &mut Workspace,
14320 mut locations: Vec<Location>,
14321 title: String,
14322 split: bool,
14323 multibuffer_selection_mode: MultibufferSelectionMode,
14324 window: &mut Window,
14325 cx: &mut Context<Workspace>,
14326 ) {
14327 // If there are multiple definitions, open them in a multibuffer
14328 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14329 let mut locations = locations.into_iter().peekable();
14330 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14331 let capability = workspace.project().read(cx).capability();
14332
14333 let excerpt_buffer = cx.new(|cx| {
14334 let mut multibuffer = MultiBuffer::new(capability);
14335 while let Some(location) = locations.next() {
14336 let buffer = location.buffer.read(cx);
14337 let mut ranges_for_buffer = Vec::new();
14338 let range = location.range.to_point(buffer);
14339 ranges_for_buffer.push(range.clone());
14340
14341 while let Some(next_location) = locations.peek() {
14342 if next_location.buffer == location.buffer {
14343 ranges_for_buffer.push(next_location.range.to_point(buffer));
14344 locations.next();
14345 } else {
14346 break;
14347 }
14348 }
14349
14350 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14351 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14352 PathKey::for_buffer(&location.buffer, cx),
14353 location.buffer.clone(),
14354 ranges_for_buffer,
14355 DEFAULT_MULTIBUFFER_CONTEXT,
14356 cx,
14357 );
14358 ranges.extend(new_ranges)
14359 }
14360
14361 multibuffer.with_title(title)
14362 });
14363
14364 let editor = cx.new(|cx| {
14365 Editor::for_multibuffer(
14366 excerpt_buffer,
14367 Some(workspace.project().clone()),
14368 window,
14369 cx,
14370 )
14371 });
14372 editor.update(cx, |editor, cx| {
14373 match multibuffer_selection_mode {
14374 MultibufferSelectionMode::First => {
14375 if let Some(first_range) = ranges.first() {
14376 editor.change_selections(None, window, cx, |selections| {
14377 selections.clear_disjoint();
14378 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14379 });
14380 }
14381 editor.highlight_background::<Self>(
14382 &ranges,
14383 |theme| theme.editor_highlighted_line_background,
14384 cx,
14385 );
14386 }
14387 MultibufferSelectionMode::All => {
14388 editor.change_selections(None, window, cx, |selections| {
14389 selections.clear_disjoint();
14390 selections.select_anchor_ranges(ranges);
14391 });
14392 }
14393 }
14394 editor.register_buffers_with_language_servers(cx);
14395 });
14396
14397 let item = Box::new(editor);
14398 let item_id = item.item_id();
14399
14400 if split {
14401 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14402 } else {
14403 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14404 let (preview_item_id, preview_item_idx) =
14405 workspace.active_pane().update(cx, |pane, _| {
14406 (pane.preview_item_id(), pane.preview_item_idx())
14407 });
14408
14409 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14410
14411 if let Some(preview_item_id) = preview_item_id {
14412 workspace.active_pane().update(cx, |pane, cx| {
14413 pane.remove_item(preview_item_id, false, false, window, cx);
14414 });
14415 }
14416 } else {
14417 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14418 }
14419 }
14420 workspace.active_pane().update(cx, |pane, cx| {
14421 pane.set_preview_item_id(Some(item_id), cx);
14422 });
14423 }
14424
14425 pub fn rename(
14426 &mut self,
14427 _: &Rename,
14428 window: &mut Window,
14429 cx: &mut Context<Self>,
14430 ) -> Option<Task<Result<()>>> {
14431 use language::ToOffset as _;
14432
14433 let provider = self.semantics_provider.clone()?;
14434 let selection = self.selections.newest_anchor().clone();
14435 let (cursor_buffer, cursor_buffer_position) = self
14436 .buffer
14437 .read(cx)
14438 .text_anchor_for_position(selection.head(), cx)?;
14439 let (tail_buffer, cursor_buffer_position_end) = self
14440 .buffer
14441 .read(cx)
14442 .text_anchor_for_position(selection.tail(), cx)?;
14443 if tail_buffer != cursor_buffer {
14444 return None;
14445 }
14446
14447 let snapshot = cursor_buffer.read(cx).snapshot();
14448 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14449 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14450 let prepare_rename = provider
14451 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14452 .unwrap_or_else(|| Task::ready(Ok(None)));
14453 drop(snapshot);
14454
14455 Some(cx.spawn_in(window, async move |this, cx| {
14456 let rename_range = if let Some(range) = prepare_rename.await? {
14457 Some(range)
14458 } else {
14459 this.update(cx, |this, cx| {
14460 let buffer = this.buffer.read(cx).snapshot(cx);
14461 let mut buffer_highlights = this
14462 .document_highlights_for_position(selection.head(), &buffer)
14463 .filter(|highlight| {
14464 highlight.start.excerpt_id == selection.head().excerpt_id
14465 && highlight.end.excerpt_id == selection.head().excerpt_id
14466 });
14467 buffer_highlights
14468 .next()
14469 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14470 })?
14471 };
14472 if let Some(rename_range) = rename_range {
14473 this.update_in(cx, |this, window, cx| {
14474 let snapshot = cursor_buffer.read(cx).snapshot();
14475 let rename_buffer_range = rename_range.to_offset(&snapshot);
14476 let cursor_offset_in_rename_range =
14477 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14478 let cursor_offset_in_rename_range_end =
14479 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14480
14481 this.take_rename(false, window, cx);
14482 let buffer = this.buffer.read(cx).read(cx);
14483 let cursor_offset = selection.head().to_offset(&buffer);
14484 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14485 let rename_end = rename_start + rename_buffer_range.len();
14486 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14487 let mut old_highlight_id = None;
14488 let old_name: Arc<str> = buffer
14489 .chunks(rename_start..rename_end, true)
14490 .map(|chunk| {
14491 if old_highlight_id.is_none() {
14492 old_highlight_id = chunk.syntax_highlight_id;
14493 }
14494 chunk.text
14495 })
14496 .collect::<String>()
14497 .into();
14498
14499 drop(buffer);
14500
14501 // Position the selection in the rename editor so that it matches the current selection.
14502 this.show_local_selections = false;
14503 let rename_editor = cx.new(|cx| {
14504 let mut editor = Editor::single_line(window, cx);
14505 editor.buffer.update(cx, |buffer, cx| {
14506 buffer.edit([(0..0, old_name.clone())], None, cx)
14507 });
14508 let rename_selection_range = match cursor_offset_in_rename_range
14509 .cmp(&cursor_offset_in_rename_range_end)
14510 {
14511 Ordering::Equal => {
14512 editor.select_all(&SelectAll, window, cx);
14513 return editor;
14514 }
14515 Ordering::Less => {
14516 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14517 }
14518 Ordering::Greater => {
14519 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14520 }
14521 };
14522 if rename_selection_range.end > old_name.len() {
14523 editor.select_all(&SelectAll, window, cx);
14524 } else {
14525 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14526 s.select_ranges([rename_selection_range]);
14527 });
14528 }
14529 editor
14530 });
14531 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14532 if e == &EditorEvent::Focused {
14533 cx.emit(EditorEvent::FocusedIn)
14534 }
14535 })
14536 .detach();
14537
14538 let write_highlights =
14539 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14540 let read_highlights =
14541 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14542 let ranges = write_highlights
14543 .iter()
14544 .flat_map(|(_, ranges)| ranges.iter())
14545 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14546 .cloned()
14547 .collect();
14548
14549 this.highlight_text::<Rename>(
14550 ranges,
14551 HighlightStyle {
14552 fade_out: Some(0.6),
14553 ..Default::default()
14554 },
14555 cx,
14556 );
14557 let rename_focus_handle = rename_editor.focus_handle(cx);
14558 window.focus(&rename_focus_handle);
14559 let block_id = this.insert_blocks(
14560 [BlockProperties {
14561 style: BlockStyle::Flex,
14562 placement: BlockPlacement::Below(range.start),
14563 height: Some(1),
14564 render: Arc::new({
14565 let rename_editor = rename_editor.clone();
14566 move |cx: &mut BlockContext| {
14567 let mut text_style = cx.editor_style.text.clone();
14568 if let Some(highlight_style) = old_highlight_id
14569 .and_then(|h| h.style(&cx.editor_style.syntax))
14570 {
14571 text_style = text_style.highlight(highlight_style);
14572 }
14573 div()
14574 .block_mouse_down()
14575 .pl(cx.anchor_x)
14576 .child(EditorElement::new(
14577 &rename_editor,
14578 EditorStyle {
14579 background: cx.theme().system().transparent,
14580 local_player: cx.editor_style.local_player,
14581 text: text_style,
14582 scrollbar_width: cx.editor_style.scrollbar_width,
14583 syntax: cx.editor_style.syntax.clone(),
14584 status: cx.editor_style.status.clone(),
14585 inlay_hints_style: HighlightStyle {
14586 font_weight: Some(FontWeight::BOLD),
14587 ..make_inlay_hints_style(cx.app)
14588 },
14589 inline_completion_styles: make_suggestion_styles(
14590 cx.app,
14591 ),
14592 ..EditorStyle::default()
14593 },
14594 ))
14595 .into_any_element()
14596 }
14597 }),
14598 priority: 0,
14599 }],
14600 Some(Autoscroll::fit()),
14601 cx,
14602 )[0];
14603 this.pending_rename = Some(RenameState {
14604 range,
14605 old_name,
14606 editor: rename_editor,
14607 block_id,
14608 });
14609 })?;
14610 }
14611
14612 Ok(())
14613 }))
14614 }
14615
14616 pub fn confirm_rename(
14617 &mut self,
14618 _: &ConfirmRename,
14619 window: &mut Window,
14620 cx: &mut Context<Self>,
14621 ) -> Option<Task<Result<()>>> {
14622 let rename = self.take_rename(false, window, cx)?;
14623 let workspace = self.workspace()?.downgrade();
14624 let (buffer, start) = self
14625 .buffer
14626 .read(cx)
14627 .text_anchor_for_position(rename.range.start, cx)?;
14628 let (end_buffer, _) = self
14629 .buffer
14630 .read(cx)
14631 .text_anchor_for_position(rename.range.end, cx)?;
14632 if buffer != end_buffer {
14633 return None;
14634 }
14635
14636 let old_name = rename.old_name;
14637 let new_name = rename.editor.read(cx).text(cx);
14638
14639 let rename = self.semantics_provider.as_ref()?.perform_rename(
14640 &buffer,
14641 start,
14642 new_name.clone(),
14643 cx,
14644 )?;
14645
14646 Some(cx.spawn_in(window, async move |editor, cx| {
14647 let project_transaction = rename.await?;
14648 Self::open_project_transaction(
14649 &editor,
14650 workspace,
14651 project_transaction,
14652 format!("Rename: {} → {}", old_name, new_name),
14653 cx,
14654 )
14655 .await?;
14656
14657 editor.update(cx, |editor, cx| {
14658 editor.refresh_document_highlights(cx);
14659 })?;
14660 Ok(())
14661 }))
14662 }
14663
14664 fn take_rename(
14665 &mut self,
14666 moving_cursor: bool,
14667 window: &mut Window,
14668 cx: &mut Context<Self>,
14669 ) -> Option<RenameState> {
14670 let rename = self.pending_rename.take()?;
14671 if rename.editor.focus_handle(cx).is_focused(window) {
14672 window.focus(&self.focus_handle);
14673 }
14674
14675 self.remove_blocks(
14676 [rename.block_id].into_iter().collect(),
14677 Some(Autoscroll::fit()),
14678 cx,
14679 );
14680 self.clear_highlights::<Rename>(cx);
14681 self.show_local_selections = true;
14682
14683 if moving_cursor {
14684 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
14685 editor.selections.newest::<usize>(cx).head()
14686 });
14687
14688 // Update the selection to match the position of the selection inside
14689 // the rename editor.
14690 let snapshot = self.buffer.read(cx).read(cx);
14691 let rename_range = rename.range.to_offset(&snapshot);
14692 let cursor_in_editor = snapshot
14693 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
14694 .min(rename_range.end);
14695 drop(snapshot);
14696
14697 self.change_selections(None, window, cx, |s| {
14698 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
14699 });
14700 } else {
14701 self.refresh_document_highlights(cx);
14702 }
14703
14704 Some(rename)
14705 }
14706
14707 pub fn pending_rename(&self) -> Option<&RenameState> {
14708 self.pending_rename.as_ref()
14709 }
14710
14711 fn format(
14712 &mut self,
14713 _: &Format,
14714 window: &mut Window,
14715 cx: &mut Context<Self>,
14716 ) -> Option<Task<Result<()>>> {
14717 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14718
14719 let project = match &self.project {
14720 Some(project) => project.clone(),
14721 None => return None,
14722 };
14723
14724 Some(self.perform_format(
14725 project,
14726 FormatTrigger::Manual,
14727 FormatTarget::Buffers,
14728 window,
14729 cx,
14730 ))
14731 }
14732
14733 fn format_selections(
14734 &mut self,
14735 _: &FormatSelections,
14736 window: &mut Window,
14737 cx: &mut Context<Self>,
14738 ) -> Option<Task<Result<()>>> {
14739 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14740
14741 let project = match &self.project {
14742 Some(project) => project.clone(),
14743 None => return None,
14744 };
14745
14746 let ranges = self
14747 .selections
14748 .all_adjusted(cx)
14749 .into_iter()
14750 .map(|selection| selection.range())
14751 .collect_vec();
14752
14753 Some(self.perform_format(
14754 project,
14755 FormatTrigger::Manual,
14756 FormatTarget::Ranges(ranges),
14757 window,
14758 cx,
14759 ))
14760 }
14761
14762 fn perform_format(
14763 &mut self,
14764 project: Entity<Project>,
14765 trigger: FormatTrigger,
14766 target: FormatTarget,
14767 window: &mut Window,
14768 cx: &mut Context<Self>,
14769 ) -> Task<Result<()>> {
14770 let buffer = self.buffer.clone();
14771 let (buffers, target) = match target {
14772 FormatTarget::Buffers => {
14773 let mut buffers = buffer.read(cx).all_buffers();
14774 if trigger == FormatTrigger::Save {
14775 buffers.retain(|buffer| buffer.read(cx).is_dirty());
14776 }
14777 (buffers, LspFormatTarget::Buffers)
14778 }
14779 FormatTarget::Ranges(selection_ranges) => {
14780 let multi_buffer = buffer.read(cx);
14781 let snapshot = multi_buffer.read(cx);
14782 let mut buffers = HashSet::default();
14783 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
14784 BTreeMap::new();
14785 for selection_range in selection_ranges {
14786 for (buffer, buffer_range, _) in
14787 snapshot.range_to_buffer_ranges(selection_range)
14788 {
14789 let buffer_id = buffer.remote_id();
14790 let start = buffer.anchor_before(buffer_range.start);
14791 let end = buffer.anchor_after(buffer_range.end);
14792 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
14793 buffer_id_to_ranges
14794 .entry(buffer_id)
14795 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
14796 .or_insert_with(|| vec![start..end]);
14797 }
14798 }
14799 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
14800 }
14801 };
14802
14803 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
14804 let selections_prev = transaction_id_prev
14805 .and_then(|transaction_id_prev| {
14806 // default to selections as they were after the last edit, if we have them,
14807 // instead of how they are now.
14808 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
14809 // will take you back to where you made the last edit, instead of staying where you scrolled
14810 self.selection_history
14811 .transaction(transaction_id_prev)
14812 .map(|t| t.0.clone())
14813 })
14814 .unwrap_or_else(|| {
14815 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
14816 self.selections.disjoint_anchors()
14817 });
14818
14819 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
14820 let format = project.update(cx, |project, cx| {
14821 project.format(buffers, target, true, trigger, cx)
14822 });
14823
14824 cx.spawn_in(window, async move |editor, cx| {
14825 let transaction = futures::select_biased! {
14826 transaction = format.log_err().fuse() => transaction,
14827 () = timeout => {
14828 log::warn!("timed out waiting for formatting");
14829 None
14830 }
14831 };
14832
14833 buffer
14834 .update(cx, |buffer, cx| {
14835 if let Some(transaction) = transaction {
14836 if !buffer.is_singleton() {
14837 buffer.push_transaction(&transaction.0, cx);
14838 }
14839 }
14840 cx.notify();
14841 })
14842 .ok();
14843
14844 if let Some(transaction_id_now) =
14845 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
14846 {
14847 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
14848 if has_new_transaction {
14849 _ = editor.update(cx, |editor, _| {
14850 editor
14851 .selection_history
14852 .insert_transaction(transaction_id_now, selections_prev);
14853 });
14854 }
14855 }
14856
14857 Ok(())
14858 })
14859 }
14860
14861 fn organize_imports(
14862 &mut self,
14863 _: &OrganizeImports,
14864 window: &mut Window,
14865 cx: &mut Context<Self>,
14866 ) -> Option<Task<Result<()>>> {
14867 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
14868 let project = match &self.project {
14869 Some(project) => project.clone(),
14870 None => return None,
14871 };
14872 Some(self.perform_code_action_kind(
14873 project,
14874 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14875 window,
14876 cx,
14877 ))
14878 }
14879
14880 fn perform_code_action_kind(
14881 &mut self,
14882 project: Entity<Project>,
14883 kind: CodeActionKind,
14884 window: &mut Window,
14885 cx: &mut Context<Self>,
14886 ) -> Task<Result<()>> {
14887 let buffer = self.buffer.clone();
14888 let buffers = buffer.read(cx).all_buffers();
14889 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
14890 let apply_action = project.update(cx, |project, cx| {
14891 project.apply_code_action_kind(buffers, kind, true, cx)
14892 });
14893 cx.spawn_in(window, async move |_, cx| {
14894 let transaction = futures::select_biased! {
14895 () = timeout => {
14896 log::warn!("timed out waiting for executing code action");
14897 None
14898 }
14899 transaction = apply_action.log_err().fuse() => transaction,
14900 };
14901 buffer
14902 .update(cx, |buffer, cx| {
14903 // check if we need this
14904 if let Some(transaction) = transaction {
14905 if !buffer.is_singleton() {
14906 buffer.push_transaction(&transaction.0, cx);
14907 }
14908 }
14909 cx.notify();
14910 })
14911 .ok();
14912 Ok(())
14913 })
14914 }
14915
14916 fn restart_language_server(
14917 &mut self,
14918 _: &RestartLanguageServer,
14919 _: &mut Window,
14920 cx: &mut Context<Self>,
14921 ) {
14922 if let Some(project) = self.project.clone() {
14923 self.buffer.update(cx, |multi_buffer, cx| {
14924 project.update(cx, |project, cx| {
14925 project.restart_language_servers_for_buffers(
14926 multi_buffer.all_buffers().into_iter().collect(),
14927 cx,
14928 );
14929 });
14930 })
14931 }
14932 }
14933
14934 fn stop_language_server(
14935 &mut self,
14936 _: &StopLanguageServer,
14937 _: &mut Window,
14938 cx: &mut Context<Self>,
14939 ) {
14940 if let Some(project) = self.project.clone() {
14941 self.buffer.update(cx, |multi_buffer, cx| {
14942 project.update(cx, |project, cx| {
14943 project.stop_language_servers_for_buffers(
14944 multi_buffer.all_buffers().into_iter().collect(),
14945 cx,
14946 );
14947 cx.emit(project::Event::RefreshInlayHints);
14948 });
14949 });
14950 }
14951 }
14952
14953 fn cancel_language_server_work(
14954 workspace: &mut Workspace,
14955 _: &actions::CancelLanguageServerWork,
14956 _: &mut Window,
14957 cx: &mut Context<Workspace>,
14958 ) {
14959 let project = workspace.project();
14960 let buffers = workspace
14961 .active_item(cx)
14962 .and_then(|item| item.act_as::<Editor>(cx))
14963 .map_or(HashSet::default(), |editor| {
14964 editor.read(cx).buffer.read(cx).all_buffers()
14965 });
14966 project.update(cx, |project, cx| {
14967 project.cancel_language_server_work_for_buffers(buffers, cx);
14968 });
14969 }
14970
14971 fn show_character_palette(
14972 &mut self,
14973 _: &ShowCharacterPalette,
14974 window: &mut Window,
14975 _: &mut Context<Self>,
14976 ) {
14977 window.show_character_palette();
14978 }
14979
14980 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
14981 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
14982 let buffer = self.buffer.read(cx).snapshot(cx);
14983 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
14984 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
14985 let is_valid = buffer
14986 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
14987 .any(|entry| {
14988 entry.diagnostic.is_primary
14989 && !entry.range.is_empty()
14990 && entry.range.start == primary_range_start
14991 && entry.diagnostic.message == active_diagnostics.active_message
14992 });
14993
14994 if !is_valid {
14995 self.dismiss_diagnostics(cx);
14996 }
14997 }
14998 }
14999
15000 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15001 match &self.active_diagnostics {
15002 ActiveDiagnostic::Group(group) => Some(group),
15003 _ => None,
15004 }
15005 }
15006
15007 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15008 self.dismiss_diagnostics(cx);
15009 self.active_diagnostics = ActiveDiagnostic::All;
15010 }
15011
15012 fn activate_diagnostics(
15013 &mut self,
15014 buffer_id: BufferId,
15015 diagnostic: DiagnosticEntry<usize>,
15016 window: &mut Window,
15017 cx: &mut Context<Self>,
15018 ) {
15019 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15020 return;
15021 }
15022 self.dismiss_diagnostics(cx);
15023 let snapshot = self.snapshot(window, cx);
15024 let buffer = self.buffer.read(cx).snapshot(cx);
15025 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15026 return;
15027 };
15028
15029 let diagnostic_group = buffer
15030 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15031 .collect::<Vec<_>>();
15032
15033 let blocks =
15034 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15035
15036 let blocks = self.display_map.update(cx, |display_map, cx| {
15037 display_map.insert_blocks(blocks, cx).into_iter().collect()
15038 });
15039 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15040 active_range: buffer.anchor_before(diagnostic.range.start)
15041 ..buffer.anchor_after(diagnostic.range.end),
15042 active_message: diagnostic.diagnostic.message.clone(),
15043 group_id: diagnostic.diagnostic.group_id,
15044 blocks,
15045 });
15046 cx.notify();
15047 }
15048
15049 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15050 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15051 return;
15052 };
15053
15054 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15055 if let ActiveDiagnostic::Group(group) = prev {
15056 self.display_map.update(cx, |display_map, cx| {
15057 display_map.remove_blocks(group.blocks, cx);
15058 });
15059 cx.notify();
15060 }
15061 }
15062
15063 /// Disable inline diagnostics rendering for this editor.
15064 pub fn disable_inline_diagnostics(&mut self) {
15065 self.inline_diagnostics_enabled = false;
15066 self.inline_diagnostics_update = Task::ready(());
15067 self.inline_diagnostics.clear();
15068 }
15069
15070 pub fn inline_diagnostics_enabled(&self) -> bool {
15071 self.inline_diagnostics_enabled
15072 }
15073
15074 pub fn show_inline_diagnostics(&self) -> bool {
15075 self.show_inline_diagnostics
15076 }
15077
15078 pub fn toggle_inline_diagnostics(
15079 &mut self,
15080 _: &ToggleInlineDiagnostics,
15081 window: &mut Window,
15082 cx: &mut Context<Editor>,
15083 ) {
15084 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15085 self.refresh_inline_diagnostics(false, window, cx);
15086 }
15087
15088 fn refresh_inline_diagnostics(
15089 &mut self,
15090 debounce: bool,
15091 window: &mut Window,
15092 cx: &mut Context<Self>,
15093 ) {
15094 if !self.inline_diagnostics_enabled || !self.show_inline_diagnostics {
15095 self.inline_diagnostics_update = Task::ready(());
15096 self.inline_diagnostics.clear();
15097 return;
15098 }
15099
15100 let debounce_ms = ProjectSettings::get_global(cx)
15101 .diagnostics
15102 .inline
15103 .update_debounce_ms;
15104 let debounce = if debounce && debounce_ms > 0 {
15105 Some(Duration::from_millis(debounce_ms))
15106 } else {
15107 None
15108 };
15109 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15110 let editor = editor.upgrade().unwrap();
15111
15112 if let Some(debounce) = debounce {
15113 cx.background_executor().timer(debounce).await;
15114 }
15115 let Some(snapshot) = editor
15116 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15117 .ok()
15118 else {
15119 return;
15120 };
15121
15122 let new_inline_diagnostics = cx
15123 .background_spawn(async move {
15124 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15125 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15126 let message = diagnostic_entry
15127 .diagnostic
15128 .message
15129 .split_once('\n')
15130 .map(|(line, _)| line)
15131 .map(SharedString::new)
15132 .unwrap_or_else(|| {
15133 SharedString::from(diagnostic_entry.diagnostic.message)
15134 });
15135 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15136 let (Ok(i) | Err(i)) = inline_diagnostics
15137 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15138 inline_diagnostics.insert(
15139 i,
15140 (
15141 start_anchor,
15142 InlineDiagnostic {
15143 message,
15144 group_id: diagnostic_entry.diagnostic.group_id,
15145 start: diagnostic_entry.range.start.to_point(&snapshot),
15146 is_primary: diagnostic_entry.diagnostic.is_primary,
15147 severity: diagnostic_entry.diagnostic.severity,
15148 },
15149 ),
15150 );
15151 }
15152 inline_diagnostics
15153 })
15154 .await;
15155
15156 editor
15157 .update(cx, |editor, cx| {
15158 editor.inline_diagnostics = new_inline_diagnostics;
15159 cx.notify();
15160 })
15161 .ok();
15162 });
15163 }
15164
15165 pub fn set_selections_from_remote(
15166 &mut self,
15167 selections: Vec<Selection<Anchor>>,
15168 pending_selection: Option<Selection<Anchor>>,
15169 window: &mut Window,
15170 cx: &mut Context<Self>,
15171 ) {
15172 let old_cursor_position = self.selections.newest_anchor().head();
15173 self.selections.change_with(cx, |s| {
15174 s.select_anchors(selections);
15175 if let Some(pending_selection) = pending_selection {
15176 s.set_pending(pending_selection, SelectMode::Character);
15177 } else {
15178 s.clear_pending();
15179 }
15180 });
15181 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15182 }
15183
15184 fn push_to_selection_history(&mut self) {
15185 self.selection_history.push(SelectionHistoryEntry {
15186 selections: self.selections.disjoint_anchors(),
15187 select_next_state: self.select_next_state.clone(),
15188 select_prev_state: self.select_prev_state.clone(),
15189 add_selections_state: self.add_selections_state.clone(),
15190 });
15191 }
15192
15193 pub fn transact(
15194 &mut self,
15195 window: &mut Window,
15196 cx: &mut Context<Self>,
15197 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15198 ) -> Option<TransactionId> {
15199 self.start_transaction_at(Instant::now(), window, cx);
15200 update(self, window, cx);
15201 self.end_transaction_at(Instant::now(), cx)
15202 }
15203
15204 pub fn start_transaction_at(
15205 &mut self,
15206 now: Instant,
15207 window: &mut Window,
15208 cx: &mut Context<Self>,
15209 ) {
15210 self.end_selection(window, cx);
15211 if let Some(tx_id) = self
15212 .buffer
15213 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15214 {
15215 self.selection_history
15216 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15217 cx.emit(EditorEvent::TransactionBegun {
15218 transaction_id: tx_id,
15219 })
15220 }
15221 }
15222
15223 pub fn end_transaction_at(
15224 &mut self,
15225 now: Instant,
15226 cx: &mut Context<Self>,
15227 ) -> Option<TransactionId> {
15228 if let Some(transaction_id) = self
15229 .buffer
15230 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15231 {
15232 if let Some((_, end_selections)) =
15233 self.selection_history.transaction_mut(transaction_id)
15234 {
15235 *end_selections = Some(self.selections.disjoint_anchors());
15236 } else {
15237 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15238 }
15239
15240 cx.emit(EditorEvent::Edited { transaction_id });
15241 Some(transaction_id)
15242 } else {
15243 None
15244 }
15245 }
15246
15247 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15248 if self.selection_mark_mode {
15249 self.change_selections(None, window, cx, |s| {
15250 s.move_with(|_, sel| {
15251 sel.collapse_to(sel.head(), SelectionGoal::None);
15252 });
15253 })
15254 }
15255 self.selection_mark_mode = true;
15256 cx.notify();
15257 }
15258
15259 pub fn swap_selection_ends(
15260 &mut self,
15261 _: &actions::SwapSelectionEnds,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 self.change_selections(None, window, cx, |s| {
15266 s.move_with(|_, sel| {
15267 if sel.start != sel.end {
15268 sel.reversed = !sel.reversed
15269 }
15270 });
15271 });
15272 self.request_autoscroll(Autoscroll::newest(), cx);
15273 cx.notify();
15274 }
15275
15276 pub fn toggle_fold(
15277 &mut self,
15278 _: &actions::ToggleFold,
15279 window: &mut Window,
15280 cx: &mut Context<Self>,
15281 ) {
15282 if self.is_singleton(cx) {
15283 let selection = self.selections.newest::<Point>(cx);
15284
15285 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15286 let range = if selection.is_empty() {
15287 let point = selection.head().to_display_point(&display_map);
15288 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15289 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15290 .to_point(&display_map);
15291 start..end
15292 } else {
15293 selection.range()
15294 };
15295 if display_map.folds_in_range(range).next().is_some() {
15296 self.unfold_lines(&Default::default(), window, cx)
15297 } else {
15298 self.fold(&Default::default(), window, cx)
15299 }
15300 } else {
15301 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15302 let buffer_ids: HashSet<_> = self
15303 .selections
15304 .disjoint_anchor_ranges()
15305 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15306 .collect();
15307
15308 let should_unfold = buffer_ids
15309 .iter()
15310 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15311
15312 for buffer_id in buffer_ids {
15313 if should_unfold {
15314 self.unfold_buffer(buffer_id, cx);
15315 } else {
15316 self.fold_buffer(buffer_id, cx);
15317 }
15318 }
15319 }
15320 }
15321
15322 pub fn toggle_fold_recursive(
15323 &mut self,
15324 _: &actions::ToggleFoldRecursive,
15325 window: &mut Window,
15326 cx: &mut Context<Self>,
15327 ) {
15328 let selection = self.selections.newest::<Point>(cx);
15329
15330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15331 let range = if selection.is_empty() {
15332 let point = selection.head().to_display_point(&display_map);
15333 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15334 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15335 .to_point(&display_map);
15336 start..end
15337 } else {
15338 selection.range()
15339 };
15340 if display_map.folds_in_range(range).next().is_some() {
15341 self.unfold_recursive(&Default::default(), window, cx)
15342 } else {
15343 self.fold_recursive(&Default::default(), window, cx)
15344 }
15345 }
15346
15347 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15348 if self.is_singleton(cx) {
15349 let mut to_fold = Vec::new();
15350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15351 let selections = self.selections.all_adjusted(cx);
15352
15353 for selection in selections {
15354 let range = selection.range().sorted();
15355 let buffer_start_row = range.start.row;
15356
15357 if range.start.row != range.end.row {
15358 let mut found = false;
15359 let mut row = range.start.row;
15360 while row <= range.end.row {
15361 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15362 {
15363 found = true;
15364 row = crease.range().end.row + 1;
15365 to_fold.push(crease);
15366 } else {
15367 row += 1
15368 }
15369 }
15370 if found {
15371 continue;
15372 }
15373 }
15374
15375 for row in (0..=range.start.row).rev() {
15376 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15377 if crease.range().end.row >= buffer_start_row {
15378 to_fold.push(crease);
15379 if row <= range.start.row {
15380 break;
15381 }
15382 }
15383 }
15384 }
15385 }
15386
15387 self.fold_creases(to_fold, true, window, cx);
15388 } else {
15389 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15390 let buffer_ids = self
15391 .selections
15392 .disjoint_anchor_ranges()
15393 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15394 .collect::<HashSet<_>>();
15395 for buffer_id in buffer_ids {
15396 self.fold_buffer(buffer_id, cx);
15397 }
15398 }
15399 }
15400
15401 fn fold_at_level(
15402 &mut self,
15403 fold_at: &FoldAtLevel,
15404 window: &mut Window,
15405 cx: &mut Context<Self>,
15406 ) {
15407 if !self.buffer.read(cx).is_singleton() {
15408 return;
15409 }
15410
15411 let fold_at_level = fold_at.0;
15412 let snapshot = self.buffer.read(cx).snapshot(cx);
15413 let mut to_fold = Vec::new();
15414 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15415
15416 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15417 while start_row < end_row {
15418 match self
15419 .snapshot(window, cx)
15420 .crease_for_buffer_row(MultiBufferRow(start_row))
15421 {
15422 Some(crease) => {
15423 let nested_start_row = crease.range().start.row + 1;
15424 let nested_end_row = crease.range().end.row;
15425
15426 if current_level < fold_at_level {
15427 stack.push((nested_start_row, nested_end_row, current_level + 1));
15428 } else if current_level == fold_at_level {
15429 to_fold.push(crease);
15430 }
15431
15432 start_row = nested_end_row + 1;
15433 }
15434 None => start_row += 1,
15435 }
15436 }
15437 }
15438
15439 self.fold_creases(to_fold, true, window, cx);
15440 }
15441
15442 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15443 if self.buffer.read(cx).is_singleton() {
15444 let mut fold_ranges = Vec::new();
15445 let snapshot = self.buffer.read(cx).snapshot(cx);
15446
15447 for row in 0..snapshot.max_row().0 {
15448 if let Some(foldable_range) = self
15449 .snapshot(window, cx)
15450 .crease_for_buffer_row(MultiBufferRow(row))
15451 {
15452 fold_ranges.push(foldable_range);
15453 }
15454 }
15455
15456 self.fold_creases(fold_ranges, true, window, cx);
15457 } else {
15458 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15459 editor
15460 .update_in(cx, |editor, _, cx| {
15461 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15462 editor.fold_buffer(buffer_id, cx);
15463 }
15464 })
15465 .ok();
15466 });
15467 }
15468 }
15469
15470 pub fn fold_function_bodies(
15471 &mut self,
15472 _: &actions::FoldFunctionBodies,
15473 window: &mut Window,
15474 cx: &mut Context<Self>,
15475 ) {
15476 let snapshot = self.buffer.read(cx).snapshot(cx);
15477
15478 let ranges = snapshot
15479 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15480 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15481 .collect::<Vec<_>>();
15482
15483 let creases = ranges
15484 .into_iter()
15485 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15486 .collect();
15487
15488 self.fold_creases(creases, true, window, cx);
15489 }
15490
15491 pub fn fold_recursive(
15492 &mut self,
15493 _: &actions::FoldRecursive,
15494 window: &mut Window,
15495 cx: &mut Context<Self>,
15496 ) {
15497 let mut to_fold = Vec::new();
15498 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15499 let selections = self.selections.all_adjusted(cx);
15500
15501 for selection in selections {
15502 let range = selection.range().sorted();
15503 let buffer_start_row = range.start.row;
15504
15505 if range.start.row != range.end.row {
15506 let mut found = false;
15507 for row in range.start.row..=range.end.row {
15508 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15509 found = true;
15510 to_fold.push(crease);
15511 }
15512 }
15513 if found {
15514 continue;
15515 }
15516 }
15517
15518 for row in (0..=range.start.row).rev() {
15519 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15520 if crease.range().end.row >= buffer_start_row {
15521 to_fold.push(crease);
15522 } else {
15523 break;
15524 }
15525 }
15526 }
15527 }
15528
15529 self.fold_creases(to_fold, true, window, cx);
15530 }
15531
15532 pub fn fold_at(
15533 &mut self,
15534 buffer_row: MultiBufferRow,
15535 window: &mut Window,
15536 cx: &mut Context<Self>,
15537 ) {
15538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15539
15540 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15541 let autoscroll = self
15542 .selections
15543 .all::<Point>(cx)
15544 .iter()
15545 .any(|selection| crease.range().overlaps(&selection.range()));
15546
15547 self.fold_creases(vec![crease], autoscroll, window, cx);
15548 }
15549 }
15550
15551 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15552 if self.is_singleton(cx) {
15553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15554 let buffer = &display_map.buffer_snapshot;
15555 let selections = self.selections.all::<Point>(cx);
15556 let ranges = selections
15557 .iter()
15558 .map(|s| {
15559 let range = s.display_range(&display_map).sorted();
15560 let mut start = range.start.to_point(&display_map);
15561 let mut end = range.end.to_point(&display_map);
15562 start.column = 0;
15563 end.column = buffer.line_len(MultiBufferRow(end.row));
15564 start..end
15565 })
15566 .collect::<Vec<_>>();
15567
15568 self.unfold_ranges(&ranges, true, true, cx);
15569 } else {
15570 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15571 let buffer_ids = self
15572 .selections
15573 .disjoint_anchor_ranges()
15574 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15575 .collect::<HashSet<_>>();
15576 for buffer_id in buffer_ids {
15577 self.unfold_buffer(buffer_id, cx);
15578 }
15579 }
15580 }
15581
15582 pub fn unfold_recursive(
15583 &mut self,
15584 _: &UnfoldRecursive,
15585 _window: &mut Window,
15586 cx: &mut Context<Self>,
15587 ) {
15588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15589 let selections = self.selections.all::<Point>(cx);
15590 let ranges = selections
15591 .iter()
15592 .map(|s| {
15593 let mut range = s.display_range(&display_map).sorted();
15594 *range.start.column_mut() = 0;
15595 *range.end.column_mut() = display_map.line_len(range.end.row());
15596 let start = range.start.to_point(&display_map);
15597 let end = range.end.to_point(&display_map);
15598 start..end
15599 })
15600 .collect::<Vec<_>>();
15601
15602 self.unfold_ranges(&ranges, true, true, cx);
15603 }
15604
15605 pub fn unfold_at(
15606 &mut self,
15607 buffer_row: MultiBufferRow,
15608 _window: &mut Window,
15609 cx: &mut Context<Self>,
15610 ) {
15611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15612
15613 let intersection_range = Point::new(buffer_row.0, 0)
15614 ..Point::new(
15615 buffer_row.0,
15616 display_map.buffer_snapshot.line_len(buffer_row),
15617 );
15618
15619 let autoscroll = self
15620 .selections
15621 .all::<Point>(cx)
15622 .iter()
15623 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
15624
15625 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
15626 }
15627
15628 pub fn unfold_all(
15629 &mut self,
15630 _: &actions::UnfoldAll,
15631 _window: &mut Window,
15632 cx: &mut Context<Self>,
15633 ) {
15634 if self.buffer.read(cx).is_singleton() {
15635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15636 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
15637 } else {
15638 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
15639 editor
15640 .update(cx, |editor, cx| {
15641 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15642 editor.unfold_buffer(buffer_id, cx);
15643 }
15644 })
15645 .ok();
15646 });
15647 }
15648 }
15649
15650 pub fn fold_selected_ranges(
15651 &mut self,
15652 _: &FoldSelectedRanges,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 let selections = self.selections.all_adjusted(cx);
15657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15658 let ranges = selections
15659 .into_iter()
15660 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
15661 .collect::<Vec<_>>();
15662 self.fold_creases(ranges, true, window, cx);
15663 }
15664
15665 pub fn fold_ranges<T: ToOffset + Clone>(
15666 &mut self,
15667 ranges: Vec<Range<T>>,
15668 auto_scroll: bool,
15669 window: &mut Window,
15670 cx: &mut Context<Self>,
15671 ) {
15672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15673 let ranges = ranges
15674 .into_iter()
15675 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
15676 .collect::<Vec<_>>();
15677 self.fold_creases(ranges, auto_scroll, window, cx);
15678 }
15679
15680 pub fn fold_creases<T: ToOffset + Clone>(
15681 &mut self,
15682 creases: Vec<Crease<T>>,
15683 auto_scroll: bool,
15684 _window: &mut Window,
15685 cx: &mut Context<Self>,
15686 ) {
15687 if creases.is_empty() {
15688 return;
15689 }
15690
15691 let mut buffers_affected = HashSet::default();
15692 let multi_buffer = self.buffer().read(cx);
15693 for crease in &creases {
15694 if let Some((_, buffer, _)) =
15695 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
15696 {
15697 buffers_affected.insert(buffer.read(cx).remote_id());
15698 };
15699 }
15700
15701 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
15702
15703 if auto_scroll {
15704 self.request_autoscroll(Autoscroll::fit(), cx);
15705 }
15706
15707 cx.notify();
15708
15709 self.scrollbar_marker_state.dirty = true;
15710 self.folds_did_change(cx);
15711 }
15712
15713 /// Removes any folds whose ranges intersect any of the given ranges.
15714 pub fn unfold_ranges<T: ToOffset + Clone>(
15715 &mut self,
15716 ranges: &[Range<T>],
15717 inclusive: bool,
15718 auto_scroll: bool,
15719 cx: &mut Context<Self>,
15720 ) {
15721 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15722 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
15723 });
15724 self.folds_did_change(cx);
15725 }
15726
15727 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15728 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
15729 return;
15730 }
15731 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15732 self.display_map.update(cx, |display_map, cx| {
15733 display_map.fold_buffers([buffer_id], cx)
15734 });
15735 cx.emit(EditorEvent::BufferFoldToggled {
15736 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
15737 folded: true,
15738 });
15739 cx.notify();
15740 }
15741
15742 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15743 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
15744 return;
15745 }
15746 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
15747 self.display_map.update(cx, |display_map, cx| {
15748 display_map.unfold_buffers([buffer_id], cx);
15749 });
15750 cx.emit(EditorEvent::BufferFoldToggled {
15751 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
15752 folded: false,
15753 });
15754 cx.notify();
15755 }
15756
15757 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
15758 self.display_map.read(cx).is_buffer_folded(buffer)
15759 }
15760
15761 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
15762 self.display_map.read(cx).folded_buffers()
15763 }
15764
15765 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
15766 self.display_map.update(cx, |display_map, cx| {
15767 display_map.disable_header_for_buffer(buffer_id, cx);
15768 });
15769 cx.notify();
15770 }
15771
15772 /// Removes any folds with the given ranges.
15773 pub fn remove_folds_with_type<T: ToOffset + Clone>(
15774 &mut self,
15775 ranges: &[Range<T>],
15776 type_id: TypeId,
15777 auto_scroll: bool,
15778 cx: &mut Context<Self>,
15779 ) {
15780 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
15781 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
15782 });
15783 self.folds_did_change(cx);
15784 }
15785
15786 fn remove_folds_with<T: ToOffset + Clone>(
15787 &mut self,
15788 ranges: &[Range<T>],
15789 auto_scroll: bool,
15790 cx: &mut Context<Self>,
15791 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
15792 ) {
15793 if ranges.is_empty() {
15794 return;
15795 }
15796
15797 let mut buffers_affected = HashSet::default();
15798 let multi_buffer = self.buffer().read(cx);
15799 for range in ranges {
15800 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
15801 buffers_affected.insert(buffer.read(cx).remote_id());
15802 };
15803 }
15804
15805 self.display_map.update(cx, update);
15806
15807 if auto_scroll {
15808 self.request_autoscroll(Autoscroll::fit(), cx);
15809 }
15810
15811 cx.notify();
15812 self.scrollbar_marker_state.dirty = true;
15813 self.active_indent_guides_state.dirty = true;
15814 }
15815
15816 pub fn update_fold_widths(
15817 &mut self,
15818 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
15819 cx: &mut Context<Self>,
15820 ) -> bool {
15821 self.display_map
15822 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
15823 }
15824
15825 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
15826 self.display_map.read(cx).fold_placeholder.clone()
15827 }
15828
15829 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
15830 self.buffer.update(cx, |buffer, cx| {
15831 buffer.set_all_diff_hunks_expanded(cx);
15832 });
15833 }
15834
15835 pub fn expand_all_diff_hunks(
15836 &mut self,
15837 _: &ExpandAllDiffHunks,
15838 _window: &mut Window,
15839 cx: &mut Context<Self>,
15840 ) {
15841 self.buffer.update(cx, |buffer, cx| {
15842 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
15843 });
15844 }
15845
15846 pub fn toggle_selected_diff_hunks(
15847 &mut self,
15848 _: &ToggleSelectedDiffHunks,
15849 _window: &mut Window,
15850 cx: &mut Context<Self>,
15851 ) {
15852 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15853 self.toggle_diff_hunks_in_ranges(ranges, cx);
15854 }
15855
15856 pub fn diff_hunks_in_ranges<'a>(
15857 &'a self,
15858 ranges: &'a [Range<Anchor>],
15859 buffer: &'a MultiBufferSnapshot,
15860 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
15861 ranges.iter().flat_map(move |range| {
15862 let end_excerpt_id = range.end.excerpt_id;
15863 let range = range.to_point(buffer);
15864 let mut peek_end = range.end;
15865 if range.end.row < buffer.max_row().0 {
15866 peek_end = Point::new(range.end.row + 1, 0);
15867 }
15868 buffer
15869 .diff_hunks_in_range(range.start..peek_end)
15870 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
15871 })
15872 }
15873
15874 pub fn has_stageable_diff_hunks_in_ranges(
15875 &self,
15876 ranges: &[Range<Anchor>],
15877 snapshot: &MultiBufferSnapshot,
15878 ) -> bool {
15879 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
15880 hunks.any(|hunk| hunk.status().has_secondary_hunk())
15881 }
15882
15883 pub fn toggle_staged_selected_diff_hunks(
15884 &mut self,
15885 _: &::git::ToggleStaged,
15886 _: &mut Window,
15887 cx: &mut Context<Self>,
15888 ) {
15889 let snapshot = self.buffer.read(cx).snapshot(cx);
15890 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
15891 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
15892 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15893 }
15894
15895 pub fn set_render_diff_hunk_controls(
15896 &mut self,
15897 render_diff_hunk_controls: RenderDiffHunkControlsFn,
15898 cx: &mut Context<Self>,
15899 ) {
15900 self.render_diff_hunk_controls = render_diff_hunk_controls;
15901 cx.notify();
15902 }
15903
15904 pub fn stage_and_next(
15905 &mut self,
15906 _: &::git::StageAndNext,
15907 window: &mut Window,
15908 cx: &mut Context<Self>,
15909 ) {
15910 self.do_stage_or_unstage_and_next(true, window, cx);
15911 }
15912
15913 pub fn unstage_and_next(
15914 &mut self,
15915 _: &::git::UnstageAndNext,
15916 window: &mut Window,
15917 cx: &mut Context<Self>,
15918 ) {
15919 self.do_stage_or_unstage_and_next(false, window, cx);
15920 }
15921
15922 pub fn stage_or_unstage_diff_hunks(
15923 &mut self,
15924 stage: bool,
15925 ranges: Vec<Range<Anchor>>,
15926 cx: &mut Context<Self>,
15927 ) {
15928 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
15929 cx.spawn(async move |this, cx| {
15930 task.await?;
15931 this.update(cx, |this, cx| {
15932 let snapshot = this.buffer.read(cx).snapshot(cx);
15933 let chunk_by = this
15934 .diff_hunks_in_ranges(&ranges, &snapshot)
15935 .chunk_by(|hunk| hunk.buffer_id);
15936 for (buffer_id, hunks) in &chunk_by {
15937 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
15938 }
15939 })
15940 })
15941 .detach_and_log_err(cx);
15942 }
15943
15944 fn save_buffers_for_ranges_if_needed(
15945 &mut self,
15946 ranges: &[Range<Anchor>],
15947 cx: &mut Context<Editor>,
15948 ) -> Task<Result<()>> {
15949 let multibuffer = self.buffer.read(cx);
15950 let snapshot = multibuffer.read(cx);
15951 let buffer_ids: HashSet<_> = ranges
15952 .iter()
15953 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
15954 .collect();
15955 drop(snapshot);
15956
15957 let mut buffers = HashSet::default();
15958 for buffer_id in buffer_ids {
15959 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
15960 let buffer = buffer_entity.read(cx);
15961 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
15962 {
15963 buffers.insert(buffer_entity);
15964 }
15965 }
15966 }
15967
15968 if let Some(project) = &self.project {
15969 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
15970 } else {
15971 Task::ready(Ok(()))
15972 }
15973 }
15974
15975 fn do_stage_or_unstage_and_next(
15976 &mut self,
15977 stage: bool,
15978 window: &mut Window,
15979 cx: &mut Context<Self>,
15980 ) {
15981 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
15982
15983 if ranges.iter().any(|range| range.start != range.end) {
15984 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15985 return;
15986 }
15987
15988 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
15989 let snapshot = self.snapshot(window, cx);
15990 let position = self.selections.newest::<Point>(cx).head();
15991 let mut row = snapshot
15992 .buffer_snapshot
15993 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15994 .find(|hunk| hunk.row_range.start.0 > position.row)
15995 .map(|hunk| hunk.row_range.start);
15996
15997 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
15998 // Outside of the project diff editor, wrap around to the beginning.
15999 if !all_diff_hunks_expanded {
16000 row = row.or_else(|| {
16001 snapshot
16002 .buffer_snapshot
16003 .diff_hunks_in_range(Point::zero()..position)
16004 .find(|hunk| hunk.row_range.end.0 < position.row)
16005 .map(|hunk| hunk.row_range.start)
16006 });
16007 }
16008
16009 if let Some(row) = row {
16010 let destination = Point::new(row.0, 0);
16011 let autoscroll = Autoscroll::center();
16012
16013 self.unfold_ranges(&[destination..destination], false, false, cx);
16014 self.change_selections(Some(autoscroll), window, cx, |s| {
16015 s.select_ranges([destination..destination]);
16016 });
16017 }
16018 }
16019
16020 fn do_stage_or_unstage(
16021 &self,
16022 stage: bool,
16023 buffer_id: BufferId,
16024 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16025 cx: &mut App,
16026 ) -> Option<()> {
16027 let project = self.project.as_ref()?;
16028 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16029 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16030 let buffer_snapshot = buffer.read(cx).snapshot();
16031 let file_exists = buffer_snapshot
16032 .file()
16033 .is_some_and(|file| file.disk_state().exists());
16034 diff.update(cx, |diff, cx| {
16035 diff.stage_or_unstage_hunks(
16036 stage,
16037 &hunks
16038 .map(|hunk| buffer_diff::DiffHunk {
16039 buffer_range: hunk.buffer_range,
16040 diff_base_byte_range: hunk.diff_base_byte_range,
16041 secondary_status: hunk.secondary_status,
16042 range: Point::zero()..Point::zero(), // unused
16043 })
16044 .collect::<Vec<_>>(),
16045 &buffer_snapshot,
16046 file_exists,
16047 cx,
16048 )
16049 });
16050 None
16051 }
16052
16053 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16054 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16055 self.buffer
16056 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16057 }
16058
16059 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16060 self.buffer.update(cx, |buffer, cx| {
16061 let ranges = vec![Anchor::min()..Anchor::max()];
16062 if !buffer.all_diff_hunks_expanded()
16063 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16064 {
16065 buffer.collapse_diff_hunks(ranges, cx);
16066 true
16067 } else {
16068 false
16069 }
16070 })
16071 }
16072
16073 fn toggle_diff_hunks_in_ranges(
16074 &mut self,
16075 ranges: Vec<Range<Anchor>>,
16076 cx: &mut Context<Editor>,
16077 ) {
16078 self.buffer.update(cx, |buffer, cx| {
16079 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16080 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16081 })
16082 }
16083
16084 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16085 self.buffer.update(cx, |buffer, cx| {
16086 let snapshot = buffer.snapshot(cx);
16087 let excerpt_id = range.end.excerpt_id;
16088 let point_range = range.to_point(&snapshot);
16089 let expand = !buffer.single_hunk_is_expanded(range, cx);
16090 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16091 })
16092 }
16093
16094 pub(crate) fn apply_all_diff_hunks(
16095 &mut self,
16096 _: &ApplyAllDiffHunks,
16097 window: &mut Window,
16098 cx: &mut Context<Self>,
16099 ) {
16100 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16101
16102 let buffers = self.buffer.read(cx).all_buffers();
16103 for branch_buffer in buffers {
16104 branch_buffer.update(cx, |branch_buffer, cx| {
16105 branch_buffer.merge_into_base(Vec::new(), cx);
16106 });
16107 }
16108
16109 if let Some(project) = self.project.clone() {
16110 self.save(true, project, window, cx).detach_and_log_err(cx);
16111 }
16112 }
16113
16114 pub(crate) fn apply_selected_diff_hunks(
16115 &mut self,
16116 _: &ApplyDiffHunk,
16117 window: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) {
16120 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16121 let snapshot = self.snapshot(window, cx);
16122 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16123 let mut ranges_by_buffer = HashMap::default();
16124 self.transact(window, cx, |editor, _window, cx| {
16125 for hunk in hunks {
16126 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16127 ranges_by_buffer
16128 .entry(buffer.clone())
16129 .or_insert_with(Vec::new)
16130 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16131 }
16132 }
16133
16134 for (buffer, ranges) in ranges_by_buffer {
16135 buffer.update(cx, |buffer, cx| {
16136 buffer.merge_into_base(ranges, cx);
16137 });
16138 }
16139 });
16140
16141 if let Some(project) = self.project.clone() {
16142 self.save(true, project, window, cx).detach_and_log_err(cx);
16143 }
16144 }
16145
16146 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16147 if hovered != self.gutter_hovered {
16148 self.gutter_hovered = hovered;
16149 cx.notify();
16150 }
16151 }
16152
16153 pub fn insert_blocks(
16154 &mut self,
16155 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16156 autoscroll: Option<Autoscroll>,
16157 cx: &mut Context<Self>,
16158 ) -> Vec<CustomBlockId> {
16159 let blocks = self
16160 .display_map
16161 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16162 if let Some(autoscroll) = autoscroll {
16163 self.request_autoscroll(autoscroll, cx);
16164 }
16165 cx.notify();
16166 blocks
16167 }
16168
16169 pub fn resize_blocks(
16170 &mut self,
16171 heights: HashMap<CustomBlockId, u32>,
16172 autoscroll: Option<Autoscroll>,
16173 cx: &mut Context<Self>,
16174 ) {
16175 self.display_map
16176 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16177 if let Some(autoscroll) = autoscroll {
16178 self.request_autoscroll(autoscroll, cx);
16179 }
16180 cx.notify();
16181 }
16182
16183 pub fn replace_blocks(
16184 &mut self,
16185 renderers: HashMap<CustomBlockId, RenderBlock>,
16186 autoscroll: Option<Autoscroll>,
16187 cx: &mut Context<Self>,
16188 ) {
16189 self.display_map
16190 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16191 if let Some(autoscroll) = autoscroll {
16192 self.request_autoscroll(autoscroll, cx);
16193 }
16194 cx.notify();
16195 }
16196
16197 pub fn remove_blocks(
16198 &mut self,
16199 block_ids: HashSet<CustomBlockId>,
16200 autoscroll: Option<Autoscroll>,
16201 cx: &mut Context<Self>,
16202 ) {
16203 self.display_map.update(cx, |display_map, cx| {
16204 display_map.remove_blocks(block_ids, cx)
16205 });
16206 if let Some(autoscroll) = autoscroll {
16207 self.request_autoscroll(autoscroll, cx);
16208 }
16209 cx.notify();
16210 }
16211
16212 pub fn row_for_block(
16213 &self,
16214 block_id: CustomBlockId,
16215 cx: &mut Context<Self>,
16216 ) -> Option<DisplayRow> {
16217 self.display_map
16218 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16219 }
16220
16221 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16222 self.focused_block = Some(focused_block);
16223 }
16224
16225 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16226 self.focused_block.take()
16227 }
16228
16229 pub fn insert_creases(
16230 &mut self,
16231 creases: impl IntoIterator<Item = Crease<Anchor>>,
16232 cx: &mut Context<Self>,
16233 ) -> Vec<CreaseId> {
16234 self.display_map
16235 .update(cx, |map, cx| map.insert_creases(creases, cx))
16236 }
16237
16238 pub fn remove_creases(
16239 &mut self,
16240 ids: impl IntoIterator<Item = CreaseId>,
16241 cx: &mut Context<Self>,
16242 ) {
16243 self.display_map
16244 .update(cx, |map, cx| map.remove_creases(ids, cx));
16245 }
16246
16247 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16248 self.display_map
16249 .update(cx, |map, cx| map.snapshot(cx))
16250 .longest_row()
16251 }
16252
16253 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16254 self.display_map
16255 .update(cx, |map, cx| map.snapshot(cx))
16256 .max_point()
16257 }
16258
16259 pub fn text(&self, cx: &App) -> String {
16260 self.buffer.read(cx).read(cx).text()
16261 }
16262
16263 pub fn is_empty(&self, cx: &App) -> bool {
16264 self.buffer.read(cx).read(cx).is_empty()
16265 }
16266
16267 pub fn text_option(&self, cx: &App) -> Option<String> {
16268 let text = self.text(cx);
16269 let text = text.trim();
16270
16271 if text.is_empty() {
16272 return None;
16273 }
16274
16275 Some(text.to_string())
16276 }
16277
16278 pub fn set_text(
16279 &mut self,
16280 text: impl Into<Arc<str>>,
16281 window: &mut Window,
16282 cx: &mut Context<Self>,
16283 ) {
16284 self.transact(window, cx, |this, _, cx| {
16285 this.buffer
16286 .read(cx)
16287 .as_singleton()
16288 .expect("you can only call set_text on editors for singleton buffers")
16289 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16290 });
16291 }
16292
16293 pub fn display_text(&self, cx: &mut App) -> String {
16294 self.display_map
16295 .update(cx, |map, cx| map.snapshot(cx))
16296 .text()
16297 }
16298
16299 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16300 let mut wrap_guides = smallvec::smallvec![];
16301
16302 if self.show_wrap_guides == Some(false) {
16303 return wrap_guides;
16304 }
16305
16306 let settings = self.buffer.read(cx).language_settings(cx);
16307 if settings.show_wrap_guides {
16308 match self.soft_wrap_mode(cx) {
16309 SoftWrap::Column(soft_wrap) => {
16310 wrap_guides.push((soft_wrap as usize, true));
16311 }
16312 SoftWrap::Bounded(soft_wrap) => {
16313 wrap_guides.push((soft_wrap as usize, true));
16314 }
16315 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16316 }
16317 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16318 }
16319
16320 wrap_guides
16321 }
16322
16323 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16324 let settings = self.buffer.read(cx).language_settings(cx);
16325 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16326 match mode {
16327 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16328 SoftWrap::None
16329 }
16330 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16331 language_settings::SoftWrap::PreferredLineLength => {
16332 SoftWrap::Column(settings.preferred_line_length)
16333 }
16334 language_settings::SoftWrap::Bounded => {
16335 SoftWrap::Bounded(settings.preferred_line_length)
16336 }
16337 }
16338 }
16339
16340 pub fn set_soft_wrap_mode(
16341 &mut self,
16342 mode: language_settings::SoftWrap,
16343
16344 cx: &mut Context<Self>,
16345 ) {
16346 self.soft_wrap_mode_override = Some(mode);
16347 cx.notify();
16348 }
16349
16350 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16351 self.hard_wrap = hard_wrap;
16352 cx.notify();
16353 }
16354
16355 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16356 self.text_style_refinement = Some(style);
16357 }
16358
16359 /// called by the Element so we know what style we were most recently rendered with.
16360 pub(crate) fn set_style(
16361 &mut self,
16362 style: EditorStyle,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) {
16366 let rem_size = window.rem_size();
16367 self.display_map.update(cx, |map, cx| {
16368 map.set_font(
16369 style.text.font(),
16370 style.text.font_size.to_pixels(rem_size),
16371 cx,
16372 )
16373 });
16374 self.style = Some(style);
16375 }
16376
16377 pub fn style(&self) -> Option<&EditorStyle> {
16378 self.style.as_ref()
16379 }
16380
16381 // Called by the element. This method is not designed to be called outside of the editor
16382 // element's layout code because it does not notify when rewrapping is computed synchronously.
16383 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16384 self.display_map
16385 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16386 }
16387
16388 pub fn set_soft_wrap(&mut self) {
16389 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16390 }
16391
16392 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16393 if self.soft_wrap_mode_override.is_some() {
16394 self.soft_wrap_mode_override.take();
16395 } else {
16396 let soft_wrap = match self.soft_wrap_mode(cx) {
16397 SoftWrap::GitDiff => return,
16398 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16399 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16400 language_settings::SoftWrap::None
16401 }
16402 };
16403 self.soft_wrap_mode_override = Some(soft_wrap);
16404 }
16405 cx.notify();
16406 }
16407
16408 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16409 let Some(workspace) = self.workspace() else {
16410 return;
16411 };
16412 let fs = workspace.read(cx).app_state().fs.clone();
16413 let current_show = TabBarSettings::get_global(cx).show;
16414 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16415 setting.show = Some(!current_show);
16416 });
16417 }
16418
16419 pub fn toggle_indent_guides(
16420 &mut self,
16421 _: &ToggleIndentGuides,
16422 _: &mut Window,
16423 cx: &mut Context<Self>,
16424 ) {
16425 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16426 self.buffer
16427 .read(cx)
16428 .language_settings(cx)
16429 .indent_guides
16430 .enabled
16431 });
16432 self.show_indent_guides = Some(!currently_enabled);
16433 cx.notify();
16434 }
16435
16436 fn should_show_indent_guides(&self) -> Option<bool> {
16437 self.show_indent_guides
16438 }
16439
16440 pub fn toggle_line_numbers(
16441 &mut self,
16442 _: &ToggleLineNumbers,
16443 _: &mut Window,
16444 cx: &mut Context<Self>,
16445 ) {
16446 let mut editor_settings = EditorSettings::get_global(cx).clone();
16447 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16448 EditorSettings::override_global(editor_settings, cx);
16449 }
16450
16451 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16452 if let Some(show_line_numbers) = self.show_line_numbers {
16453 return show_line_numbers;
16454 }
16455 EditorSettings::get_global(cx).gutter.line_numbers
16456 }
16457
16458 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16459 self.use_relative_line_numbers
16460 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16461 }
16462
16463 pub fn toggle_relative_line_numbers(
16464 &mut self,
16465 _: &ToggleRelativeLineNumbers,
16466 _: &mut Window,
16467 cx: &mut Context<Self>,
16468 ) {
16469 let is_relative = self.should_use_relative_line_numbers(cx);
16470 self.set_relative_line_number(Some(!is_relative), cx)
16471 }
16472
16473 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16474 self.use_relative_line_numbers = is_relative;
16475 cx.notify();
16476 }
16477
16478 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16479 self.show_gutter = show_gutter;
16480 cx.notify();
16481 }
16482
16483 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16484 self.show_scrollbars = show_scrollbars;
16485 cx.notify();
16486 }
16487
16488 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16489 self.show_line_numbers = Some(show_line_numbers);
16490 cx.notify();
16491 }
16492
16493 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
16494 self.disable_expand_excerpt_buttons = true;
16495 cx.notify();
16496 }
16497
16498 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
16499 self.show_git_diff_gutter = Some(show_git_diff_gutter);
16500 cx.notify();
16501 }
16502
16503 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
16504 self.show_code_actions = Some(show_code_actions);
16505 cx.notify();
16506 }
16507
16508 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
16509 self.show_runnables = Some(show_runnables);
16510 cx.notify();
16511 }
16512
16513 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
16514 self.show_breakpoints = Some(show_breakpoints);
16515 cx.notify();
16516 }
16517
16518 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
16519 if self.display_map.read(cx).masked != masked {
16520 self.display_map.update(cx, |map, _| map.masked = masked);
16521 }
16522 cx.notify()
16523 }
16524
16525 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
16526 self.show_wrap_guides = Some(show_wrap_guides);
16527 cx.notify();
16528 }
16529
16530 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
16531 self.show_indent_guides = Some(show_indent_guides);
16532 cx.notify();
16533 }
16534
16535 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
16536 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
16537 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
16538 if let Some(dir) = file.abs_path(cx).parent() {
16539 return Some(dir.to_owned());
16540 }
16541 }
16542
16543 if let Some(project_path) = buffer.read(cx).project_path(cx) {
16544 return Some(project_path.path.to_path_buf());
16545 }
16546 }
16547
16548 None
16549 }
16550
16551 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
16552 self.active_excerpt(cx)?
16553 .1
16554 .read(cx)
16555 .file()
16556 .and_then(|f| f.as_local())
16557 }
16558
16559 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16560 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16561 let buffer = buffer.read(cx);
16562 if let Some(project_path) = buffer.project_path(cx) {
16563 let project = self.project.as_ref()?.read(cx);
16564 project.absolute_path(&project_path, cx)
16565 } else {
16566 buffer
16567 .file()
16568 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
16569 }
16570 })
16571 }
16572
16573 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
16574 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
16575 let project_path = buffer.read(cx).project_path(cx)?;
16576 let project = self.project.as_ref()?.read(cx);
16577 let entry = project.entry_for_path(&project_path, cx)?;
16578 let path = entry.path.to_path_buf();
16579 Some(path)
16580 })
16581 }
16582
16583 pub fn reveal_in_finder(
16584 &mut self,
16585 _: &RevealInFileManager,
16586 _window: &mut Window,
16587 cx: &mut Context<Self>,
16588 ) {
16589 if let Some(target) = self.target_file(cx) {
16590 cx.reveal_path(&target.abs_path(cx));
16591 }
16592 }
16593
16594 pub fn copy_path(
16595 &mut self,
16596 _: &zed_actions::workspace::CopyPath,
16597 _window: &mut Window,
16598 cx: &mut Context<Self>,
16599 ) {
16600 if let Some(path) = self.target_file_abs_path(cx) {
16601 if let Some(path) = path.to_str() {
16602 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16603 }
16604 }
16605 }
16606
16607 pub fn copy_relative_path(
16608 &mut self,
16609 _: &zed_actions::workspace::CopyRelativePath,
16610 _window: &mut Window,
16611 cx: &mut Context<Self>,
16612 ) {
16613 if let Some(path) = self.target_file_path(cx) {
16614 if let Some(path) = path.to_str() {
16615 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
16616 }
16617 }
16618 }
16619
16620 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
16621 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
16622 buffer.read(cx).project_path(cx)
16623 } else {
16624 None
16625 }
16626 }
16627
16628 // Returns true if the editor handled a go-to-line request
16629 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
16630 maybe!({
16631 let breakpoint_store = self.breakpoint_store.as_ref()?;
16632
16633 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
16634 else {
16635 self.clear_row_highlights::<ActiveDebugLine>();
16636 return None;
16637 };
16638
16639 let position = active_stack_frame.position;
16640 let buffer_id = position.buffer_id?;
16641 let snapshot = self
16642 .project
16643 .as_ref()?
16644 .read(cx)
16645 .buffer_for_id(buffer_id, cx)?
16646 .read(cx)
16647 .snapshot();
16648
16649 let mut handled = false;
16650 for (id, ExcerptRange { context, .. }) in
16651 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
16652 {
16653 if context.start.cmp(&position, &snapshot).is_ge()
16654 || context.end.cmp(&position, &snapshot).is_lt()
16655 {
16656 continue;
16657 }
16658 let snapshot = self.buffer.read(cx).snapshot(cx);
16659 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
16660
16661 handled = true;
16662 self.clear_row_highlights::<ActiveDebugLine>();
16663 self.go_to_line::<ActiveDebugLine>(
16664 multibuffer_anchor,
16665 Some(cx.theme().colors().editor_debugger_active_line_background),
16666 window,
16667 cx,
16668 );
16669
16670 cx.notify();
16671 }
16672
16673 handled.then_some(())
16674 })
16675 .is_some()
16676 }
16677
16678 pub fn copy_file_name_without_extension(
16679 &mut self,
16680 _: &CopyFileNameWithoutExtension,
16681 _: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 if let Some(file) = self.target_file(cx) {
16685 if let Some(file_stem) = file.path().file_stem() {
16686 if let Some(name) = file_stem.to_str() {
16687 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16688 }
16689 }
16690 }
16691 }
16692
16693 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
16694 if let Some(file) = self.target_file(cx) {
16695 if let Some(file_name) = file.path().file_name() {
16696 if let Some(name) = file_name.to_str() {
16697 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
16698 }
16699 }
16700 }
16701 }
16702
16703 pub fn toggle_git_blame(
16704 &mut self,
16705 _: &::git::Blame,
16706 window: &mut Window,
16707 cx: &mut Context<Self>,
16708 ) {
16709 self.show_git_blame_gutter = !self.show_git_blame_gutter;
16710
16711 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
16712 self.start_git_blame(true, window, cx);
16713 }
16714
16715 cx.notify();
16716 }
16717
16718 pub fn toggle_git_blame_inline(
16719 &mut self,
16720 _: &ToggleGitBlameInline,
16721 window: &mut Window,
16722 cx: &mut Context<Self>,
16723 ) {
16724 self.toggle_git_blame_inline_internal(true, window, cx);
16725 cx.notify();
16726 }
16727
16728 pub fn open_git_blame_commit(
16729 &mut self,
16730 _: &OpenGitBlameCommit,
16731 window: &mut Window,
16732 cx: &mut Context<Self>,
16733 ) {
16734 self.open_git_blame_commit_internal(window, cx);
16735 }
16736
16737 fn open_git_blame_commit_internal(
16738 &mut self,
16739 window: &mut Window,
16740 cx: &mut Context<Self>,
16741 ) -> Option<()> {
16742 let blame = self.blame.as_ref()?;
16743 let snapshot = self.snapshot(window, cx);
16744 let cursor = self.selections.newest::<Point>(cx).head();
16745 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
16746 let blame_entry = blame
16747 .update(cx, |blame, cx| {
16748 blame
16749 .blame_for_rows(
16750 &[RowInfo {
16751 buffer_id: Some(buffer.remote_id()),
16752 buffer_row: Some(point.row),
16753 ..Default::default()
16754 }],
16755 cx,
16756 )
16757 .next()
16758 })
16759 .flatten()?;
16760 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
16761 let repo = blame.read(cx).repository(cx)?;
16762 let workspace = self.workspace()?.downgrade();
16763 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
16764 None
16765 }
16766
16767 pub fn git_blame_inline_enabled(&self) -> bool {
16768 self.git_blame_inline_enabled
16769 }
16770
16771 pub fn toggle_selection_menu(
16772 &mut self,
16773 _: &ToggleSelectionMenu,
16774 _: &mut Window,
16775 cx: &mut Context<Self>,
16776 ) {
16777 self.show_selection_menu = self
16778 .show_selection_menu
16779 .map(|show_selections_menu| !show_selections_menu)
16780 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
16781
16782 cx.notify();
16783 }
16784
16785 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
16786 self.show_selection_menu
16787 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
16788 }
16789
16790 fn start_git_blame(
16791 &mut self,
16792 user_triggered: bool,
16793 window: &mut Window,
16794 cx: &mut Context<Self>,
16795 ) {
16796 if let Some(project) = self.project.as_ref() {
16797 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
16798 return;
16799 };
16800
16801 if buffer.read(cx).file().is_none() {
16802 return;
16803 }
16804
16805 let focused = self.focus_handle(cx).contains_focused(window, cx);
16806
16807 let project = project.clone();
16808 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
16809 self.blame_subscription =
16810 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
16811 self.blame = Some(blame);
16812 }
16813 }
16814
16815 fn toggle_git_blame_inline_internal(
16816 &mut self,
16817 user_triggered: bool,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) {
16821 if self.git_blame_inline_enabled {
16822 self.git_blame_inline_enabled = false;
16823 self.show_git_blame_inline = false;
16824 self.show_git_blame_inline_delay_task.take();
16825 } else {
16826 self.git_blame_inline_enabled = true;
16827 self.start_git_blame_inline(user_triggered, window, cx);
16828 }
16829
16830 cx.notify();
16831 }
16832
16833 fn start_git_blame_inline(
16834 &mut self,
16835 user_triggered: bool,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) {
16839 self.start_git_blame(user_triggered, window, cx);
16840
16841 if ProjectSettings::get_global(cx)
16842 .git
16843 .inline_blame_delay()
16844 .is_some()
16845 {
16846 self.start_inline_blame_timer(window, cx);
16847 } else {
16848 self.show_git_blame_inline = true
16849 }
16850 }
16851
16852 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
16853 self.blame.as_ref()
16854 }
16855
16856 pub fn show_git_blame_gutter(&self) -> bool {
16857 self.show_git_blame_gutter
16858 }
16859
16860 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
16861 self.show_git_blame_gutter && self.has_blame_entries(cx)
16862 }
16863
16864 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
16865 self.show_git_blame_inline
16866 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
16867 && !self.newest_selection_head_on_empty_line(cx)
16868 && self.has_blame_entries(cx)
16869 }
16870
16871 fn has_blame_entries(&self, cx: &App) -> bool {
16872 self.blame()
16873 .map_or(false, |blame| blame.read(cx).has_generated_entries())
16874 }
16875
16876 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
16877 let cursor_anchor = self.selections.newest_anchor().head();
16878
16879 let snapshot = self.buffer.read(cx).snapshot(cx);
16880 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
16881
16882 snapshot.line_len(buffer_row) == 0
16883 }
16884
16885 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
16886 let buffer_and_selection = maybe!({
16887 let selection = self.selections.newest::<Point>(cx);
16888 let selection_range = selection.range();
16889
16890 let multi_buffer = self.buffer().read(cx);
16891 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16892 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
16893
16894 let (buffer, range, _) = if selection.reversed {
16895 buffer_ranges.first()
16896 } else {
16897 buffer_ranges.last()
16898 }?;
16899
16900 let selection = text::ToPoint::to_point(&range.start, &buffer).row
16901 ..text::ToPoint::to_point(&range.end, &buffer).row;
16902 Some((
16903 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
16904 selection,
16905 ))
16906 });
16907
16908 let Some((buffer, selection)) = buffer_and_selection else {
16909 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
16910 };
16911
16912 let Some(project) = self.project.as_ref() else {
16913 return Task::ready(Err(anyhow!("editor does not have project")));
16914 };
16915
16916 project.update(cx, |project, cx| {
16917 project.get_permalink_to_line(&buffer, selection, cx)
16918 })
16919 }
16920
16921 pub fn copy_permalink_to_line(
16922 &mut self,
16923 _: &CopyPermalinkToLine,
16924 window: &mut Window,
16925 cx: &mut Context<Self>,
16926 ) {
16927 let permalink_task = self.get_permalink_to_line(cx);
16928 let workspace = self.workspace();
16929
16930 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16931 Ok(permalink) => {
16932 cx.update(|_, cx| {
16933 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
16934 })
16935 .ok();
16936 }
16937 Err(err) => {
16938 let message = format!("Failed to copy permalink: {err}");
16939
16940 Err::<(), anyhow::Error>(err).log_err();
16941
16942 if let Some(workspace) = workspace {
16943 workspace
16944 .update_in(cx, |workspace, _, cx| {
16945 struct CopyPermalinkToLine;
16946
16947 workspace.show_toast(
16948 Toast::new(
16949 NotificationId::unique::<CopyPermalinkToLine>(),
16950 message,
16951 ),
16952 cx,
16953 )
16954 })
16955 .ok();
16956 }
16957 }
16958 })
16959 .detach();
16960 }
16961
16962 pub fn copy_file_location(
16963 &mut self,
16964 _: &CopyFileLocation,
16965 _: &mut Window,
16966 cx: &mut Context<Self>,
16967 ) {
16968 let selection = self.selections.newest::<Point>(cx).start.row + 1;
16969 if let Some(file) = self.target_file(cx) {
16970 if let Some(path) = file.path().to_str() {
16971 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
16972 }
16973 }
16974 }
16975
16976 pub fn open_permalink_to_line(
16977 &mut self,
16978 _: &OpenPermalinkToLine,
16979 window: &mut Window,
16980 cx: &mut Context<Self>,
16981 ) {
16982 let permalink_task = self.get_permalink_to_line(cx);
16983 let workspace = self.workspace();
16984
16985 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
16986 Ok(permalink) => {
16987 cx.update(|_, cx| {
16988 cx.open_url(permalink.as_ref());
16989 })
16990 .ok();
16991 }
16992 Err(err) => {
16993 let message = format!("Failed to open permalink: {err}");
16994
16995 Err::<(), anyhow::Error>(err).log_err();
16996
16997 if let Some(workspace) = workspace {
16998 workspace
16999 .update(cx, |workspace, cx| {
17000 struct OpenPermalinkToLine;
17001
17002 workspace.show_toast(
17003 Toast::new(
17004 NotificationId::unique::<OpenPermalinkToLine>(),
17005 message,
17006 ),
17007 cx,
17008 )
17009 })
17010 .ok();
17011 }
17012 }
17013 })
17014 .detach();
17015 }
17016
17017 pub fn insert_uuid_v4(
17018 &mut self,
17019 _: &InsertUuidV4,
17020 window: &mut Window,
17021 cx: &mut Context<Self>,
17022 ) {
17023 self.insert_uuid(UuidVersion::V4, window, cx);
17024 }
17025
17026 pub fn insert_uuid_v7(
17027 &mut self,
17028 _: &InsertUuidV7,
17029 window: &mut Window,
17030 cx: &mut Context<Self>,
17031 ) {
17032 self.insert_uuid(UuidVersion::V7, window, cx);
17033 }
17034
17035 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17036 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17037 self.transact(window, cx, |this, window, cx| {
17038 let edits = this
17039 .selections
17040 .all::<Point>(cx)
17041 .into_iter()
17042 .map(|selection| {
17043 let uuid = match version {
17044 UuidVersion::V4 => uuid::Uuid::new_v4(),
17045 UuidVersion::V7 => uuid::Uuid::now_v7(),
17046 };
17047
17048 (selection.range(), uuid.to_string())
17049 });
17050 this.edit(edits, cx);
17051 this.refresh_inline_completion(true, false, window, cx);
17052 });
17053 }
17054
17055 pub fn open_selections_in_multibuffer(
17056 &mut self,
17057 _: &OpenSelectionsInMultibuffer,
17058 window: &mut Window,
17059 cx: &mut Context<Self>,
17060 ) {
17061 let multibuffer = self.buffer.read(cx);
17062
17063 let Some(buffer) = multibuffer.as_singleton() else {
17064 return;
17065 };
17066
17067 let Some(workspace) = self.workspace() else {
17068 return;
17069 };
17070
17071 let locations = self
17072 .selections
17073 .disjoint_anchors()
17074 .iter()
17075 .map(|range| Location {
17076 buffer: buffer.clone(),
17077 range: range.start.text_anchor..range.end.text_anchor,
17078 })
17079 .collect::<Vec<_>>();
17080
17081 let title = multibuffer.title(cx).to_string();
17082
17083 cx.spawn_in(window, async move |_, cx| {
17084 workspace.update_in(cx, |workspace, window, cx| {
17085 Self::open_locations_in_multibuffer(
17086 workspace,
17087 locations,
17088 format!("Selections for '{title}'"),
17089 false,
17090 MultibufferSelectionMode::All,
17091 window,
17092 cx,
17093 );
17094 })
17095 })
17096 .detach();
17097 }
17098
17099 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17100 /// last highlight added will be used.
17101 ///
17102 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17103 pub fn highlight_rows<T: 'static>(
17104 &mut self,
17105 range: Range<Anchor>,
17106 color: Hsla,
17107 options: RowHighlightOptions,
17108 cx: &mut Context<Self>,
17109 ) {
17110 let snapshot = self.buffer().read(cx).snapshot(cx);
17111 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17112 let ix = row_highlights.binary_search_by(|highlight| {
17113 Ordering::Equal
17114 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17115 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17116 });
17117
17118 if let Err(mut ix) = ix {
17119 let index = post_inc(&mut self.highlight_order);
17120
17121 // If this range intersects with the preceding highlight, then merge it with
17122 // the preceding highlight. Otherwise insert a new highlight.
17123 let mut merged = false;
17124 if ix > 0 {
17125 let prev_highlight = &mut row_highlights[ix - 1];
17126 if prev_highlight
17127 .range
17128 .end
17129 .cmp(&range.start, &snapshot)
17130 .is_ge()
17131 {
17132 ix -= 1;
17133 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17134 prev_highlight.range.end = range.end;
17135 }
17136 merged = true;
17137 prev_highlight.index = index;
17138 prev_highlight.color = color;
17139 prev_highlight.options = options;
17140 }
17141 }
17142
17143 if !merged {
17144 row_highlights.insert(
17145 ix,
17146 RowHighlight {
17147 range: range.clone(),
17148 index,
17149 color,
17150 options,
17151 type_id: TypeId::of::<T>(),
17152 },
17153 );
17154 }
17155
17156 // If any of the following highlights intersect with this one, merge them.
17157 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17158 let highlight = &row_highlights[ix];
17159 if next_highlight
17160 .range
17161 .start
17162 .cmp(&highlight.range.end, &snapshot)
17163 .is_le()
17164 {
17165 if next_highlight
17166 .range
17167 .end
17168 .cmp(&highlight.range.end, &snapshot)
17169 .is_gt()
17170 {
17171 row_highlights[ix].range.end = next_highlight.range.end;
17172 }
17173 row_highlights.remove(ix + 1);
17174 } else {
17175 break;
17176 }
17177 }
17178 }
17179 }
17180
17181 /// Remove any highlighted row ranges of the given type that intersect the
17182 /// given ranges.
17183 pub fn remove_highlighted_rows<T: 'static>(
17184 &mut self,
17185 ranges_to_remove: Vec<Range<Anchor>>,
17186 cx: &mut Context<Self>,
17187 ) {
17188 let snapshot = self.buffer().read(cx).snapshot(cx);
17189 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17190 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17191 row_highlights.retain(|highlight| {
17192 while let Some(range_to_remove) = ranges_to_remove.peek() {
17193 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17194 Ordering::Less | Ordering::Equal => {
17195 ranges_to_remove.next();
17196 }
17197 Ordering::Greater => {
17198 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17199 Ordering::Less | Ordering::Equal => {
17200 return false;
17201 }
17202 Ordering::Greater => break,
17203 }
17204 }
17205 }
17206 }
17207
17208 true
17209 })
17210 }
17211
17212 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17213 pub fn clear_row_highlights<T: 'static>(&mut self) {
17214 self.highlighted_rows.remove(&TypeId::of::<T>());
17215 }
17216
17217 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17218 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17219 self.highlighted_rows
17220 .get(&TypeId::of::<T>())
17221 .map_or(&[] as &[_], |vec| vec.as_slice())
17222 .iter()
17223 .map(|highlight| (highlight.range.clone(), highlight.color))
17224 }
17225
17226 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17227 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17228 /// Allows to ignore certain kinds of highlights.
17229 pub fn highlighted_display_rows(
17230 &self,
17231 window: &mut Window,
17232 cx: &mut App,
17233 ) -> BTreeMap<DisplayRow, LineHighlight> {
17234 let snapshot = self.snapshot(window, cx);
17235 let mut used_highlight_orders = HashMap::default();
17236 self.highlighted_rows
17237 .iter()
17238 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17239 .fold(
17240 BTreeMap::<DisplayRow, LineHighlight>::new(),
17241 |mut unique_rows, highlight| {
17242 let start = highlight.range.start.to_display_point(&snapshot);
17243 let end = highlight.range.end.to_display_point(&snapshot);
17244 let start_row = start.row().0;
17245 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17246 && end.column() == 0
17247 {
17248 end.row().0.saturating_sub(1)
17249 } else {
17250 end.row().0
17251 };
17252 for row in start_row..=end_row {
17253 let used_index =
17254 used_highlight_orders.entry(row).or_insert(highlight.index);
17255 if highlight.index >= *used_index {
17256 *used_index = highlight.index;
17257 unique_rows.insert(
17258 DisplayRow(row),
17259 LineHighlight {
17260 include_gutter: highlight.options.include_gutter,
17261 border: None,
17262 background: highlight.color.into(),
17263 type_id: Some(highlight.type_id),
17264 },
17265 );
17266 }
17267 }
17268 unique_rows
17269 },
17270 )
17271 }
17272
17273 pub fn highlighted_display_row_for_autoscroll(
17274 &self,
17275 snapshot: &DisplaySnapshot,
17276 ) -> Option<DisplayRow> {
17277 self.highlighted_rows
17278 .values()
17279 .flat_map(|highlighted_rows| highlighted_rows.iter())
17280 .filter_map(|highlight| {
17281 if highlight.options.autoscroll {
17282 Some(highlight.range.start.to_display_point(snapshot).row())
17283 } else {
17284 None
17285 }
17286 })
17287 .min()
17288 }
17289
17290 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17291 self.highlight_background::<SearchWithinRange>(
17292 ranges,
17293 |colors| colors.editor_document_highlight_read_background,
17294 cx,
17295 )
17296 }
17297
17298 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17299 self.breadcrumb_header = Some(new_header);
17300 }
17301
17302 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17303 self.clear_background_highlights::<SearchWithinRange>(cx);
17304 }
17305
17306 pub fn highlight_background<T: 'static>(
17307 &mut self,
17308 ranges: &[Range<Anchor>],
17309 color_fetcher: fn(&ThemeColors) -> Hsla,
17310 cx: &mut Context<Self>,
17311 ) {
17312 self.background_highlights
17313 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17314 self.scrollbar_marker_state.dirty = true;
17315 cx.notify();
17316 }
17317
17318 pub fn clear_background_highlights<T: 'static>(
17319 &mut self,
17320 cx: &mut Context<Self>,
17321 ) -> Option<BackgroundHighlight> {
17322 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17323 if !text_highlights.1.is_empty() {
17324 self.scrollbar_marker_state.dirty = true;
17325 cx.notify();
17326 }
17327 Some(text_highlights)
17328 }
17329
17330 pub fn highlight_gutter<T: 'static>(
17331 &mut self,
17332 ranges: &[Range<Anchor>],
17333 color_fetcher: fn(&App) -> Hsla,
17334 cx: &mut Context<Self>,
17335 ) {
17336 self.gutter_highlights
17337 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17338 cx.notify();
17339 }
17340
17341 pub fn clear_gutter_highlights<T: 'static>(
17342 &mut self,
17343 cx: &mut Context<Self>,
17344 ) -> Option<GutterHighlight> {
17345 cx.notify();
17346 self.gutter_highlights.remove(&TypeId::of::<T>())
17347 }
17348
17349 #[cfg(feature = "test-support")]
17350 pub fn all_text_background_highlights(
17351 &self,
17352 window: &mut Window,
17353 cx: &mut Context<Self>,
17354 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17355 let snapshot = self.snapshot(window, cx);
17356 let buffer = &snapshot.buffer_snapshot;
17357 let start = buffer.anchor_before(0);
17358 let end = buffer.anchor_after(buffer.len());
17359 let theme = cx.theme().colors();
17360 self.background_highlights_in_range(start..end, &snapshot, theme)
17361 }
17362
17363 #[cfg(feature = "test-support")]
17364 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17365 let snapshot = self.buffer().read(cx).snapshot(cx);
17366
17367 let highlights = self
17368 .background_highlights
17369 .get(&TypeId::of::<items::BufferSearchHighlights>());
17370
17371 if let Some((_color, ranges)) = highlights {
17372 ranges
17373 .iter()
17374 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17375 .collect_vec()
17376 } else {
17377 vec![]
17378 }
17379 }
17380
17381 fn document_highlights_for_position<'a>(
17382 &'a self,
17383 position: Anchor,
17384 buffer: &'a MultiBufferSnapshot,
17385 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17386 let read_highlights = self
17387 .background_highlights
17388 .get(&TypeId::of::<DocumentHighlightRead>())
17389 .map(|h| &h.1);
17390 let write_highlights = self
17391 .background_highlights
17392 .get(&TypeId::of::<DocumentHighlightWrite>())
17393 .map(|h| &h.1);
17394 let left_position = position.bias_left(buffer);
17395 let right_position = position.bias_right(buffer);
17396 read_highlights
17397 .into_iter()
17398 .chain(write_highlights)
17399 .flat_map(move |ranges| {
17400 let start_ix = match ranges.binary_search_by(|probe| {
17401 let cmp = probe.end.cmp(&left_position, buffer);
17402 if cmp.is_ge() {
17403 Ordering::Greater
17404 } else {
17405 Ordering::Less
17406 }
17407 }) {
17408 Ok(i) | Err(i) => i,
17409 };
17410
17411 ranges[start_ix..]
17412 .iter()
17413 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17414 })
17415 }
17416
17417 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17418 self.background_highlights
17419 .get(&TypeId::of::<T>())
17420 .map_or(false, |(_, highlights)| !highlights.is_empty())
17421 }
17422
17423 pub fn background_highlights_in_range(
17424 &self,
17425 search_range: Range<Anchor>,
17426 display_snapshot: &DisplaySnapshot,
17427 theme: &ThemeColors,
17428 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17429 let mut results = Vec::new();
17430 for (color_fetcher, ranges) in self.background_highlights.values() {
17431 let color = color_fetcher(theme);
17432 let start_ix = match ranges.binary_search_by(|probe| {
17433 let cmp = probe
17434 .end
17435 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17436 if cmp.is_gt() {
17437 Ordering::Greater
17438 } else {
17439 Ordering::Less
17440 }
17441 }) {
17442 Ok(i) | Err(i) => i,
17443 };
17444 for range in &ranges[start_ix..] {
17445 if range
17446 .start
17447 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17448 .is_ge()
17449 {
17450 break;
17451 }
17452
17453 let start = range.start.to_display_point(display_snapshot);
17454 let end = range.end.to_display_point(display_snapshot);
17455 results.push((start..end, color))
17456 }
17457 }
17458 results
17459 }
17460
17461 pub fn background_highlight_row_ranges<T: 'static>(
17462 &self,
17463 search_range: Range<Anchor>,
17464 display_snapshot: &DisplaySnapshot,
17465 count: usize,
17466 ) -> Vec<RangeInclusive<DisplayPoint>> {
17467 let mut results = Vec::new();
17468 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17469 return vec![];
17470 };
17471
17472 let start_ix = match ranges.binary_search_by(|probe| {
17473 let cmp = probe
17474 .end
17475 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17476 if cmp.is_gt() {
17477 Ordering::Greater
17478 } else {
17479 Ordering::Less
17480 }
17481 }) {
17482 Ok(i) | Err(i) => i,
17483 };
17484 let mut push_region = |start: Option<Point>, end: Option<Point>| {
17485 if let (Some(start_display), Some(end_display)) = (start, end) {
17486 results.push(
17487 start_display.to_display_point(display_snapshot)
17488 ..=end_display.to_display_point(display_snapshot),
17489 );
17490 }
17491 };
17492 let mut start_row: Option<Point> = None;
17493 let mut end_row: Option<Point> = None;
17494 if ranges.len() > count {
17495 return Vec::new();
17496 }
17497 for range in &ranges[start_ix..] {
17498 if range
17499 .start
17500 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17501 .is_ge()
17502 {
17503 break;
17504 }
17505 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
17506 if let Some(current_row) = &end_row {
17507 if end.row == current_row.row {
17508 continue;
17509 }
17510 }
17511 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
17512 if start_row.is_none() {
17513 assert_eq!(end_row, None);
17514 start_row = Some(start);
17515 end_row = Some(end);
17516 continue;
17517 }
17518 if let Some(current_end) = end_row.as_mut() {
17519 if start.row > current_end.row + 1 {
17520 push_region(start_row, end_row);
17521 start_row = Some(start);
17522 end_row = Some(end);
17523 } else {
17524 // Merge two hunks.
17525 *current_end = end;
17526 }
17527 } else {
17528 unreachable!();
17529 }
17530 }
17531 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
17532 push_region(start_row, end_row);
17533 results
17534 }
17535
17536 pub fn gutter_highlights_in_range(
17537 &self,
17538 search_range: Range<Anchor>,
17539 display_snapshot: &DisplaySnapshot,
17540 cx: &App,
17541 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17542 let mut results = Vec::new();
17543 for (color_fetcher, ranges) in self.gutter_highlights.values() {
17544 let color = color_fetcher(cx);
17545 let start_ix = match ranges.binary_search_by(|probe| {
17546 let cmp = probe
17547 .end
17548 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17549 if cmp.is_gt() {
17550 Ordering::Greater
17551 } else {
17552 Ordering::Less
17553 }
17554 }) {
17555 Ok(i) | Err(i) => i,
17556 };
17557 for range in &ranges[start_ix..] {
17558 if range
17559 .start
17560 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17561 .is_ge()
17562 {
17563 break;
17564 }
17565
17566 let start = range.start.to_display_point(display_snapshot);
17567 let end = range.end.to_display_point(display_snapshot);
17568 results.push((start..end, color))
17569 }
17570 }
17571 results
17572 }
17573
17574 /// Get the text ranges corresponding to the redaction query
17575 pub fn redacted_ranges(
17576 &self,
17577 search_range: Range<Anchor>,
17578 display_snapshot: &DisplaySnapshot,
17579 cx: &App,
17580 ) -> Vec<Range<DisplayPoint>> {
17581 display_snapshot
17582 .buffer_snapshot
17583 .redacted_ranges(search_range, |file| {
17584 if let Some(file) = file {
17585 file.is_private()
17586 && EditorSettings::get(
17587 Some(SettingsLocation {
17588 worktree_id: file.worktree_id(cx),
17589 path: file.path().as_ref(),
17590 }),
17591 cx,
17592 )
17593 .redact_private_values
17594 } else {
17595 false
17596 }
17597 })
17598 .map(|range| {
17599 range.start.to_display_point(display_snapshot)
17600 ..range.end.to_display_point(display_snapshot)
17601 })
17602 .collect()
17603 }
17604
17605 pub fn highlight_text<T: 'static>(
17606 &mut self,
17607 ranges: Vec<Range<Anchor>>,
17608 style: HighlightStyle,
17609 cx: &mut Context<Self>,
17610 ) {
17611 self.display_map.update(cx, |map, _| {
17612 map.highlight_text(TypeId::of::<T>(), ranges, style)
17613 });
17614 cx.notify();
17615 }
17616
17617 pub(crate) fn highlight_inlays<T: 'static>(
17618 &mut self,
17619 highlights: Vec<InlayHighlight>,
17620 style: HighlightStyle,
17621 cx: &mut Context<Self>,
17622 ) {
17623 self.display_map.update(cx, |map, _| {
17624 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
17625 });
17626 cx.notify();
17627 }
17628
17629 pub fn text_highlights<'a, T: 'static>(
17630 &'a self,
17631 cx: &'a App,
17632 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
17633 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
17634 }
17635
17636 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
17637 let cleared = self
17638 .display_map
17639 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
17640 if cleared {
17641 cx.notify();
17642 }
17643 }
17644
17645 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
17646 (self.read_only(cx) || self.blink_manager.read(cx).visible())
17647 && self.focus_handle.is_focused(window)
17648 }
17649
17650 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
17651 self.show_cursor_when_unfocused = is_enabled;
17652 cx.notify();
17653 }
17654
17655 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
17656 cx.notify();
17657 }
17658
17659 fn on_debug_session_event(
17660 &mut self,
17661 _session: Entity<Session>,
17662 event: &SessionEvent,
17663 cx: &mut Context<Self>,
17664 ) {
17665 match event {
17666 SessionEvent::InvalidateInlineValue => {
17667 self.refresh_inline_values(cx);
17668 }
17669 _ => {}
17670 }
17671 }
17672
17673 fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
17674 let Some(project) = self.project.clone() else {
17675 return;
17676 };
17677 let Some(buffer) = self.buffer.read(cx).as_singleton() else {
17678 return;
17679 };
17680 if !self.inline_value_cache.enabled {
17681 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
17682 self.splice_inlays(&inlays, Vec::new(), cx);
17683 return;
17684 }
17685
17686 let current_execution_position = self
17687 .highlighted_rows
17688 .get(&TypeId::of::<ActiveDebugLine>())
17689 .and_then(|lines| lines.last().map(|line| line.range.start));
17690
17691 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
17692 let snapshot = editor
17693 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17694 .ok()?;
17695
17696 let inline_values = editor
17697 .update(cx, |_, cx| {
17698 let Some(current_execution_position) = current_execution_position else {
17699 return Some(Task::ready(Ok(Vec::new())));
17700 };
17701
17702 // todo(debugger) when introducing multi buffer inline values check execution position's buffer id to make sure the text
17703 // anchor is in the same buffer
17704 let range =
17705 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
17706 project.inline_values(buffer, range, cx)
17707 })
17708 .ok()
17709 .flatten()?
17710 .await
17711 .context("refreshing debugger inlays")
17712 .log_err()?;
17713
17714 let (excerpt_id, buffer_id) = snapshot
17715 .excerpts()
17716 .next()
17717 .map(|excerpt| (excerpt.0, excerpt.1.remote_id()))?;
17718 editor
17719 .update(cx, |editor, cx| {
17720 let new_inlays = inline_values
17721 .into_iter()
17722 .map(|debugger_value| {
17723 Inlay::debugger_hint(
17724 post_inc(&mut editor.next_inlay_id),
17725 Anchor::in_buffer(excerpt_id, buffer_id, debugger_value.position),
17726 debugger_value.text(),
17727 )
17728 })
17729 .collect::<Vec<_>>();
17730 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
17731 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
17732
17733 editor.splice_inlays(&inlay_ids, new_inlays, cx);
17734 })
17735 .ok()?;
17736 Some(())
17737 });
17738 }
17739
17740 fn on_buffer_event(
17741 &mut self,
17742 multibuffer: &Entity<MultiBuffer>,
17743 event: &multi_buffer::Event,
17744 window: &mut Window,
17745 cx: &mut Context<Self>,
17746 ) {
17747 match event {
17748 multi_buffer::Event::Edited {
17749 singleton_buffer_edited,
17750 edited_buffer: buffer_edited,
17751 } => {
17752 self.scrollbar_marker_state.dirty = true;
17753 self.active_indent_guides_state.dirty = true;
17754 self.refresh_active_diagnostics(cx);
17755 self.refresh_code_actions(window, cx);
17756 self.refresh_selected_text_highlights(true, window, cx);
17757 refresh_matching_bracket_highlights(self, window, cx);
17758 if self.has_active_inline_completion() {
17759 self.update_visible_inline_completion(window, cx);
17760 }
17761 if let Some(buffer) = buffer_edited {
17762 let buffer_id = buffer.read(cx).remote_id();
17763 if !self.registered_buffers.contains_key(&buffer_id) {
17764 if let Some(project) = self.project.as_ref() {
17765 project.update(cx, |project, cx| {
17766 self.registered_buffers.insert(
17767 buffer_id,
17768 project.register_buffer_with_language_servers(&buffer, cx),
17769 );
17770 })
17771 }
17772 }
17773 }
17774 cx.emit(EditorEvent::BufferEdited);
17775 cx.emit(SearchEvent::MatchesInvalidated);
17776 if *singleton_buffer_edited {
17777 if let Some(project) = &self.project {
17778 #[allow(clippy::mutable_key_type)]
17779 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
17780 multibuffer
17781 .all_buffers()
17782 .into_iter()
17783 .filter_map(|buffer| {
17784 buffer.update(cx, |buffer, cx| {
17785 let language = buffer.language()?;
17786 let should_discard = project.update(cx, |project, cx| {
17787 project.is_local()
17788 && !project.has_language_servers_for(buffer, cx)
17789 });
17790 should_discard.not().then_some(language.clone())
17791 })
17792 })
17793 .collect::<HashSet<_>>()
17794 });
17795 if !languages_affected.is_empty() {
17796 self.refresh_inlay_hints(
17797 InlayHintRefreshReason::BufferEdited(languages_affected),
17798 cx,
17799 );
17800 }
17801 }
17802 }
17803
17804 let Some(project) = &self.project else { return };
17805 let (telemetry, is_via_ssh) = {
17806 let project = project.read(cx);
17807 let telemetry = project.client().telemetry().clone();
17808 let is_via_ssh = project.is_via_ssh();
17809 (telemetry, is_via_ssh)
17810 };
17811 refresh_linked_ranges(self, window, cx);
17812 telemetry.log_edit_event("editor", is_via_ssh);
17813 }
17814 multi_buffer::Event::ExcerptsAdded {
17815 buffer,
17816 predecessor,
17817 excerpts,
17818 } => {
17819 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17820 let buffer_id = buffer.read(cx).remote_id();
17821 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
17822 if let Some(project) = &self.project {
17823 get_uncommitted_diff_for_buffer(
17824 project,
17825 [buffer.clone()],
17826 self.buffer.clone(),
17827 cx,
17828 )
17829 .detach();
17830 }
17831 }
17832 cx.emit(EditorEvent::ExcerptsAdded {
17833 buffer: buffer.clone(),
17834 predecessor: *predecessor,
17835 excerpts: excerpts.clone(),
17836 });
17837 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17838 }
17839 multi_buffer::Event::ExcerptsRemoved {
17840 ids,
17841 removed_buffer_ids,
17842 } => {
17843 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
17844 let buffer = self.buffer.read(cx);
17845 self.registered_buffers
17846 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
17847 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17848 cx.emit(EditorEvent::ExcerptsRemoved {
17849 ids: ids.clone(),
17850 removed_buffer_ids: removed_buffer_ids.clone(),
17851 })
17852 }
17853 multi_buffer::Event::ExcerptsEdited {
17854 excerpt_ids,
17855 buffer_ids,
17856 } => {
17857 self.display_map.update(cx, |map, cx| {
17858 map.unfold_buffers(buffer_ids.iter().copied(), cx)
17859 });
17860 cx.emit(EditorEvent::ExcerptsEdited {
17861 ids: excerpt_ids.clone(),
17862 })
17863 }
17864 multi_buffer::Event::ExcerptsExpanded { ids } => {
17865 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17866 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
17867 }
17868 multi_buffer::Event::Reparsed(buffer_id) => {
17869 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17870 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17871
17872 cx.emit(EditorEvent::Reparsed(*buffer_id));
17873 }
17874 multi_buffer::Event::DiffHunksToggled => {
17875 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17876 }
17877 multi_buffer::Event::LanguageChanged(buffer_id) => {
17878 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
17879 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
17880 cx.emit(EditorEvent::Reparsed(*buffer_id));
17881 cx.notify();
17882 }
17883 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
17884 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
17885 multi_buffer::Event::FileHandleChanged
17886 | multi_buffer::Event::Reloaded
17887 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
17888 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
17889 multi_buffer::Event::DiagnosticsUpdated => {
17890 self.refresh_active_diagnostics(cx);
17891 self.refresh_inline_diagnostics(true, window, cx);
17892 self.scrollbar_marker_state.dirty = true;
17893 cx.notify();
17894 }
17895 _ => {}
17896 };
17897 }
17898
17899 fn on_display_map_changed(
17900 &mut self,
17901 _: Entity<DisplayMap>,
17902 _: &mut Window,
17903 cx: &mut Context<Self>,
17904 ) {
17905 cx.notify();
17906 }
17907
17908 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17909 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
17910 self.update_edit_prediction_settings(cx);
17911 self.refresh_inline_completion(true, false, window, cx);
17912 self.refresh_inlay_hints(
17913 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
17914 self.selections.newest_anchor().head(),
17915 &self.buffer.read(cx).snapshot(cx),
17916 cx,
17917 )),
17918 cx,
17919 );
17920
17921 let old_cursor_shape = self.cursor_shape;
17922
17923 {
17924 let editor_settings = EditorSettings::get_global(cx);
17925 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
17926 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
17927 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
17928 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
17929 }
17930
17931 if old_cursor_shape != self.cursor_shape {
17932 cx.emit(EditorEvent::CursorShapeChanged);
17933 }
17934
17935 let project_settings = ProjectSettings::get_global(cx);
17936 self.serialize_dirty_buffers = project_settings.session.restore_unsaved_buffers;
17937
17938 if self.mode.is_full() {
17939 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
17940 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
17941 if self.show_inline_diagnostics != show_inline_diagnostics {
17942 self.show_inline_diagnostics = show_inline_diagnostics;
17943 self.refresh_inline_diagnostics(false, window, cx);
17944 }
17945
17946 if self.git_blame_inline_enabled != inline_blame_enabled {
17947 self.toggle_git_blame_inline_internal(false, window, cx);
17948 }
17949 }
17950
17951 cx.notify();
17952 }
17953
17954 pub fn set_searchable(&mut self, searchable: bool) {
17955 self.searchable = searchable;
17956 }
17957
17958 pub fn searchable(&self) -> bool {
17959 self.searchable
17960 }
17961
17962 fn open_proposed_changes_editor(
17963 &mut self,
17964 _: &OpenProposedChangesEditor,
17965 window: &mut Window,
17966 cx: &mut Context<Self>,
17967 ) {
17968 let Some(workspace) = self.workspace() else {
17969 cx.propagate();
17970 return;
17971 };
17972
17973 let selections = self.selections.all::<usize>(cx);
17974 let multi_buffer = self.buffer.read(cx);
17975 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17976 let mut new_selections_by_buffer = HashMap::default();
17977 for selection in selections {
17978 for (buffer, range, _) in
17979 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
17980 {
17981 let mut range = range.to_point(buffer);
17982 range.start.column = 0;
17983 range.end.column = buffer.line_len(range.end.row);
17984 new_selections_by_buffer
17985 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
17986 .or_insert(Vec::new())
17987 .push(range)
17988 }
17989 }
17990
17991 let proposed_changes_buffers = new_selections_by_buffer
17992 .into_iter()
17993 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
17994 .collect::<Vec<_>>();
17995 let proposed_changes_editor = cx.new(|cx| {
17996 ProposedChangesEditor::new(
17997 "Proposed changes",
17998 proposed_changes_buffers,
17999 self.project.clone(),
18000 window,
18001 cx,
18002 )
18003 });
18004
18005 window.defer(cx, move |window, cx| {
18006 workspace.update(cx, |workspace, cx| {
18007 workspace.active_pane().update(cx, |pane, cx| {
18008 pane.add_item(
18009 Box::new(proposed_changes_editor),
18010 true,
18011 true,
18012 None,
18013 window,
18014 cx,
18015 );
18016 });
18017 });
18018 });
18019 }
18020
18021 pub fn open_excerpts_in_split(
18022 &mut self,
18023 _: &OpenExcerptsSplit,
18024 window: &mut Window,
18025 cx: &mut Context<Self>,
18026 ) {
18027 self.open_excerpts_common(None, true, window, cx)
18028 }
18029
18030 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18031 self.open_excerpts_common(None, false, window, cx)
18032 }
18033
18034 fn open_excerpts_common(
18035 &mut self,
18036 jump_data: Option<JumpData>,
18037 split: bool,
18038 window: &mut Window,
18039 cx: &mut Context<Self>,
18040 ) {
18041 let Some(workspace) = self.workspace() else {
18042 cx.propagate();
18043 return;
18044 };
18045
18046 if self.buffer.read(cx).is_singleton() {
18047 cx.propagate();
18048 return;
18049 }
18050
18051 let mut new_selections_by_buffer = HashMap::default();
18052 match &jump_data {
18053 Some(JumpData::MultiBufferPoint {
18054 excerpt_id,
18055 position,
18056 anchor,
18057 line_offset_from_top,
18058 }) => {
18059 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18060 if let Some(buffer) = multi_buffer_snapshot
18061 .buffer_id_for_excerpt(*excerpt_id)
18062 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18063 {
18064 let buffer_snapshot = buffer.read(cx).snapshot();
18065 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18066 language::ToPoint::to_point(anchor, &buffer_snapshot)
18067 } else {
18068 buffer_snapshot.clip_point(*position, Bias::Left)
18069 };
18070 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18071 new_selections_by_buffer.insert(
18072 buffer,
18073 (
18074 vec![jump_to_offset..jump_to_offset],
18075 Some(*line_offset_from_top),
18076 ),
18077 );
18078 }
18079 }
18080 Some(JumpData::MultiBufferRow {
18081 row,
18082 line_offset_from_top,
18083 }) => {
18084 let point = MultiBufferPoint::new(row.0, 0);
18085 if let Some((buffer, buffer_point, _)) =
18086 self.buffer.read(cx).point_to_buffer_point(point, cx)
18087 {
18088 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18089 new_selections_by_buffer
18090 .entry(buffer)
18091 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18092 .0
18093 .push(buffer_offset..buffer_offset)
18094 }
18095 }
18096 None => {
18097 let selections = self.selections.all::<usize>(cx);
18098 let multi_buffer = self.buffer.read(cx);
18099 for selection in selections {
18100 for (snapshot, range, _, anchor) in multi_buffer
18101 .snapshot(cx)
18102 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18103 {
18104 if let Some(anchor) = anchor {
18105 // selection is in a deleted hunk
18106 let Some(buffer_id) = anchor.buffer_id else {
18107 continue;
18108 };
18109 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18110 continue;
18111 };
18112 let offset = text::ToOffset::to_offset(
18113 &anchor.text_anchor,
18114 &buffer_handle.read(cx).snapshot(),
18115 );
18116 let range = offset..offset;
18117 new_selections_by_buffer
18118 .entry(buffer_handle)
18119 .or_insert((Vec::new(), None))
18120 .0
18121 .push(range)
18122 } else {
18123 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18124 else {
18125 continue;
18126 };
18127 new_selections_by_buffer
18128 .entry(buffer_handle)
18129 .or_insert((Vec::new(), None))
18130 .0
18131 .push(range)
18132 }
18133 }
18134 }
18135 }
18136 }
18137
18138 new_selections_by_buffer
18139 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18140
18141 if new_selections_by_buffer.is_empty() {
18142 return;
18143 }
18144
18145 // We defer the pane interaction because we ourselves are a workspace item
18146 // and activating a new item causes the pane to call a method on us reentrantly,
18147 // which panics if we're on the stack.
18148 window.defer(cx, move |window, cx| {
18149 workspace.update(cx, |workspace, cx| {
18150 let pane = if split {
18151 workspace.adjacent_pane(window, cx)
18152 } else {
18153 workspace.active_pane().clone()
18154 };
18155
18156 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18157 let editor = buffer
18158 .read(cx)
18159 .file()
18160 .is_none()
18161 .then(|| {
18162 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18163 // so `workspace.open_project_item` will never find them, always opening a new editor.
18164 // Instead, we try to activate the existing editor in the pane first.
18165 let (editor, pane_item_index) =
18166 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18167 let editor = item.downcast::<Editor>()?;
18168 let singleton_buffer =
18169 editor.read(cx).buffer().read(cx).as_singleton()?;
18170 if singleton_buffer == buffer {
18171 Some((editor, i))
18172 } else {
18173 None
18174 }
18175 })?;
18176 pane.update(cx, |pane, cx| {
18177 pane.activate_item(pane_item_index, true, true, window, cx)
18178 });
18179 Some(editor)
18180 })
18181 .flatten()
18182 .unwrap_or_else(|| {
18183 workspace.open_project_item::<Self>(
18184 pane.clone(),
18185 buffer,
18186 true,
18187 true,
18188 window,
18189 cx,
18190 )
18191 });
18192
18193 editor.update(cx, |editor, cx| {
18194 let autoscroll = match scroll_offset {
18195 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18196 None => Autoscroll::newest(),
18197 };
18198 let nav_history = editor.nav_history.take();
18199 editor.change_selections(Some(autoscroll), window, cx, |s| {
18200 s.select_ranges(ranges);
18201 });
18202 editor.nav_history = nav_history;
18203 });
18204 }
18205 })
18206 });
18207 }
18208
18209 // For now, don't allow opening excerpts in buffers that aren't backed by
18210 // regular project files.
18211 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18212 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18213 }
18214
18215 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18216 let snapshot = self.buffer.read(cx).read(cx);
18217 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18218 Some(
18219 ranges
18220 .iter()
18221 .map(move |range| {
18222 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18223 })
18224 .collect(),
18225 )
18226 }
18227
18228 fn selection_replacement_ranges(
18229 &self,
18230 range: Range<OffsetUtf16>,
18231 cx: &mut App,
18232 ) -> Vec<Range<OffsetUtf16>> {
18233 let selections = self.selections.all::<OffsetUtf16>(cx);
18234 let newest_selection = selections
18235 .iter()
18236 .max_by_key(|selection| selection.id)
18237 .unwrap();
18238 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18239 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18240 let snapshot = self.buffer.read(cx).read(cx);
18241 selections
18242 .into_iter()
18243 .map(|mut selection| {
18244 selection.start.0 =
18245 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18246 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18247 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18248 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18249 })
18250 .collect()
18251 }
18252
18253 fn report_editor_event(
18254 &self,
18255 event_type: &'static str,
18256 file_extension: Option<String>,
18257 cx: &App,
18258 ) {
18259 if cfg!(any(test, feature = "test-support")) {
18260 return;
18261 }
18262
18263 let Some(project) = &self.project else { return };
18264
18265 // If None, we are in a file without an extension
18266 let file = self
18267 .buffer
18268 .read(cx)
18269 .as_singleton()
18270 .and_then(|b| b.read(cx).file());
18271 let file_extension = file_extension.or(file
18272 .as_ref()
18273 .and_then(|file| Path::new(file.file_name(cx)).extension())
18274 .and_then(|e| e.to_str())
18275 .map(|a| a.to_string()));
18276
18277 let vim_mode = vim_enabled(cx);
18278
18279 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18280 let copilot_enabled = edit_predictions_provider
18281 == language::language_settings::EditPredictionProvider::Copilot;
18282 let copilot_enabled_for_language = self
18283 .buffer
18284 .read(cx)
18285 .language_settings(cx)
18286 .show_edit_predictions;
18287
18288 let project = project.read(cx);
18289 telemetry::event!(
18290 event_type,
18291 file_extension,
18292 vim_mode,
18293 copilot_enabled,
18294 copilot_enabled_for_language,
18295 edit_predictions_provider,
18296 is_via_ssh = project.is_via_ssh(),
18297 );
18298 }
18299
18300 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18301 /// with each line being an array of {text, highlight} objects.
18302 fn copy_highlight_json(
18303 &mut self,
18304 _: &CopyHighlightJson,
18305 window: &mut Window,
18306 cx: &mut Context<Self>,
18307 ) {
18308 #[derive(Serialize)]
18309 struct Chunk<'a> {
18310 text: String,
18311 highlight: Option<&'a str>,
18312 }
18313
18314 let snapshot = self.buffer.read(cx).snapshot(cx);
18315 let range = self
18316 .selected_text_range(false, window, cx)
18317 .and_then(|selection| {
18318 if selection.range.is_empty() {
18319 None
18320 } else {
18321 Some(selection.range)
18322 }
18323 })
18324 .unwrap_or_else(|| 0..snapshot.len());
18325
18326 let chunks = snapshot.chunks(range, true);
18327 let mut lines = Vec::new();
18328 let mut line: VecDeque<Chunk> = VecDeque::new();
18329
18330 let Some(style) = self.style.as_ref() else {
18331 return;
18332 };
18333
18334 for chunk in chunks {
18335 let highlight = chunk
18336 .syntax_highlight_id
18337 .and_then(|id| id.name(&style.syntax));
18338 let mut chunk_lines = chunk.text.split('\n').peekable();
18339 while let Some(text) = chunk_lines.next() {
18340 let mut merged_with_last_token = false;
18341 if let Some(last_token) = line.back_mut() {
18342 if last_token.highlight == highlight {
18343 last_token.text.push_str(text);
18344 merged_with_last_token = true;
18345 }
18346 }
18347
18348 if !merged_with_last_token {
18349 line.push_back(Chunk {
18350 text: text.into(),
18351 highlight,
18352 });
18353 }
18354
18355 if chunk_lines.peek().is_some() {
18356 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18357 line.pop_front();
18358 }
18359 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18360 line.pop_back();
18361 }
18362
18363 lines.push(mem::take(&mut line));
18364 }
18365 }
18366 }
18367
18368 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18369 return;
18370 };
18371 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18372 }
18373
18374 pub fn open_context_menu(
18375 &mut self,
18376 _: &OpenContextMenu,
18377 window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 self.request_autoscroll(Autoscroll::newest(), cx);
18381 let position = self.selections.newest_display(cx).start;
18382 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18383 }
18384
18385 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18386 &self.inlay_hint_cache
18387 }
18388
18389 pub fn replay_insert_event(
18390 &mut self,
18391 text: &str,
18392 relative_utf16_range: Option<Range<isize>>,
18393 window: &mut Window,
18394 cx: &mut Context<Self>,
18395 ) {
18396 if !self.input_enabled {
18397 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18398 return;
18399 }
18400 if let Some(relative_utf16_range) = relative_utf16_range {
18401 let selections = self.selections.all::<OffsetUtf16>(cx);
18402 self.change_selections(None, window, cx, |s| {
18403 let new_ranges = selections.into_iter().map(|range| {
18404 let start = OffsetUtf16(
18405 range
18406 .head()
18407 .0
18408 .saturating_add_signed(relative_utf16_range.start),
18409 );
18410 let end = OffsetUtf16(
18411 range
18412 .head()
18413 .0
18414 .saturating_add_signed(relative_utf16_range.end),
18415 );
18416 start..end
18417 });
18418 s.select_ranges(new_ranges);
18419 });
18420 }
18421
18422 self.handle_input(text, window, cx);
18423 }
18424
18425 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
18426 let Some(provider) = self.semantics_provider.as_ref() else {
18427 return false;
18428 };
18429
18430 let mut supports = false;
18431 self.buffer().update(cx, |this, cx| {
18432 this.for_each_buffer(|buffer| {
18433 supports |= provider.supports_inlay_hints(buffer, cx);
18434 });
18435 });
18436
18437 supports
18438 }
18439
18440 pub fn is_focused(&self, window: &Window) -> bool {
18441 self.focus_handle.is_focused(window)
18442 }
18443
18444 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18445 cx.emit(EditorEvent::Focused);
18446
18447 if let Some(descendant) = self
18448 .last_focused_descendant
18449 .take()
18450 .and_then(|descendant| descendant.upgrade())
18451 {
18452 window.focus(&descendant);
18453 } else {
18454 if let Some(blame) = self.blame.as_ref() {
18455 blame.update(cx, GitBlame::focus)
18456 }
18457
18458 self.blink_manager.update(cx, BlinkManager::enable);
18459 self.show_cursor_names(window, cx);
18460 self.buffer.update(cx, |buffer, cx| {
18461 buffer.finalize_last_transaction(cx);
18462 if self.leader_peer_id.is_none() {
18463 buffer.set_active_selections(
18464 &self.selections.disjoint_anchors(),
18465 self.selections.line_mode,
18466 self.cursor_shape,
18467 cx,
18468 );
18469 }
18470 });
18471 }
18472 }
18473
18474 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
18475 cx.emit(EditorEvent::FocusedIn)
18476 }
18477
18478 fn handle_focus_out(
18479 &mut self,
18480 event: FocusOutEvent,
18481 _window: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) {
18484 if event.blurred != self.focus_handle {
18485 self.last_focused_descendant = Some(event.blurred);
18486 }
18487 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
18488 }
18489
18490 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18491 self.blink_manager.update(cx, BlinkManager::disable);
18492 self.buffer
18493 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
18494
18495 if let Some(blame) = self.blame.as_ref() {
18496 blame.update(cx, GitBlame::blur)
18497 }
18498 if !self.hover_state.focused(window, cx) {
18499 hide_hover(self, cx);
18500 }
18501 if !self
18502 .context_menu
18503 .borrow()
18504 .as_ref()
18505 .is_some_and(|context_menu| context_menu.focused(window, cx))
18506 {
18507 self.hide_context_menu(window, cx);
18508 }
18509 self.discard_inline_completion(false, cx);
18510 cx.emit(EditorEvent::Blurred);
18511 cx.notify();
18512 }
18513
18514 pub fn register_action<A: Action>(
18515 &mut self,
18516 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
18517 ) -> Subscription {
18518 let id = self.next_editor_action_id.post_inc();
18519 let listener = Arc::new(listener);
18520 self.editor_actions.borrow_mut().insert(
18521 id,
18522 Box::new(move |window, _| {
18523 let listener = listener.clone();
18524 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
18525 let action = action.downcast_ref().unwrap();
18526 if phase == DispatchPhase::Bubble {
18527 listener(action, window, cx)
18528 }
18529 })
18530 }),
18531 );
18532
18533 let editor_actions = self.editor_actions.clone();
18534 Subscription::new(move || {
18535 editor_actions.borrow_mut().remove(&id);
18536 })
18537 }
18538
18539 pub fn file_header_size(&self) -> u32 {
18540 FILE_HEADER_HEIGHT
18541 }
18542
18543 pub fn restore(
18544 &mut self,
18545 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
18546 window: &mut Window,
18547 cx: &mut Context<Self>,
18548 ) {
18549 let workspace = self.workspace();
18550 let project = self.project.as_ref();
18551 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
18552 let mut tasks = Vec::new();
18553 for (buffer_id, changes) in revert_changes {
18554 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
18555 buffer.update(cx, |buffer, cx| {
18556 buffer.edit(
18557 changes
18558 .into_iter()
18559 .map(|(range, text)| (range, text.to_string())),
18560 None,
18561 cx,
18562 );
18563 });
18564
18565 if let Some(project) =
18566 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
18567 {
18568 project.update(cx, |project, cx| {
18569 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
18570 })
18571 }
18572 }
18573 }
18574 tasks
18575 });
18576 cx.spawn_in(window, async move |_, cx| {
18577 for (buffer, task) in save_tasks {
18578 let result = task.await;
18579 if result.is_err() {
18580 let Some(path) = buffer
18581 .read_with(cx, |buffer, cx| buffer.project_path(cx))
18582 .ok()
18583 else {
18584 continue;
18585 };
18586 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
18587 let Some(task) = cx
18588 .update_window_entity(&workspace, |workspace, window, cx| {
18589 workspace
18590 .open_path_preview(path, None, false, false, false, window, cx)
18591 })
18592 .ok()
18593 else {
18594 continue;
18595 };
18596 task.await.log_err();
18597 }
18598 }
18599 }
18600 })
18601 .detach();
18602 self.change_selections(None, window, cx, |selections| selections.refresh());
18603 }
18604
18605 pub fn to_pixel_point(
18606 &self,
18607 source: multi_buffer::Anchor,
18608 editor_snapshot: &EditorSnapshot,
18609 window: &mut Window,
18610 ) -> Option<gpui::Point<Pixels>> {
18611 let source_point = source.to_display_point(editor_snapshot);
18612 self.display_to_pixel_point(source_point, editor_snapshot, window)
18613 }
18614
18615 pub fn display_to_pixel_point(
18616 &self,
18617 source: DisplayPoint,
18618 editor_snapshot: &EditorSnapshot,
18619 window: &mut Window,
18620 ) -> Option<gpui::Point<Pixels>> {
18621 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
18622 let text_layout_details = self.text_layout_details(window);
18623 let scroll_top = text_layout_details
18624 .scroll_anchor
18625 .scroll_position(editor_snapshot)
18626 .y;
18627
18628 if source.row().as_f32() < scroll_top.floor() {
18629 return None;
18630 }
18631 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
18632 let source_y = line_height * (source.row().as_f32() - scroll_top);
18633 Some(gpui::Point::new(source_x, source_y))
18634 }
18635
18636 pub fn has_visible_completions_menu(&self) -> bool {
18637 !self.edit_prediction_preview_is_active()
18638 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
18639 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
18640 })
18641 }
18642
18643 pub fn register_addon<T: Addon>(&mut self, instance: T) {
18644 self.addons
18645 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
18646 }
18647
18648 pub fn unregister_addon<T: Addon>(&mut self) {
18649 self.addons.remove(&std::any::TypeId::of::<T>());
18650 }
18651
18652 pub fn addon<T: Addon>(&self) -> Option<&T> {
18653 let type_id = std::any::TypeId::of::<T>();
18654 self.addons
18655 .get(&type_id)
18656 .and_then(|item| item.to_any().downcast_ref::<T>())
18657 }
18658
18659 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
18660 let type_id = std::any::TypeId::of::<T>();
18661 self.addons
18662 .get_mut(&type_id)
18663 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
18664 }
18665
18666 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
18667 let text_layout_details = self.text_layout_details(window);
18668 let style = &text_layout_details.editor_style;
18669 let font_id = window.text_system().resolve_font(&style.text.font());
18670 let font_size = style.text.font_size.to_pixels(window.rem_size());
18671 let line_height = style.text.line_height_in_pixels(window.rem_size());
18672 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
18673
18674 gpui::Size::new(em_width, line_height)
18675 }
18676
18677 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
18678 self.load_diff_task.clone()
18679 }
18680
18681 fn read_metadata_from_db(
18682 &mut self,
18683 item_id: u64,
18684 workspace_id: WorkspaceId,
18685 window: &mut Window,
18686 cx: &mut Context<Editor>,
18687 ) {
18688 if self.is_singleton(cx)
18689 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
18690 {
18691 let buffer_snapshot = OnceCell::new();
18692
18693 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
18694 if !folds.is_empty() {
18695 let snapshot =
18696 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18697 self.fold_ranges(
18698 folds
18699 .into_iter()
18700 .map(|(start, end)| {
18701 snapshot.clip_offset(start, Bias::Left)
18702 ..snapshot.clip_offset(end, Bias::Right)
18703 })
18704 .collect(),
18705 false,
18706 window,
18707 cx,
18708 );
18709 }
18710 }
18711
18712 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
18713 if !selections.is_empty() {
18714 let snapshot =
18715 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
18716 self.change_selections(None, window, cx, |s| {
18717 s.select_ranges(selections.into_iter().map(|(start, end)| {
18718 snapshot.clip_offset(start, Bias::Left)
18719 ..snapshot.clip_offset(end, Bias::Right)
18720 }));
18721 });
18722 }
18723 };
18724 }
18725
18726 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
18727 }
18728}
18729
18730fn vim_enabled(cx: &App) -> bool {
18731 cx.global::<SettingsStore>()
18732 .raw_user_settings()
18733 .get("vim_mode")
18734 == Some(&serde_json::Value::Bool(true))
18735}
18736
18737// Consider user intent and default settings
18738fn choose_completion_range(
18739 completion: &Completion,
18740 intent: CompletionIntent,
18741 buffer: &Entity<Buffer>,
18742 cx: &mut Context<Editor>,
18743) -> Range<usize> {
18744 fn should_replace(
18745 completion: &Completion,
18746 insert_range: &Range<text::Anchor>,
18747 intent: CompletionIntent,
18748 completion_mode_setting: LspInsertMode,
18749 buffer: &Buffer,
18750 ) -> bool {
18751 // specific actions take precedence over settings
18752 match intent {
18753 CompletionIntent::CompleteWithInsert => return false,
18754 CompletionIntent::CompleteWithReplace => return true,
18755 CompletionIntent::Complete | CompletionIntent::Compose => {}
18756 }
18757
18758 match completion_mode_setting {
18759 LspInsertMode::Insert => false,
18760 LspInsertMode::Replace => true,
18761 LspInsertMode::ReplaceSubsequence => {
18762 let mut text_to_replace = buffer.chars_for_range(
18763 buffer.anchor_before(completion.replace_range.start)
18764 ..buffer.anchor_after(completion.replace_range.end),
18765 );
18766 let mut completion_text = completion.new_text.chars();
18767
18768 // is `text_to_replace` a subsequence of `completion_text`
18769 text_to_replace
18770 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
18771 }
18772 LspInsertMode::ReplaceSuffix => {
18773 let range_after_cursor = insert_range.end..completion.replace_range.end;
18774
18775 let text_after_cursor = buffer
18776 .text_for_range(
18777 buffer.anchor_before(range_after_cursor.start)
18778 ..buffer.anchor_after(range_after_cursor.end),
18779 )
18780 .collect::<String>();
18781 completion.new_text.ends_with(&text_after_cursor)
18782 }
18783 }
18784 }
18785
18786 let buffer = buffer.read(cx);
18787
18788 if let CompletionSource::Lsp {
18789 insert_range: Some(insert_range),
18790 ..
18791 } = &completion.source
18792 {
18793 let completion_mode_setting =
18794 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
18795 .completions
18796 .lsp_insert_mode;
18797
18798 if !should_replace(
18799 completion,
18800 &insert_range,
18801 intent,
18802 completion_mode_setting,
18803 buffer,
18804 ) {
18805 return insert_range.to_offset(buffer);
18806 }
18807 }
18808
18809 completion.replace_range.to_offset(buffer)
18810}
18811
18812fn insert_extra_newline_brackets(
18813 buffer: &MultiBufferSnapshot,
18814 range: Range<usize>,
18815 language: &language::LanguageScope,
18816) -> bool {
18817 let leading_whitespace_len = buffer
18818 .reversed_chars_at(range.start)
18819 .take_while(|c| c.is_whitespace() && *c != '\n')
18820 .map(|c| c.len_utf8())
18821 .sum::<usize>();
18822 let trailing_whitespace_len = buffer
18823 .chars_at(range.end)
18824 .take_while(|c| c.is_whitespace() && *c != '\n')
18825 .map(|c| c.len_utf8())
18826 .sum::<usize>();
18827 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
18828
18829 language.brackets().any(|(pair, enabled)| {
18830 let pair_start = pair.start.trim_end();
18831 let pair_end = pair.end.trim_start();
18832
18833 enabled
18834 && pair.newline
18835 && buffer.contains_str_at(range.end, pair_end)
18836 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
18837 })
18838}
18839
18840fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
18841 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
18842 [(buffer, range, _)] => (*buffer, range.clone()),
18843 _ => return false,
18844 };
18845 let pair = {
18846 let mut result: Option<BracketMatch> = None;
18847
18848 for pair in buffer
18849 .all_bracket_ranges(range.clone())
18850 .filter(move |pair| {
18851 pair.open_range.start <= range.start && pair.close_range.end >= range.end
18852 })
18853 {
18854 let len = pair.close_range.end - pair.open_range.start;
18855
18856 if let Some(existing) = &result {
18857 let existing_len = existing.close_range.end - existing.open_range.start;
18858 if len > existing_len {
18859 continue;
18860 }
18861 }
18862
18863 result = Some(pair);
18864 }
18865
18866 result
18867 };
18868 let Some(pair) = pair else {
18869 return false;
18870 };
18871 pair.newline_only
18872 && buffer
18873 .chars_for_range(pair.open_range.end..range.start)
18874 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
18875 .all(|c| c.is_whitespace() && c != '\n')
18876}
18877
18878fn get_uncommitted_diff_for_buffer(
18879 project: &Entity<Project>,
18880 buffers: impl IntoIterator<Item = Entity<Buffer>>,
18881 buffer: Entity<MultiBuffer>,
18882 cx: &mut App,
18883) -> Task<()> {
18884 let mut tasks = Vec::new();
18885 project.update(cx, |project, cx| {
18886 for buffer in buffers {
18887 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
18888 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
18889 }
18890 }
18891 });
18892 cx.spawn(async move |cx| {
18893 let diffs = future::join_all(tasks).await;
18894 buffer
18895 .update(cx, |buffer, cx| {
18896 for diff in diffs.into_iter().flatten() {
18897 buffer.add_diff(diff, cx);
18898 }
18899 })
18900 .ok();
18901 })
18902}
18903
18904fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
18905 let tab_size = tab_size.get() as usize;
18906 let mut width = offset;
18907
18908 for ch in text.chars() {
18909 width += if ch == '\t' {
18910 tab_size - (width % tab_size)
18911 } else {
18912 1
18913 };
18914 }
18915
18916 width - offset
18917}
18918
18919#[cfg(test)]
18920mod tests {
18921 use super::*;
18922
18923 #[test]
18924 fn test_string_size_with_expanded_tabs() {
18925 let nz = |val| NonZeroU32::new(val).unwrap();
18926 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
18927 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
18928 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
18929 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
18930 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
18931 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
18932 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
18933 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
18934 }
18935}
18936
18937/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
18938struct WordBreakingTokenizer<'a> {
18939 input: &'a str,
18940}
18941
18942impl<'a> WordBreakingTokenizer<'a> {
18943 fn new(input: &'a str) -> Self {
18944 Self { input }
18945 }
18946}
18947
18948fn is_char_ideographic(ch: char) -> bool {
18949 use unicode_script::Script::*;
18950 use unicode_script::UnicodeScript;
18951 matches!(ch.script(), Han | Tangut | Yi)
18952}
18953
18954fn is_grapheme_ideographic(text: &str) -> bool {
18955 text.chars().any(is_char_ideographic)
18956}
18957
18958fn is_grapheme_whitespace(text: &str) -> bool {
18959 text.chars().any(|x| x.is_whitespace())
18960}
18961
18962fn should_stay_with_preceding_ideograph(text: &str) -> bool {
18963 text.chars().next().map_or(false, |ch| {
18964 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
18965 })
18966}
18967
18968#[derive(PartialEq, Eq, Debug, Clone, Copy)]
18969enum WordBreakToken<'a> {
18970 Word { token: &'a str, grapheme_len: usize },
18971 InlineWhitespace { token: &'a str, grapheme_len: usize },
18972 Newline,
18973}
18974
18975impl<'a> Iterator for WordBreakingTokenizer<'a> {
18976 /// Yields a span, the count of graphemes in the token, and whether it was
18977 /// whitespace. Note that it also breaks at word boundaries.
18978 type Item = WordBreakToken<'a>;
18979
18980 fn next(&mut self) -> Option<Self::Item> {
18981 use unicode_segmentation::UnicodeSegmentation;
18982 if self.input.is_empty() {
18983 return None;
18984 }
18985
18986 let mut iter = self.input.graphemes(true).peekable();
18987 let mut offset = 0;
18988 let mut grapheme_len = 0;
18989 if let Some(first_grapheme) = iter.next() {
18990 let is_newline = first_grapheme == "\n";
18991 let is_whitespace = is_grapheme_whitespace(first_grapheme);
18992 offset += first_grapheme.len();
18993 grapheme_len += 1;
18994 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
18995 if let Some(grapheme) = iter.peek().copied() {
18996 if should_stay_with_preceding_ideograph(grapheme) {
18997 offset += grapheme.len();
18998 grapheme_len += 1;
18999 }
19000 }
19001 } else {
19002 let mut words = self.input[offset..].split_word_bound_indices().peekable();
19003 let mut next_word_bound = words.peek().copied();
19004 if next_word_bound.map_or(false, |(i, _)| i == 0) {
19005 next_word_bound = words.next();
19006 }
19007 while let Some(grapheme) = iter.peek().copied() {
19008 if next_word_bound.map_or(false, |(i, _)| i == offset) {
19009 break;
19010 };
19011 if is_grapheme_whitespace(grapheme) != is_whitespace
19012 || (grapheme == "\n") != is_newline
19013 {
19014 break;
19015 };
19016 offset += grapheme.len();
19017 grapheme_len += 1;
19018 iter.next();
19019 }
19020 }
19021 let token = &self.input[..offset];
19022 self.input = &self.input[offset..];
19023 if token == "\n" {
19024 Some(WordBreakToken::Newline)
19025 } else if is_whitespace {
19026 Some(WordBreakToken::InlineWhitespace {
19027 token,
19028 grapheme_len,
19029 })
19030 } else {
19031 Some(WordBreakToken::Word {
19032 token,
19033 grapheme_len,
19034 })
19035 }
19036 } else {
19037 None
19038 }
19039 }
19040}
19041
19042#[test]
19043fn test_word_breaking_tokenizer() {
19044 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
19045 ("", &[]),
19046 (" ", &[whitespace(" ", 2)]),
19047 ("Ʒ", &[word("Ʒ", 1)]),
19048 ("Ǽ", &[word("Ǽ", 1)]),
19049 ("⋑", &[word("⋑", 1)]),
19050 ("⋑⋑", &[word("⋑⋑", 2)]),
19051 (
19052 "原理,进而",
19053 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
19054 ),
19055 (
19056 "hello world",
19057 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
19058 ),
19059 (
19060 "hello, world",
19061 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
19062 ),
19063 (
19064 " hello world",
19065 &[
19066 whitespace(" ", 2),
19067 word("hello", 5),
19068 whitespace(" ", 1),
19069 word("world", 5),
19070 ],
19071 ),
19072 (
19073 "这是什么 \n 钢笔",
19074 &[
19075 word("这", 1),
19076 word("是", 1),
19077 word("什", 1),
19078 word("么", 1),
19079 whitespace(" ", 1),
19080 newline(),
19081 whitespace(" ", 1),
19082 word("钢", 1),
19083 word("笔", 1),
19084 ],
19085 ),
19086 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
19087 ];
19088
19089 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19090 WordBreakToken::Word {
19091 token,
19092 grapheme_len,
19093 }
19094 }
19095
19096 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
19097 WordBreakToken::InlineWhitespace {
19098 token,
19099 grapheme_len,
19100 }
19101 }
19102
19103 fn newline() -> WordBreakToken<'static> {
19104 WordBreakToken::Newline
19105 }
19106
19107 for (input, result) in tests {
19108 assert_eq!(
19109 WordBreakingTokenizer::new(input)
19110 .collect::<Vec<_>>()
19111 .as_slice(),
19112 *result,
19113 );
19114 }
19115}
19116
19117fn wrap_with_prefix(
19118 line_prefix: String,
19119 unwrapped_text: String,
19120 wrap_column: usize,
19121 tab_size: NonZeroU32,
19122 preserve_existing_whitespace: bool,
19123) -> String {
19124 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
19125 let mut wrapped_text = String::new();
19126 let mut current_line = line_prefix.clone();
19127
19128 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
19129 let mut current_line_len = line_prefix_len;
19130 let mut in_whitespace = false;
19131 for token in tokenizer {
19132 let have_preceding_whitespace = in_whitespace;
19133 match token {
19134 WordBreakToken::Word {
19135 token,
19136 grapheme_len,
19137 } => {
19138 in_whitespace = false;
19139 if current_line_len + grapheme_len > wrap_column
19140 && current_line_len != line_prefix_len
19141 {
19142 wrapped_text.push_str(current_line.trim_end());
19143 wrapped_text.push('\n');
19144 current_line.truncate(line_prefix.len());
19145 current_line_len = line_prefix_len;
19146 }
19147 current_line.push_str(token);
19148 current_line_len += grapheme_len;
19149 }
19150 WordBreakToken::InlineWhitespace {
19151 mut token,
19152 mut grapheme_len,
19153 } => {
19154 in_whitespace = true;
19155 if have_preceding_whitespace && !preserve_existing_whitespace {
19156 continue;
19157 }
19158 if !preserve_existing_whitespace {
19159 token = " ";
19160 grapheme_len = 1;
19161 }
19162 if current_line_len + grapheme_len > wrap_column {
19163 wrapped_text.push_str(current_line.trim_end());
19164 wrapped_text.push('\n');
19165 current_line.truncate(line_prefix.len());
19166 current_line_len = line_prefix_len;
19167 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
19168 current_line.push_str(token);
19169 current_line_len += grapheme_len;
19170 }
19171 }
19172 WordBreakToken::Newline => {
19173 in_whitespace = true;
19174 if preserve_existing_whitespace {
19175 wrapped_text.push_str(current_line.trim_end());
19176 wrapped_text.push('\n');
19177 current_line.truncate(line_prefix.len());
19178 current_line_len = line_prefix_len;
19179 } else if have_preceding_whitespace {
19180 continue;
19181 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
19182 {
19183 wrapped_text.push_str(current_line.trim_end());
19184 wrapped_text.push('\n');
19185 current_line.truncate(line_prefix.len());
19186 current_line_len = line_prefix_len;
19187 } else if current_line_len != line_prefix_len {
19188 current_line.push(' ');
19189 current_line_len += 1;
19190 }
19191 }
19192 }
19193 }
19194
19195 if !current_line.is_empty() {
19196 wrapped_text.push_str(¤t_line);
19197 }
19198 wrapped_text
19199}
19200
19201#[test]
19202fn test_wrap_with_prefix() {
19203 assert_eq!(
19204 wrap_with_prefix(
19205 "# ".to_string(),
19206 "abcdefg".to_string(),
19207 4,
19208 NonZeroU32::new(4).unwrap(),
19209 false,
19210 ),
19211 "# abcdefg"
19212 );
19213 assert_eq!(
19214 wrap_with_prefix(
19215 "".to_string(),
19216 "\thello world".to_string(),
19217 8,
19218 NonZeroU32::new(4).unwrap(),
19219 false,
19220 ),
19221 "hello\nworld"
19222 );
19223 assert_eq!(
19224 wrap_with_prefix(
19225 "// ".to_string(),
19226 "xx \nyy zz aa bb cc".to_string(),
19227 12,
19228 NonZeroU32::new(4).unwrap(),
19229 false,
19230 ),
19231 "// xx yy zz\n// aa bb cc"
19232 );
19233 assert_eq!(
19234 wrap_with_prefix(
19235 String::new(),
19236 "这是什么 \n 钢笔".to_string(),
19237 3,
19238 NonZeroU32::new(4).unwrap(),
19239 false,
19240 ),
19241 "这是什\n么 钢\n笔"
19242 );
19243}
19244
19245pub trait CollaborationHub {
19246 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19247 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19248 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19249}
19250
19251impl CollaborationHub for Entity<Project> {
19252 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19253 self.read(cx).collaborators()
19254 }
19255
19256 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19257 self.read(cx).user_store().read(cx).participant_indices()
19258 }
19259
19260 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19261 let this = self.read(cx);
19262 let user_ids = this.collaborators().values().map(|c| c.user_id);
19263 this.user_store().read_with(cx, |user_store, cx| {
19264 user_store.participant_names(user_ids, cx)
19265 })
19266 }
19267}
19268
19269pub trait SemanticsProvider {
19270 fn hover(
19271 &self,
19272 buffer: &Entity<Buffer>,
19273 position: text::Anchor,
19274 cx: &mut App,
19275 ) -> Option<Task<Vec<project::Hover>>>;
19276
19277 fn inline_values(
19278 &self,
19279 buffer_handle: Entity<Buffer>,
19280 range: Range<text::Anchor>,
19281 cx: &mut App,
19282 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19283
19284 fn inlay_hints(
19285 &self,
19286 buffer_handle: Entity<Buffer>,
19287 range: Range<text::Anchor>,
19288 cx: &mut App,
19289 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19290
19291 fn resolve_inlay_hint(
19292 &self,
19293 hint: InlayHint,
19294 buffer_handle: Entity<Buffer>,
19295 server_id: LanguageServerId,
19296 cx: &mut App,
19297 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19298
19299 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19300
19301 fn document_highlights(
19302 &self,
19303 buffer: &Entity<Buffer>,
19304 position: text::Anchor,
19305 cx: &mut App,
19306 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19307
19308 fn definitions(
19309 &self,
19310 buffer: &Entity<Buffer>,
19311 position: text::Anchor,
19312 kind: GotoDefinitionKind,
19313 cx: &mut App,
19314 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19315
19316 fn range_for_rename(
19317 &self,
19318 buffer: &Entity<Buffer>,
19319 position: text::Anchor,
19320 cx: &mut App,
19321 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19322
19323 fn perform_rename(
19324 &self,
19325 buffer: &Entity<Buffer>,
19326 position: text::Anchor,
19327 new_name: String,
19328 cx: &mut App,
19329 ) -> Option<Task<Result<ProjectTransaction>>>;
19330}
19331
19332pub trait CompletionProvider {
19333 fn completions(
19334 &self,
19335 excerpt_id: ExcerptId,
19336 buffer: &Entity<Buffer>,
19337 buffer_position: text::Anchor,
19338 trigger: CompletionContext,
19339 window: &mut Window,
19340 cx: &mut Context<Editor>,
19341 ) -> Task<Result<Option<Vec<Completion>>>>;
19342
19343 fn resolve_completions(
19344 &self,
19345 buffer: Entity<Buffer>,
19346 completion_indices: Vec<usize>,
19347 completions: Rc<RefCell<Box<[Completion]>>>,
19348 cx: &mut Context<Editor>,
19349 ) -> Task<Result<bool>>;
19350
19351 fn apply_additional_edits_for_completion(
19352 &self,
19353 _buffer: Entity<Buffer>,
19354 _completions: Rc<RefCell<Box<[Completion]>>>,
19355 _completion_index: usize,
19356 _push_to_history: bool,
19357 _cx: &mut Context<Editor>,
19358 ) -> Task<Result<Option<language::Transaction>>> {
19359 Task::ready(Ok(None))
19360 }
19361
19362 fn is_completion_trigger(
19363 &self,
19364 buffer: &Entity<Buffer>,
19365 position: language::Anchor,
19366 text: &str,
19367 trigger_in_words: bool,
19368 cx: &mut Context<Editor>,
19369 ) -> bool;
19370
19371 fn sort_completions(&self) -> bool {
19372 true
19373 }
19374
19375 fn filter_completions(&self) -> bool {
19376 true
19377 }
19378}
19379
19380pub trait CodeActionProvider {
19381 fn id(&self) -> Arc<str>;
19382
19383 fn code_actions(
19384 &self,
19385 buffer: &Entity<Buffer>,
19386 range: Range<text::Anchor>,
19387 window: &mut Window,
19388 cx: &mut App,
19389 ) -> Task<Result<Vec<CodeAction>>>;
19390
19391 fn apply_code_action(
19392 &self,
19393 buffer_handle: Entity<Buffer>,
19394 action: CodeAction,
19395 excerpt_id: ExcerptId,
19396 push_to_history: bool,
19397 window: &mut Window,
19398 cx: &mut App,
19399 ) -> Task<Result<ProjectTransaction>>;
19400}
19401
19402impl CodeActionProvider for Entity<Project> {
19403 fn id(&self) -> Arc<str> {
19404 "project".into()
19405 }
19406
19407 fn code_actions(
19408 &self,
19409 buffer: &Entity<Buffer>,
19410 range: Range<text::Anchor>,
19411 _window: &mut Window,
19412 cx: &mut App,
19413 ) -> Task<Result<Vec<CodeAction>>> {
19414 self.update(cx, |project, cx| {
19415 let code_lens = project.code_lens(buffer, range.clone(), cx);
19416 let code_actions = project.code_actions(buffer, range, None, cx);
19417 cx.background_spawn(async move {
19418 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19419 Ok(code_lens
19420 .context("code lens fetch")?
19421 .into_iter()
19422 .chain(code_actions.context("code action fetch")?)
19423 .collect())
19424 })
19425 })
19426 }
19427
19428 fn apply_code_action(
19429 &self,
19430 buffer_handle: Entity<Buffer>,
19431 action: CodeAction,
19432 _excerpt_id: ExcerptId,
19433 push_to_history: bool,
19434 _window: &mut Window,
19435 cx: &mut App,
19436 ) -> Task<Result<ProjectTransaction>> {
19437 self.update(cx, |project, cx| {
19438 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19439 })
19440 }
19441}
19442
19443fn snippet_completions(
19444 project: &Project,
19445 buffer: &Entity<Buffer>,
19446 buffer_position: text::Anchor,
19447 cx: &mut App,
19448) -> Task<Result<Vec<Completion>>> {
19449 let languages = buffer.read(cx).languages_at(buffer_position);
19450 let snippet_store = project.snippets().read(cx);
19451
19452 let scopes: Vec<_> = languages
19453 .iter()
19454 .filter_map(|language| {
19455 let language_name = language.lsp_id();
19456 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19457
19458 if snippets.is_empty() {
19459 None
19460 } else {
19461 Some((language.default_scope(), snippets))
19462 }
19463 })
19464 .collect();
19465
19466 if scopes.is_empty() {
19467 return Task::ready(Ok(vec![]));
19468 }
19469
19470 let snapshot = buffer.read(cx).text_snapshot();
19471 let chars: String = snapshot
19472 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19473 .collect();
19474 let executor = cx.background_executor().clone();
19475
19476 cx.background_spawn(async move {
19477 let mut all_results: Vec<Completion> = Vec::new();
19478 for (scope, snippets) in scopes.into_iter() {
19479 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19480 let mut last_word = chars
19481 .chars()
19482 .take_while(|c| classifier.is_word(*c))
19483 .collect::<String>();
19484 last_word = last_word.chars().rev().collect();
19485
19486 if last_word.is_empty() {
19487 return Ok(vec![]);
19488 }
19489
19490 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19491 let to_lsp = |point: &text::Anchor| {
19492 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19493 point_to_lsp(end)
19494 };
19495 let lsp_end = to_lsp(&buffer_position);
19496
19497 let candidates = snippets
19498 .iter()
19499 .enumerate()
19500 .flat_map(|(ix, snippet)| {
19501 snippet
19502 .prefix
19503 .iter()
19504 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19505 })
19506 .collect::<Vec<StringMatchCandidate>>();
19507
19508 let mut matches = fuzzy::match_strings(
19509 &candidates,
19510 &last_word,
19511 last_word.chars().any(|c| c.is_uppercase()),
19512 100,
19513 &Default::default(),
19514 executor.clone(),
19515 )
19516 .await;
19517
19518 // Remove all candidates where the query's start does not match the start of any word in the candidate
19519 if let Some(query_start) = last_word.chars().next() {
19520 matches.retain(|string_match| {
19521 split_words(&string_match.string).any(|word| {
19522 // Check that the first codepoint of the word as lowercase matches the first
19523 // codepoint of the query as lowercase
19524 word.chars()
19525 .flat_map(|codepoint| codepoint.to_lowercase())
19526 .zip(query_start.to_lowercase())
19527 .all(|(word_cp, query_cp)| word_cp == query_cp)
19528 })
19529 });
19530 }
19531
19532 let matched_strings = matches
19533 .into_iter()
19534 .map(|m| m.string)
19535 .collect::<HashSet<_>>();
19536
19537 let mut result: Vec<Completion> = snippets
19538 .iter()
19539 .filter_map(|snippet| {
19540 let matching_prefix = snippet
19541 .prefix
19542 .iter()
19543 .find(|prefix| matched_strings.contains(*prefix))?;
19544 let start = as_offset - last_word.len();
19545 let start = snapshot.anchor_before(start);
19546 let range = start..buffer_position;
19547 let lsp_start = to_lsp(&start);
19548 let lsp_range = lsp::Range {
19549 start: lsp_start,
19550 end: lsp_end,
19551 };
19552 Some(Completion {
19553 replace_range: range,
19554 new_text: snippet.body.clone(),
19555 source: CompletionSource::Lsp {
19556 insert_range: None,
19557 server_id: LanguageServerId(usize::MAX),
19558 resolved: true,
19559 lsp_completion: Box::new(lsp::CompletionItem {
19560 label: snippet.prefix.first().unwrap().clone(),
19561 kind: Some(CompletionItemKind::SNIPPET),
19562 label_details: snippet.description.as_ref().map(|description| {
19563 lsp::CompletionItemLabelDetails {
19564 detail: Some(description.clone()),
19565 description: None,
19566 }
19567 }),
19568 insert_text_format: Some(InsertTextFormat::SNIPPET),
19569 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19570 lsp::InsertReplaceEdit {
19571 new_text: snippet.body.clone(),
19572 insert: lsp_range,
19573 replace: lsp_range,
19574 },
19575 )),
19576 filter_text: Some(snippet.body.clone()),
19577 sort_text: Some(char::MAX.to_string()),
19578 ..lsp::CompletionItem::default()
19579 }),
19580 lsp_defaults: None,
19581 },
19582 label: CodeLabel {
19583 text: matching_prefix.clone(),
19584 runs: Vec::new(),
19585 filter_range: 0..matching_prefix.len(),
19586 },
19587 icon_path: None,
19588 documentation: snippet.description.clone().map(|description| {
19589 CompletionDocumentation::SingleLine(description.into())
19590 }),
19591 insert_text_mode: None,
19592 confirm: None,
19593 })
19594 })
19595 .collect();
19596
19597 all_results.append(&mut result);
19598 }
19599
19600 Ok(all_results)
19601 })
19602}
19603
19604impl CompletionProvider for Entity<Project> {
19605 fn completions(
19606 &self,
19607 _excerpt_id: ExcerptId,
19608 buffer: &Entity<Buffer>,
19609 buffer_position: text::Anchor,
19610 options: CompletionContext,
19611 _window: &mut Window,
19612 cx: &mut Context<Editor>,
19613 ) -> Task<Result<Option<Vec<Completion>>>> {
19614 self.update(cx, |project, cx| {
19615 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19616 let project_completions = project.completions(buffer, buffer_position, options, cx);
19617 cx.background_spawn(async move {
19618 let snippets_completions = snippets.await?;
19619 match project_completions.await? {
19620 Some(mut completions) => {
19621 completions.extend(snippets_completions);
19622 Ok(Some(completions))
19623 }
19624 None => {
19625 if snippets_completions.is_empty() {
19626 Ok(None)
19627 } else {
19628 Ok(Some(snippets_completions))
19629 }
19630 }
19631 }
19632 })
19633 })
19634 }
19635
19636 fn resolve_completions(
19637 &self,
19638 buffer: Entity<Buffer>,
19639 completion_indices: Vec<usize>,
19640 completions: Rc<RefCell<Box<[Completion]>>>,
19641 cx: &mut Context<Editor>,
19642 ) -> Task<Result<bool>> {
19643 self.update(cx, |project, cx| {
19644 project.lsp_store().update(cx, |lsp_store, cx| {
19645 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19646 })
19647 })
19648 }
19649
19650 fn apply_additional_edits_for_completion(
19651 &self,
19652 buffer: Entity<Buffer>,
19653 completions: Rc<RefCell<Box<[Completion]>>>,
19654 completion_index: usize,
19655 push_to_history: bool,
19656 cx: &mut Context<Editor>,
19657 ) -> Task<Result<Option<language::Transaction>>> {
19658 self.update(cx, |project, cx| {
19659 project.lsp_store().update(cx, |lsp_store, cx| {
19660 lsp_store.apply_additional_edits_for_completion(
19661 buffer,
19662 completions,
19663 completion_index,
19664 push_to_history,
19665 cx,
19666 )
19667 })
19668 })
19669 }
19670
19671 fn is_completion_trigger(
19672 &self,
19673 buffer: &Entity<Buffer>,
19674 position: language::Anchor,
19675 text: &str,
19676 trigger_in_words: bool,
19677 cx: &mut Context<Editor>,
19678 ) -> bool {
19679 let mut chars = text.chars();
19680 let char = if let Some(char) = chars.next() {
19681 char
19682 } else {
19683 return false;
19684 };
19685 if chars.next().is_some() {
19686 return false;
19687 }
19688
19689 let buffer = buffer.read(cx);
19690 let snapshot = buffer.snapshot();
19691 if !snapshot.settings_at(position, cx).show_completions_on_input {
19692 return false;
19693 }
19694 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19695 if trigger_in_words && classifier.is_word(char) {
19696 return true;
19697 }
19698
19699 buffer.completion_triggers().contains(text)
19700 }
19701}
19702
19703impl SemanticsProvider for Entity<Project> {
19704 fn hover(
19705 &self,
19706 buffer: &Entity<Buffer>,
19707 position: text::Anchor,
19708 cx: &mut App,
19709 ) -> Option<Task<Vec<project::Hover>>> {
19710 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19711 }
19712
19713 fn document_highlights(
19714 &self,
19715 buffer: &Entity<Buffer>,
19716 position: text::Anchor,
19717 cx: &mut App,
19718 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19719 Some(self.update(cx, |project, cx| {
19720 project.document_highlights(buffer, position, cx)
19721 }))
19722 }
19723
19724 fn definitions(
19725 &self,
19726 buffer: &Entity<Buffer>,
19727 position: text::Anchor,
19728 kind: GotoDefinitionKind,
19729 cx: &mut App,
19730 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19731 Some(self.update(cx, |project, cx| match kind {
19732 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19733 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19734 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19735 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
19736 }))
19737 }
19738
19739 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
19740 // TODO: make this work for remote projects
19741 self.update(cx, |project, cx| {
19742 if project
19743 .active_debug_session(cx)
19744 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
19745 {
19746 return true;
19747 }
19748
19749 buffer.update(cx, |buffer, cx| {
19750 project.any_language_server_supports_inlay_hints(buffer, cx)
19751 })
19752 })
19753 }
19754
19755 fn inline_values(
19756 &self,
19757 buffer_handle: Entity<Buffer>,
19758 range: Range<text::Anchor>,
19759 cx: &mut App,
19760 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19761 self.update(cx, |project, cx| {
19762 let (session, active_stack_frame) = project.active_debug_session(cx)?;
19763
19764 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
19765 })
19766 }
19767
19768 fn inlay_hints(
19769 &self,
19770 buffer_handle: Entity<Buffer>,
19771 range: Range<text::Anchor>,
19772 cx: &mut App,
19773 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
19774 Some(self.update(cx, |project, cx| {
19775 project.inlay_hints(buffer_handle, range, cx)
19776 }))
19777 }
19778
19779 fn resolve_inlay_hint(
19780 &self,
19781 hint: InlayHint,
19782 buffer_handle: Entity<Buffer>,
19783 server_id: LanguageServerId,
19784 cx: &mut App,
19785 ) -> Option<Task<anyhow::Result<InlayHint>>> {
19786 Some(self.update(cx, |project, cx| {
19787 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
19788 }))
19789 }
19790
19791 fn range_for_rename(
19792 &self,
19793 buffer: &Entity<Buffer>,
19794 position: text::Anchor,
19795 cx: &mut App,
19796 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
19797 Some(self.update(cx, |project, cx| {
19798 let buffer = buffer.clone();
19799 let task = project.prepare_rename(buffer.clone(), position, cx);
19800 cx.spawn(async move |_, cx| {
19801 Ok(match task.await? {
19802 PrepareRenameResponse::Success(range) => Some(range),
19803 PrepareRenameResponse::InvalidPosition => None,
19804 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
19805 // Fallback on using TreeSitter info to determine identifier range
19806 buffer.update(cx, |buffer, _| {
19807 let snapshot = buffer.snapshot();
19808 let (range, kind) = snapshot.surrounding_word(position);
19809 if kind != Some(CharKind::Word) {
19810 return None;
19811 }
19812 Some(
19813 snapshot.anchor_before(range.start)
19814 ..snapshot.anchor_after(range.end),
19815 )
19816 })?
19817 }
19818 })
19819 })
19820 }))
19821 }
19822
19823 fn perform_rename(
19824 &self,
19825 buffer: &Entity<Buffer>,
19826 position: text::Anchor,
19827 new_name: String,
19828 cx: &mut App,
19829 ) -> Option<Task<Result<ProjectTransaction>>> {
19830 Some(self.update(cx, |project, cx| {
19831 project.perform_rename(buffer.clone(), position, new_name, cx)
19832 }))
19833 }
19834}
19835
19836fn inlay_hint_settings(
19837 location: Anchor,
19838 snapshot: &MultiBufferSnapshot,
19839 cx: &mut Context<Editor>,
19840) -> InlayHintSettings {
19841 let file = snapshot.file_at(location);
19842 let language = snapshot.language_at(location).map(|l| l.name());
19843 language_settings(language, file, cx).inlay_hints
19844}
19845
19846fn consume_contiguous_rows(
19847 contiguous_row_selections: &mut Vec<Selection<Point>>,
19848 selection: &Selection<Point>,
19849 display_map: &DisplaySnapshot,
19850 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
19851) -> (MultiBufferRow, MultiBufferRow) {
19852 contiguous_row_selections.push(selection.clone());
19853 let start_row = MultiBufferRow(selection.start.row);
19854 let mut end_row = ending_row(selection, display_map);
19855
19856 while let Some(next_selection) = selections.peek() {
19857 if next_selection.start.row <= end_row.0 {
19858 end_row = ending_row(next_selection, display_map);
19859 contiguous_row_selections.push(selections.next().unwrap().clone());
19860 } else {
19861 break;
19862 }
19863 }
19864 (start_row, end_row)
19865}
19866
19867fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
19868 if next_selection.end.column > 0 || next_selection.is_empty() {
19869 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
19870 } else {
19871 MultiBufferRow(next_selection.end.row)
19872 }
19873}
19874
19875impl EditorSnapshot {
19876 pub fn remote_selections_in_range<'a>(
19877 &'a self,
19878 range: &'a Range<Anchor>,
19879 collaboration_hub: &dyn CollaborationHub,
19880 cx: &'a App,
19881 ) -> impl 'a + Iterator<Item = RemoteSelection> {
19882 let participant_names = collaboration_hub.user_names(cx);
19883 let participant_indices = collaboration_hub.user_participant_indices(cx);
19884 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
19885 let collaborators_by_replica_id = collaborators_by_peer_id
19886 .iter()
19887 .map(|(_, collaborator)| (collaborator.replica_id, collaborator))
19888 .collect::<HashMap<_, _>>();
19889 self.buffer_snapshot
19890 .selections_in_range(range, false)
19891 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
19892 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
19893 let participant_index = participant_indices.get(&collaborator.user_id).copied();
19894 let user_name = participant_names.get(&collaborator.user_id).cloned();
19895 Some(RemoteSelection {
19896 replica_id,
19897 selection,
19898 cursor_shape,
19899 line_mode,
19900 participant_index,
19901 peer_id: collaborator.peer_id,
19902 user_name,
19903 })
19904 })
19905 }
19906
19907 pub fn hunks_for_ranges(
19908 &self,
19909 ranges: impl IntoIterator<Item = Range<Point>>,
19910 ) -> Vec<MultiBufferDiffHunk> {
19911 let mut hunks = Vec::new();
19912 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
19913 HashMap::default();
19914 for query_range in ranges {
19915 let query_rows =
19916 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
19917 for hunk in self.buffer_snapshot.diff_hunks_in_range(
19918 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
19919 ) {
19920 // Include deleted hunks that are adjacent to the query range, because
19921 // otherwise they would be missed.
19922 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
19923 if hunk.status().is_deleted() {
19924 intersects_range |= hunk.row_range.start == query_rows.end;
19925 intersects_range |= hunk.row_range.end == query_rows.start;
19926 }
19927 if intersects_range {
19928 if !processed_buffer_rows
19929 .entry(hunk.buffer_id)
19930 .or_default()
19931 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
19932 {
19933 continue;
19934 }
19935 hunks.push(hunk);
19936 }
19937 }
19938 }
19939
19940 hunks
19941 }
19942
19943 fn display_diff_hunks_for_rows<'a>(
19944 &'a self,
19945 display_rows: Range<DisplayRow>,
19946 folded_buffers: &'a HashSet<BufferId>,
19947 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
19948 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
19949 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
19950
19951 self.buffer_snapshot
19952 .diff_hunks_in_range(buffer_start..buffer_end)
19953 .filter_map(|hunk| {
19954 if folded_buffers.contains(&hunk.buffer_id) {
19955 return None;
19956 }
19957
19958 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
19959 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
19960
19961 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
19962 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
19963
19964 let display_hunk = if hunk_display_start.column() != 0 {
19965 DisplayDiffHunk::Folded {
19966 display_row: hunk_display_start.row(),
19967 }
19968 } else {
19969 let mut end_row = hunk_display_end.row();
19970 if hunk_display_end.column() > 0 {
19971 end_row.0 += 1;
19972 }
19973 let is_created_file = hunk.is_created_file();
19974 DisplayDiffHunk::Unfolded {
19975 status: hunk.status(),
19976 diff_base_byte_range: hunk.diff_base_byte_range,
19977 display_row_range: hunk_display_start.row()..end_row,
19978 multi_buffer_range: Anchor::range_in_buffer(
19979 hunk.excerpt_id,
19980 hunk.buffer_id,
19981 hunk.buffer_range,
19982 ),
19983 is_created_file,
19984 }
19985 };
19986
19987 Some(display_hunk)
19988 })
19989 }
19990
19991 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
19992 self.display_snapshot.buffer_snapshot.language_at(position)
19993 }
19994
19995 pub fn is_focused(&self) -> bool {
19996 self.is_focused
19997 }
19998
19999 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20000 self.placeholder_text.as_ref()
20001 }
20002
20003 pub fn scroll_position(&self) -> gpui::Point<f32> {
20004 self.scroll_anchor.scroll_position(&self.display_snapshot)
20005 }
20006
20007 fn gutter_dimensions(
20008 &self,
20009 font_id: FontId,
20010 font_size: Pixels,
20011 max_line_number_width: Pixels,
20012 cx: &App,
20013 ) -> Option<GutterDimensions> {
20014 if !self.show_gutter {
20015 return None;
20016 }
20017
20018 let descent = cx.text_system().descent(font_id, font_size);
20019 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20020 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20021
20022 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20023 matches!(
20024 ProjectSettings::get_global(cx).git.git_gutter,
20025 Some(GitGutterSetting::TrackedFiles)
20026 )
20027 });
20028 let gutter_settings = EditorSettings::get_global(cx).gutter;
20029 let show_line_numbers = self
20030 .show_line_numbers
20031 .unwrap_or(gutter_settings.line_numbers);
20032 let line_gutter_width = if show_line_numbers {
20033 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20034 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20035 max_line_number_width.max(min_width_for_number_on_gutter)
20036 } else {
20037 0.0.into()
20038 };
20039
20040 let show_code_actions = self
20041 .show_code_actions
20042 .unwrap_or(gutter_settings.code_actions);
20043
20044 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20045 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20046
20047 let git_blame_entries_width =
20048 self.git_blame_gutter_max_author_length
20049 .map(|max_author_length| {
20050 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20051 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20052
20053 /// The number of characters to dedicate to gaps and margins.
20054 const SPACING_WIDTH: usize = 4;
20055
20056 let max_char_count = max_author_length.min(renderer.max_author_length())
20057 + ::git::SHORT_SHA_LENGTH
20058 + MAX_RELATIVE_TIMESTAMP.len()
20059 + SPACING_WIDTH;
20060
20061 em_advance * max_char_count
20062 });
20063
20064 let is_singleton = self.buffer_snapshot.is_singleton();
20065
20066 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20067 left_padding += if !is_singleton {
20068 em_width * 4.0
20069 } else if show_code_actions || show_runnables || show_breakpoints {
20070 em_width * 3.0
20071 } else if show_git_gutter && show_line_numbers {
20072 em_width * 2.0
20073 } else if show_git_gutter || show_line_numbers {
20074 em_width
20075 } else {
20076 px(0.)
20077 };
20078
20079 let shows_folds = is_singleton && gutter_settings.folds;
20080
20081 let right_padding = if shows_folds && show_line_numbers {
20082 em_width * 4.0
20083 } else if shows_folds || (!is_singleton && show_line_numbers) {
20084 em_width * 3.0
20085 } else if show_line_numbers {
20086 em_width
20087 } else {
20088 px(0.)
20089 };
20090
20091 Some(GutterDimensions {
20092 left_padding,
20093 right_padding,
20094 width: line_gutter_width + left_padding + right_padding,
20095 margin: -descent,
20096 git_blame_entries_width,
20097 })
20098 }
20099
20100 pub fn render_crease_toggle(
20101 &self,
20102 buffer_row: MultiBufferRow,
20103 row_contains_cursor: bool,
20104 editor: Entity<Editor>,
20105 window: &mut Window,
20106 cx: &mut App,
20107 ) -> Option<AnyElement> {
20108 let folded = self.is_line_folded(buffer_row);
20109 let mut is_foldable = false;
20110
20111 if let Some(crease) = self
20112 .crease_snapshot
20113 .query_row(buffer_row, &self.buffer_snapshot)
20114 {
20115 is_foldable = true;
20116 match crease {
20117 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20118 if let Some(render_toggle) = render_toggle {
20119 let toggle_callback =
20120 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20121 if folded {
20122 editor.update(cx, |editor, cx| {
20123 editor.fold_at(buffer_row, window, cx)
20124 });
20125 } else {
20126 editor.update(cx, |editor, cx| {
20127 editor.unfold_at(buffer_row, window, cx)
20128 });
20129 }
20130 });
20131 return Some((render_toggle)(
20132 buffer_row,
20133 folded,
20134 toggle_callback,
20135 window,
20136 cx,
20137 ));
20138 }
20139 }
20140 }
20141 }
20142
20143 is_foldable |= self.starts_indent(buffer_row);
20144
20145 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20146 Some(
20147 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20148 .toggle_state(folded)
20149 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20150 if folded {
20151 this.unfold_at(buffer_row, window, cx);
20152 } else {
20153 this.fold_at(buffer_row, window, cx);
20154 }
20155 }))
20156 .into_any_element(),
20157 )
20158 } else {
20159 None
20160 }
20161 }
20162
20163 pub fn render_crease_trailer(
20164 &self,
20165 buffer_row: MultiBufferRow,
20166 window: &mut Window,
20167 cx: &mut App,
20168 ) -> Option<AnyElement> {
20169 let folded = self.is_line_folded(buffer_row);
20170 if let Crease::Inline { render_trailer, .. } = self
20171 .crease_snapshot
20172 .query_row(buffer_row, &self.buffer_snapshot)?
20173 {
20174 let render_trailer = render_trailer.as_ref()?;
20175 Some(render_trailer(buffer_row, folded, window, cx))
20176 } else {
20177 None
20178 }
20179 }
20180}
20181
20182impl Deref for EditorSnapshot {
20183 type Target = DisplaySnapshot;
20184
20185 fn deref(&self) -> &Self::Target {
20186 &self.display_snapshot
20187 }
20188}
20189
20190#[derive(Clone, Debug, PartialEq, Eq)]
20191pub enum EditorEvent {
20192 InputIgnored {
20193 text: Arc<str>,
20194 },
20195 InputHandled {
20196 utf16_range_to_replace: Option<Range<isize>>,
20197 text: Arc<str>,
20198 },
20199 ExcerptsAdded {
20200 buffer: Entity<Buffer>,
20201 predecessor: ExcerptId,
20202 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20203 },
20204 ExcerptsRemoved {
20205 ids: Vec<ExcerptId>,
20206 removed_buffer_ids: Vec<BufferId>,
20207 },
20208 BufferFoldToggled {
20209 ids: Vec<ExcerptId>,
20210 folded: bool,
20211 },
20212 ExcerptsEdited {
20213 ids: Vec<ExcerptId>,
20214 },
20215 ExcerptsExpanded {
20216 ids: Vec<ExcerptId>,
20217 },
20218 BufferEdited,
20219 Edited {
20220 transaction_id: clock::Lamport,
20221 },
20222 Reparsed(BufferId),
20223 Focused,
20224 FocusedIn,
20225 Blurred,
20226 DirtyChanged,
20227 Saved,
20228 TitleChanged,
20229 DiffBaseChanged,
20230 SelectionsChanged {
20231 local: bool,
20232 },
20233 ScrollPositionChanged {
20234 local: bool,
20235 autoscroll: bool,
20236 },
20237 Closed,
20238 TransactionUndone {
20239 transaction_id: clock::Lamport,
20240 },
20241 TransactionBegun {
20242 transaction_id: clock::Lamport,
20243 },
20244 Reloaded,
20245 CursorShapeChanged,
20246 PushedToNavHistory {
20247 anchor: Anchor,
20248 is_deactivate: bool,
20249 },
20250}
20251
20252impl EventEmitter<EditorEvent> for Editor {}
20253
20254impl Focusable for Editor {
20255 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20256 self.focus_handle.clone()
20257 }
20258}
20259
20260impl Render for Editor {
20261 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20262 let settings = ThemeSettings::get_global(cx);
20263
20264 let mut text_style = match self.mode {
20265 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20266 color: cx.theme().colors().editor_foreground,
20267 font_family: settings.ui_font.family.clone(),
20268 font_features: settings.ui_font.features.clone(),
20269 font_fallbacks: settings.ui_font.fallbacks.clone(),
20270 font_size: rems(0.875).into(),
20271 font_weight: settings.ui_font.weight,
20272 line_height: relative(settings.buffer_line_height.value()),
20273 ..Default::default()
20274 },
20275 EditorMode::Full { .. } => TextStyle {
20276 color: cx.theme().colors().editor_foreground,
20277 font_family: settings.buffer_font.family.clone(),
20278 font_features: settings.buffer_font.features.clone(),
20279 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20280 font_size: settings.buffer_font_size(cx).into(),
20281 font_weight: settings.buffer_font.weight,
20282 line_height: relative(settings.buffer_line_height.value()),
20283 ..Default::default()
20284 },
20285 };
20286 if let Some(text_style_refinement) = &self.text_style_refinement {
20287 text_style.refine(text_style_refinement)
20288 }
20289
20290 let background = match self.mode {
20291 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20292 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20293 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20294 };
20295
20296 EditorElement::new(
20297 &cx.entity(),
20298 EditorStyle {
20299 background,
20300 local_player: cx.theme().players().local(),
20301 text: text_style,
20302 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20303 syntax: cx.theme().syntax().clone(),
20304 status: cx.theme().status().clone(),
20305 inlay_hints_style: make_inlay_hints_style(cx),
20306 inline_completion_styles: make_suggestion_styles(cx),
20307 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20308 },
20309 )
20310 }
20311}
20312
20313impl EntityInputHandler for Editor {
20314 fn text_for_range(
20315 &mut self,
20316 range_utf16: Range<usize>,
20317 adjusted_range: &mut Option<Range<usize>>,
20318 _: &mut Window,
20319 cx: &mut Context<Self>,
20320 ) -> Option<String> {
20321 let snapshot = self.buffer.read(cx).read(cx);
20322 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20323 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20324 if (start.0..end.0) != range_utf16 {
20325 adjusted_range.replace(start.0..end.0);
20326 }
20327 Some(snapshot.text_for_range(start..end).collect())
20328 }
20329
20330 fn selected_text_range(
20331 &mut self,
20332 ignore_disabled_input: bool,
20333 _: &mut Window,
20334 cx: &mut Context<Self>,
20335 ) -> Option<UTF16Selection> {
20336 // Prevent the IME menu from appearing when holding down an alphabetic key
20337 // while input is disabled.
20338 if !ignore_disabled_input && !self.input_enabled {
20339 return None;
20340 }
20341
20342 let selection = self.selections.newest::<OffsetUtf16>(cx);
20343 let range = selection.range();
20344
20345 Some(UTF16Selection {
20346 range: range.start.0..range.end.0,
20347 reversed: selection.reversed,
20348 })
20349 }
20350
20351 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20352 let snapshot = self.buffer.read(cx).read(cx);
20353 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20354 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20355 }
20356
20357 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20358 self.clear_highlights::<InputComposition>(cx);
20359 self.ime_transaction.take();
20360 }
20361
20362 fn replace_text_in_range(
20363 &mut self,
20364 range_utf16: Option<Range<usize>>,
20365 text: &str,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) {
20369 if !self.input_enabled {
20370 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20371 return;
20372 }
20373
20374 self.transact(window, cx, |this, window, cx| {
20375 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20376 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20377 Some(this.selection_replacement_ranges(range_utf16, cx))
20378 } else {
20379 this.marked_text_ranges(cx)
20380 };
20381
20382 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20383 let newest_selection_id = this.selections.newest_anchor().id;
20384 this.selections
20385 .all::<OffsetUtf16>(cx)
20386 .iter()
20387 .zip(ranges_to_replace.iter())
20388 .find_map(|(selection, range)| {
20389 if selection.id == newest_selection_id {
20390 Some(
20391 (range.start.0 as isize - selection.head().0 as isize)
20392 ..(range.end.0 as isize - selection.head().0 as isize),
20393 )
20394 } else {
20395 None
20396 }
20397 })
20398 });
20399
20400 cx.emit(EditorEvent::InputHandled {
20401 utf16_range_to_replace: range_to_replace,
20402 text: text.into(),
20403 });
20404
20405 if let Some(new_selected_ranges) = new_selected_ranges {
20406 this.change_selections(None, window, cx, |selections| {
20407 selections.select_ranges(new_selected_ranges)
20408 });
20409 this.backspace(&Default::default(), window, cx);
20410 }
20411
20412 this.handle_input(text, window, cx);
20413 });
20414
20415 if let Some(transaction) = self.ime_transaction {
20416 self.buffer.update(cx, |buffer, cx| {
20417 buffer.group_until_transaction(transaction, cx);
20418 });
20419 }
20420
20421 self.unmark_text(window, cx);
20422 }
20423
20424 fn replace_and_mark_text_in_range(
20425 &mut self,
20426 range_utf16: Option<Range<usize>>,
20427 text: &str,
20428 new_selected_range_utf16: Option<Range<usize>>,
20429 window: &mut Window,
20430 cx: &mut Context<Self>,
20431 ) {
20432 if !self.input_enabled {
20433 return;
20434 }
20435
20436 let transaction = self.transact(window, cx, |this, window, cx| {
20437 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20438 let snapshot = this.buffer.read(cx).read(cx);
20439 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20440 for marked_range in &mut marked_ranges {
20441 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20442 marked_range.start.0 += relative_range_utf16.start;
20443 marked_range.start =
20444 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20445 marked_range.end =
20446 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20447 }
20448 }
20449 Some(marked_ranges)
20450 } else if let Some(range_utf16) = range_utf16 {
20451 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20452 Some(this.selection_replacement_ranges(range_utf16, cx))
20453 } else {
20454 None
20455 };
20456
20457 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20458 let newest_selection_id = this.selections.newest_anchor().id;
20459 this.selections
20460 .all::<OffsetUtf16>(cx)
20461 .iter()
20462 .zip(ranges_to_replace.iter())
20463 .find_map(|(selection, range)| {
20464 if selection.id == newest_selection_id {
20465 Some(
20466 (range.start.0 as isize - selection.head().0 as isize)
20467 ..(range.end.0 as isize - selection.head().0 as isize),
20468 )
20469 } else {
20470 None
20471 }
20472 })
20473 });
20474
20475 cx.emit(EditorEvent::InputHandled {
20476 utf16_range_to_replace: range_to_replace,
20477 text: text.into(),
20478 });
20479
20480 if let Some(ranges) = ranges_to_replace {
20481 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20482 }
20483
20484 let marked_ranges = {
20485 let snapshot = this.buffer.read(cx).read(cx);
20486 this.selections
20487 .disjoint_anchors()
20488 .iter()
20489 .map(|selection| {
20490 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20491 })
20492 .collect::<Vec<_>>()
20493 };
20494
20495 if text.is_empty() {
20496 this.unmark_text(window, cx);
20497 } else {
20498 this.highlight_text::<InputComposition>(
20499 marked_ranges.clone(),
20500 HighlightStyle {
20501 underline: Some(UnderlineStyle {
20502 thickness: px(1.),
20503 color: None,
20504 wavy: false,
20505 }),
20506 ..Default::default()
20507 },
20508 cx,
20509 );
20510 }
20511
20512 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20513 let use_autoclose = this.use_autoclose;
20514 let use_auto_surround = this.use_auto_surround;
20515 this.set_use_autoclose(false);
20516 this.set_use_auto_surround(false);
20517 this.handle_input(text, window, cx);
20518 this.set_use_autoclose(use_autoclose);
20519 this.set_use_auto_surround(use_auto_surround);
20520
20521 if let Some(new_selected_range) = new_selected_range_utf16 {
20522 let snapshot = this.buffer.read(cx).read(cx);
20523 let new_selected_ranges = marked_ranges
20524 .into_iter()
20525 .map(|marked_range| {
20526 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20527 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20528 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20529 snapshot.clip_offset_utf16(new_start, Bias::Left)
20530 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20531 })
20532 .collect::<Vec<_>>();
20533
20534 drop(snapshot);
20535 this.change_selections(None, window, cx, |selections| {
20536 selections.select_ranges(new_selected_ranges)
20537 });
20538 }
20539 });
20540
20541 self.ime_transaction = self.ime_transaction.or(transaction);
20542 if let Some(transaction) = self.ime_transaction {
20543 self.buffer.update(cx, |buffer, cx| {
20544 buffer.group_until_transaction(transaction, cx);
20545 });
20546 }
20547
20548 if self.text_highlights::<InputComposition>(cx).is_none() {
20549 self.ime_transaction.take();
20550 }
20551 }
20552
20553 fn bounds_for_range(
20554 &mut self,
20555 range_utf16: Range<usize>,
20556 element_bounds: gpui::Bounds<Pixels>,
20557 window: &mut Window,
20558 cx: &mut Context<Self>,
20559 ) -> Option<gpui::Bounds<Pixels>> {
20560 let text_layout_details = self.text_layout_details(window);
20561 let gpui::Size {
20562 width: em_width,
20563 height: line_height,
20564 } = self.character_size(window);
20565
20566 let snapshot = self.snapshot(window, cx);
20567 let scroll_position = snapshot.scroll_position();
20568 let scroll_left = scroll_position.x * em_width;
20569
20570 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20571 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20572 + self.gutter_dimensions.width
20573 + self.gutter_dimensions.margin;
20574 let y = line_height * (start.row().as_f32() - scroll_position.y);
20575
20576 Some(Bounds {
20577 origin: element_bounds.origin + point(x, y),
20578 size: size(em_width, line_height),
20579 })
20580 }
20581
20582 fn character_index_for_point(
20583 &mut self,
20584 point: gpui::Point<Pixels>,
20585 _window: &mut Window,
20586 _cx: &mut Context<Self>,
20587 ) -> Option<usize> {
20588 let position_map = self.last_position_map.as_ref()?;
20589 if !position_map.text_hitbox.contains(&point) {
20590 return None;
20591 }
20592 let display_point = position_map.point_for_position(point).previous_valid;
20593 let anchor = position_map
20594 .snapshot
20595 .display_point_to_anchor(display_point, Bias::Left);
20596 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20597 Some(utf16_offset.0)
20598 }
20599}
20600
20601trait SelectionExt {
20602 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20603 fn spanned_rows(
20604 &self,
20605 include_end_if_at_line_start: bool,
20606 map: &DisplaySnapshot,
20607 ) -> Range<MultiBufferRow>;
20608}
20609
20610impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20611 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20612 let start = self
20613 .start
20614 .to_point(&map.buffer_snapshot)
20615 .to_display_point(map);
20616 let end = self
20617 .end
20618 .to_point(&map.buffer_snapshot)
20619 .to_display_point(map);
20620 if self.reversed {
20621 end..start
20622 } else {
20623 start..end
20624 }
20625 }
20626
20627 fn spanned_rows(
20628 &self,
20629 include_end_if_at_line_start: bool,
20630 map: &DisplaySnapshot,
20631 ) -> Range<MultiBufferRow> {
20632 let start = self.start.to_point(&map.buffer_snapshot);
20633 let mut end = self.end.to_point(&map.buffer_snapshot);
20634 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20635 end.row -= 1;
20636 }
20637
20638 let buffer_start = map.prev_line_boundary(start).0;
20639 let buffer_end = map.next_line_boundary(end).0;
20640 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20641 }
20642}
20643
20644impl<T: InvalidationRegion> InvalidationStack<T> {
20645 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20646 where
20647 S: Clone + ToOffset,
20648 {
20649 while let Some(region) = self.last() {
20650 let all_selections_inside_invalidation_ranges =
20651 if selections.len() == region.ranges().len() {
20652 selections
20653 .iter()
20654 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20655 .all(|(selection, invalidation_range)| {
20656 let head = selection.head().to_offset(buffer);
20657 invalidation_range.start <= head && invalidation_range.end >= head
20658 })
20659 } else {
20660 false
20661 };
20662
20663 if all_selections_inside_invalidation_ranges {
20664 break;
20665 } else {
20666 self.pop();
20667 }
20668 }
20669 }
20670}
20671
20672impl<T> Default for InvalidationStack<T> {
20673 fn default() -> Self {
20674 Self(Default::default())
20675 }
20676}
20677
20678impl<T> Deref for InvalidationStack<T> {
20679 type Target = Vec<T>;
20680
20681 fn deref(&self) -> &Self::Target {
20682 &self.0
20683 }
20684}
20685
20686impl<T> DerefMut for InvalidationStack<T> {
20687 fn deref_mut(&mut self) -> &mut Self::Target {
20688 &mut self.0
20689 }
20690}
20691
20692impl InvalidationRegion for SnippetState {
20693 fn ranges(&self) -> &[Range<Anchor>] {
20694 &self.ranges[self.active_index]
20695 }
20696}
20697
20698fn inline_completion_edit_text(
20699 current_snapshot: &BufferSnapshot,
20700 edits: &[(Range<Anchor>, String)],
20701 edit_preview: &EditPreview,
20702 include_deletions: bool,
20703 cx: &App,
20704) -> HighlightedText {
20705 let edits = edits
20706 .iter()
20707 .map(|(anchor, text)| {
20708 (
20709 anchor.start.text_anchor..anchor.end.text_anchor,
20710 text.clone(),
20711 )
20712 })
20713 .collect::<Vec<_>>();
20714
20715 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20716}
20717
20718pub fn diagnostic_style(severity: DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20719 match severity {
20720 DiagnosticSeverity::ERROR => colors.error,
20721 DiagnosticSeverity::WARNING => colors.warning,
20722 DiagnosticSeverity::INFORMATION => colors.info,
20723 DiagnosticSeverity::HINT => colors.info,
20724 _ => colors.ignored,
20725 }
20726}
20727
20728pub fn styled_runs_for_code_label<'a>(
20729 label: &'a CodeLabel,
20730 syntax_theme: &'a theme::SyntaxTheme,
20731) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
20732 let fade_out = HighlightStyle {
20733 fade_out: Some(0.35),
20734 ..Default::default()
20735 };
20736
20737 let mut prev_end = label.filter_range.end;
20738 label
20739 .runs
20740 .iter()
20741 .enumerate()
20742 .flat_map(move |(ix, (range, highlight_id))| {
20743 let style = if let Some(style) = highlight_id.style(syntax_theme) {
20744 style
20745 } else {
20746 return Default::default();
20747 };
20748 let mut muted_style = style;
20749 muted_style.highlight(fade_out);
20750
20751 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
20752 if range.start >= label.filter_range.end {
20753 if range.start > prev_end {
20754 runs.push((prev_end..range.start, fade_out));
20755 }
20756 runs.push((range.clone(), muted_style));
20757 } else if range.end <= label.filter_range.end {
20758 runs.push((range.clone(), style));
20759 } else {
20760 runs.push((range.start..label.filter_range.end, style));
20761 runs.push((label.filter_range.end..range.end, muted_style));
20762 }
20763 prev_end = cmp::max(prev_end, range.end);
20764
20765 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
20766 runs.push((prev_end..label.text.len(), fade_out));
20767 }
20768
20769 runs
20770 })
20771}
20772
20773pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
20774 let mut prev_index = 0;
20775 let mut prev_codepoint: Option<char> = None;
20776 text.char_indices()
20777 .chain([(text.len(), '\0')])
20778 .filter_map(move |(index, codepoint)| {
20779 let prev_codepoint = prev_codepoint.replace(codepoint)?;
20780 let is_boundary = index == text.len()
20781 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
20782 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
20783 if is_boundary {
20784 let chunk = &text[prev_index..index];
20785 prev_index = index;
20786 Some(chunk)
20787 } else {
20788 None
20789 }
20790 })
20791}
20792
20793pub trait RangeToAnchorExt: Sized {
20794 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
20795
20796 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
20797 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
20798 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
20799 }
20800}
20801
20802impl<T: ToOffset> RangeToAnchorExt for Range<T> {
20803 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
20804 let start_offset = self.start.to_offset(snapshot);
20805 let end_offset = self.end.to_offset(snapshot);
20806 if start_offset == end_offset {
20807 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
20808 } else {
20809 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
20810 }
20811 }
20812}
20813
20814pub trait RowExt {
20815 fn as_f32(&self) -> f32;
20816
20817 fn next_row(&self) -> Self;
20818
20819 fn previous_row(&self) -> Self;
20820
20821 fn minus(&self, other: Self) -> u32;
20822}
20823
20824impl RowExt for DisplayRow {
20825 fn as_f32(&self) -> f32 {
20826 self.0 as f32
20827 }
20828
20829 fn next_row(&self) -> Self {
20830 Self(self.0 + 1)
20831 }
20832
20833 fn previous_row(&self) -> Self {
20834 Self(self.0.saturating_sub(1))
20835 }
20836
20837 fn minus(&self, other: Self) -> u32 {
20838 self.0 - other.0
20839 }
20840}
20841
20842impl RowExt for MultiBufferRow {
20843 fn as_f32(&self) -> f32 {
20844 self.0 as f32
20845 }
20846
20847 fn next_row(&self) -> Self {
20848 Self(self.0 + 1)
20849 }
20850
20851 fn previous_row(&self) -> Self {
20852 Self(self.0.saturating_sub(1))
20853 }
20854
20855 fn minus(&self, other: Self) -> u32 {
20856 self.0 - other.0
20857 }
20858}
20859
20860trait RowRangeExt {
20861 type Row;
20862
20863 fn len(&self) -> usize;
20864
20865 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
20866}
20867
20868impl RowRangeExt for Range<MultiBufferRow> {
20869 type Row = MultiBufferRow;
20870
20871 fn len(&self) -> usize {
20872 (self.end.0 - self.start.0) as usize
20873 }
20874
20875 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
20876 (self.start.0..self.end.0).map(MultiBufferRow)
20877 }
20878}
20879
20880impl RowRangeExt for Range<DisplayRow> {
20881 type Row = DisplayRow;
20882
20883 fn len(&self) -> usize {
20884 (self.end.0 - self.start.0) as usize
20885 }
20886
20887 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
20888 (self.start.0..self.end.0).map(DisplayRow)
20889 }
20890}
20891
20892/// If select range has more than one line, we
20893/// just point the cursor to range.start.
20894fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
20895 if range.start.row == range.end.row {
20896 range
20897 } else {
20898 range.start..range.start
20899 }
20900}
20901pub struct KillRing(ClipboardItem);
20902impl Global for KillRing {}
20903
20904const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
20905
20906enum BreakpointPromptEditAction {
20907 Log,
20908 Condition,
20909 HitCondition,
20910}
20911
20912struct BreakpointPromptEditor {
20913 pub(crate) prompt: Entity<Editor>,
20914 editor: WeakEntity<Editor>,
20915 breakpoint_anchor: Anchor,
20916 breakpoint: Breakpoint,
20917 edit_action: BreakpointPromptEditAction,
20918 block_ids: HashSet<CustomBlockId>,
20919 gutter_dimensions: Arc<Mutex<GutterDimensions>>,
20920 _subscriptions: Vec<Subscription>,
20921}
20922
20923impl BreakpointPromptEditor {
20924 const MAX_LINES: u8 = 4;
20925
20926 fn new(
20927 editor: WeakEntity<Editor>,
20928 breakpoint_anchor: Anchor,
20929 breakpoint: Breakpoint,
20930 edit_action: BreakpointPromptEditAction,
20931 window: &mut Window,
20932 cx: &mut Context<Self>,
20933 ) -> Self {
20934 let base_text = match edit_action {
20935 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
20936 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
20937 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
20938 }
20939 .map(|msg| msg.to_string())
20940 .unwrap_or_default();
20941
20942 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
20943 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20944
20945 let prompt = cx.new(|cx| {
20946 let mut prompt = Editor::new(
20947 EditorMode::AutoHeight {
20948 max_lines: Self::MAX_LINES as usize,
20949 },
20950 buffer,
20951 None,
20952 window,
20953 cx,
20954 );
20955 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
20956 prompt.set_show_cursor_when_unfocused(false, cx);
20957 prompt.set_placeholder_text(
20958 match edit_action {
20959 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
20960 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
20961 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
20962 },
20963 cx,
20964 );
20965
20966 prompt
20967 });
20968
20969 Self {
20970 prompt,
20971 editor,
20972 breakpoint_anchor,
20973 breakpoint,
20974 edit_action,
20975 gutter_dimensions: Arc::new(Mutex::new(GutterDimensions::default())),
20976 block_ids: Default::default(),
20977 _subscriptions: vec![],
20978 }
20979 }
20980
20981 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
20982 self.block_ids.extend(block_ids)
20983 }
20984
20985 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
20986 if let Some(editor) = self.editor.upgrade() {
20987 let message = self
20988 .prompt
20989 .read(cx)
20990 .buffer
20991 .read(cx)
20992 .as_singleton()
20993 .expect("A multi buffer in breakpoint prompt isn't possible")
20994 .read(cx)
20995 .as_rope()
20996 .to_string();
20997
20998 editor.update(cx, |editor, cx| {
20999 editor.edit_breakpoint_at_anchor(
21000 self.breakpoint_anchor,
21001 self.breakpoint.clone(),
21002 match self.edit_action {
21003 BreakpointPromptEditAction::Log => {
21004 BreakpointEditAction::EditLogMessage(message.into())
21005 }
21006 BreakpointPromptEditAction::Condition => {
21007 BreakpointEditAction::EditCondition(message.into())
21008 }
21009 BreakpointPromptEditAction::HitCondition => {
21010 BreakpointEditAction::EditHitCondition(message.into())
21011 }
21012 },
21013 cx,
21014 );
21015
21016 editor.remove_blocks(self.block_ids.clone(), None, cx);
21017 cx.focus_self(window);
21018 });
21019 }
21020 }
21021
21022 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21023 self.editor
21024 .update(cx, |editor, cx| {
21025 editor.remove_blocks(self.block_ids.clone(), None, cx);
21026 window.focus(&editor.focus_handle);
21027 })
21028 .log_err();
21029 }
21030
21031 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21032 let settings = ThemeSettings::get_global(cx);
21033 let text_style = TextStyle {
21034 color: if self.prompt.read(cx).read_only(cx) {
21035 cx.theme().colors().text_disabled
21036 } else {
21037 cx.theme().colors().text
21038 },
21039 font_family: settings.buffer_font.family.clone(),
21040 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21041 font_size: settings.buffer_font_size(cx).into(),
21042 font_weight: settings.buffer_font.weight,
21043 line_height: relative(settings.buffer_line_height.value()),
21044 ..Default::default()
21045 };
21046 EditorElement::new(
21047 &self.prompt,
21048 EditorStyle {
21049 background: cx.theme().colors().editor_background,
21050 local_player: cx.theme().players().local(),
21051 text: text_style,
21052 ..Default::default()
21053 },
21054 )
21055 }
21056}
21057
21058impl Render for BreakpointPromptEditor {
21059 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21060 let gutter_dimensions = *self.gutter_dimensions.lock();
21061 h_flex()
21062 .key_context("Editor")
21063 .bg(cx.theme().colors().editor_background)
21064 .border_y_1()
21065 .border_color(cx.theme().status().info_border)
21066 .size_full()
21067 .py(window.line_height() / 2.5)
21068 .on_action(cx.listener(Self::confirm))
21069 .on_action(cx.listener(Self::cancel))
21070 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21071 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21072 }
21073}
21074
21075impl Focusable for BreakpointPromptEditor {
21076 fn focus_handle(&self, cx: &App) -> FocusHandle {
21077 self.prompt.focus_handle(cx)
21078 }
21079}
21080
21081fn all_edits_insertions_or_deletions(
21082 edits: &Vec<(Range<Anchor>, String)>,
21083 snapshot: &MultiBufferSnapshot,
21084) -> bool {
21085 let mut all_insertions = true;
21086 let mut all_deletions = true;
21087
21088 for (range, new_text) in edits.iter() {
21089 let range_is_empty = range.to_offset(&snapshot).is_empty();
21090 let text_is_empty = new_text.is_empty();
21091
21092 if range_is_empty != text_is_empty {
21093 if range_is_empty {
21094 all_deletions = false;
21095 } else {
21096 all_insertions = false;
21097 }
21098 } else {
21099 return false;
21100 }
21101
21102 if !all_insertions && !all_deletions {
21103 return false;
21104 }
21105 }
21106 all_insertions || all_deletions
21107}
21108
21109struct MissingEditPredictionKeybindingTooltip;
21110
21111impl Render for MissingEditPredictionKeybindingTooltip {
21112 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21113 ui::tooltip_container(window, cx, |container, _, cx| {
21114 container
21115 .flex_shrink_0()
21116 .max_w_80()
21117 .min_h(rems_from_px(124.))
21118 .justify_between()
21119 .child(
21120 v_flex()
21121 .flex_1()
21122 .text_ui_sm(cx)
21123 .child(Label::new("Conflict with Accept Keybinding"))
21124 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21125 )
21126 .child(
21127 h_flex()
21128 .pb_1()
21129 .gap_1()
21130 .items_end()
21131 .w_full()
21132 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21133 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21134 }))
21135 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21136 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21137 })),
21138 )
21139 })
21140 }
21141}
21142
21143#[derive(Debug, Clone, Copy, PartialEq)]
21144pub struct LineHighlight {
21145 pub background: Background,
21146 pub border: Option<gpui::Hsla>,
21147 pub include_gutter: bool,
21148 pub type_id: Option<TypeId>,
21149}
21150
21151fn render_diff_hunk_controls(
21152 row: u32,
21153 status: &DiffHunkStatus,
21154 hunk_range: Range<Anchor>,
21155 is_created_file: bool,
21156 line_height: Pixels,
21157 editor: &Entity<Editor>,
21158 _window: &mut Window,
21159 cx: &mut App,
21160) -> AnyElement {
21161 h_flex()
21162 .h(line_height)
21163 .mr_1()
21164 .gap_1()
21165 .px_0p5()
21166 .pb_1()
21167 .border_x_1()
21168 .border_b_1()
21169 .border_color(cx.theme().colors().border_variant)
21170 .rounded_b_lg()
21171 .bg(cx.theme().colors().editor_background)
21172 .gap_1()
21173 .occlude()
21174 .shadow_md()
21175 .child(if status.has_secondary_hunk() {
21176 Button::new(("stage", row as u64), "Stage")
21177 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21178 .tooltip({
21179 let focus_handle = editor.focus_handle(cx);
21180 move |window, cx| {
21181 Tooltip::for_action_in(
21182 "Stage Hunk",
21183 &::git::ToggleStaged,
21184 &focus_handle,
21185 window,
21186 cx,
21187 )
21188 }
21189 })
21190 .on_click({
21191 let editor = editor.clone();
21192 move |_event, _window, cx| {
21193 editor.update(cx, |editor, cx| {
21194 editor.stage_or_unstage_diff_hunks(
21195 true,
21196 vec![hunk_range.start..hunk_range.start],
21197 cx,
21198 );
21199 });
21200 }
21201 })
21202 } else {
21203 Button::new(("unstage", row as u64), "Unstage")
21204 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21205 .tooltip({
21206 let focus_handle = editor.focus_handle(cx);
21207 move |window, cx| {
21208 Tooltip::for_action_in(
21209 "Unstage Hunk",
21210 &::git::ToggleStaged,
21211 &focus_handle,
21212 window,
21213 cx,
21214 )
21215 }
21216 })
21217 .on_click({
21218 let editor = editor.clone();
21219 move |_event, _window, cx| {
21220 editor.update(cx, |editor, cx| {
21221 editor.stage_or_unstage_diff_hunks(
21222 false,
21223 vec![hunk_range.start..hunk_range.start],
21224 cx,
21225 );
21226 });
21227 }
21228 })
21229 })
21230 .child(
21231 Button::new(("restore", row as u64), "Restore")
21232 .tooltip({
21233 let focus_handle = editor.focus_handle(cx);
21234 move |window, cx| {
21235 Tooltip::for_action_in(
21236 "Restore Hunk",
21237 &::git::Restore,
21238 &focus_handle,
21239 window,
21240 cx,
21241 )
21242 }
21243 })
21244 .on_click({
21245 let editor = editor.clone();
21246 move |_event, window, cx| {
21247 editor.update(cx, |editor, cx| {
21248 let snapshot = editor.snapshot(window, cx);
21249 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21250 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21251 });
21252 }
21253 })
21254 .disabled(is_created_file),
21255 )
21256 .when(
21257 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21258 |el| {
21259 el.child(
21260 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21261 .shape(IconButtonShape::Square)
21262 .icon_size(IconSize::Small)
21263 // .disabled(!has_multiple_hunks)
21264 .tooltip({
21265 let focus_handle = editor.focus_handle(cx);
21266 move |window, cx| {
21267 Tooltip::for_action_in(
21268 "Next Hunk",
21269 &GoToHunk,
21270 &focus_handle,
21271 window,
21272 cx,
21273 )
21274 }
21275 })
21276 .on_click({
21277 let editor = editor.clone();
21278 move |_event, window, cx| {
21279 editor.update(cx, |editor, cx| {
21280 let snapshot = editor.snapshot(window, cx);
21281 let position =
21282 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21283 editor.go_to_hunk_before_or_after_position(
21284 &snapshot,
21285 position,
21286 Direction::Next,
21287 window,
21288 cx,
21289 );
21290 editor.expand_selected_diff_hunks(cx);
21291 });
21292 }
21293 }),
21294 )
21295 .child(
21296 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21297 .shape(IconButtonShape::Square)
21298 .icon_size(IconSize::Small)
21299 // .disabled(!has_multiple_hunks)
21300 .tooltip({
21301 let focus_handle = editor.focus_handle(cx);
21302 move |window, cx| {
21303 Tooltip::for_action_in(
21304 "Previous Hunk",
21305 &GoToPreviousHunk,
21306 &focus_handle,
21307 window,
21308 cx,
21309 )
21310 }
21311 })
21312 .on_click({
21313 let editor = editor.clone();
21314 move |_event, window, cx| {
21315 editor.update(cx, |editor, cx| {
21316 let snapshot = editor.snapshot(window, cx);
21317 let point =
21318 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21319 editor.go_to_hunk_before_or_after_position(
21320 &snapshot,
21321 point,
21322 Direction::Prev,
21323 window,
21324 cx,
21325 );
21326 editor.expand_selected_diff_hunks(cx);
21327 });
21328 }
21329 }),
21330 )
21331 },
21332 )
21333 .into_any_element()
21334}